/* ================================================================
   youth/site.css — the D (navy field) theme as a real site.

   Adapted from preview/site.css, which is where every one of these
   components was designed and reviewed against live data. Keep the two
   in step: the preview is still the design surface, and a component
   that diverges here has no reviewed version to fall back to.

   Every converted page in youth/ links this plus ./shared/tokens.css
   and runs data-tenant="pickup-usa" data-surface="navy".

   Tokens only. No literal hex except the two franchise brand values
   that are deliberately NOT in the tenant slot:
     #E53E30  PANTONE 179 C — chrome only, never beside a stat
     (the navy is the field itself, via data-surface)

   Component language, carried from mock-d-navy:
     · panels are cut FROM the field — translucent over one wash
     · every data body is a table, mono numerals, tabular alignment
     · hierarchy from weight/case/colour, not size

   What this file has that preview/site.css does not: the footer here
   carries the Coach and Admin links and the build-stamped version, all
   three of which the preview chrome drops (plan §2.1, §3 rule 2).
   ================================================================ */

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

body {
  margin: 0; background: var(--bg); color: var(--ink);
  font-family: var(--font-body); line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  padding-bottom: 88px; position: relative;
}
@media (min-width: 900px) { body { padding-bottom: 32px; } }

/* one continuous wash, so panels read as cut from the field */
body::before {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(ellipse 120% 70% at 12% -10%, color-mix(in oklab, var(--accent) 14%, transparent), transparent 60%),
    radial-gradient(ellipse 90% 60% at 96% 8%, color-mix(in oklab, #E53E30 10%, transparent), transparent 62%);
}

.shell { position: relative; z-index: 1; max-width: 520px; margin: 0 auto; padding: 0 16px; }
@media (min-width: 900px) { .shell { max-width: 1160px; padding: 0 28px; } }

h1, h2, h3, .display {
  font-family: var(--font-display);
  font-variation-settings: 'wdth' var(--display-width);
  letter-spacing: var(--display-tracking);
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------------- masthead ---------------- */
.masthead { padding: 16px 0 14px; border-bottom: 1px solid var(--border); position: relative; z-index: 1; }
.masthead .row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
/* 132px render from the 400px asset — the package forbids scaling up */
.masthead .lockup { width: 132px; height: auto; display: block; }
.masthead .where {
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-secondary);
  border-left: 1px solid var(--border); padding-left: 14px;
}
/* No nav here by design — see chrome.js. The right-hand slot carries the
   StatMason attribution instead, which is the header half of the two-place
   rule in docs/visual-refresh/plan.md §2.1 (the footer carries the other). */
.masthead .powered-by {
  margin-left: auto; display: flex; align-items: center; gap: 8px;
  text-decoration: none;
}
.masthead .powered-by span {
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-muted);
}
/* The lockup is wordmark-plus-mark, so it needs more height than a bare word
   at the same optical weight as the tenant lockup opposite it. */
.masthead .powered-by img { width: auto; height: 26px; display: block; opacity: .92; }
.masthead .powered-by:hover img { opacity: 1; }
@media (max-width: 620px) {
  .masthead .powered-by { margin-left: 0; }
  .masthead .powered-by span { display: none; }
}

/* ---------------- page head ---------------- */
.pagehead { padding: 26px 0 22px; }
.pagehead .eyebrow {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--ink-muted); margin-bottom: 10px;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.pagehead h1 { margin: 0 0 6px; font-weight: 800; text-transform: uppercase; line-height: .96;
  font-size: clamp(1.9rem, 6vw, 3rem); }
.pagehead h1 .thin { color: var(--ink-secondary); font-weight: 600; }
.pagehead h1 .hot { color: var(--accent); }
.pagehead p { margin: 0; color: var(--ink-secondary); max-width: 52ch; }
.backlink { font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; }

/* ---------------- panels ---------------- */
.panel {
  background: color-mix(in oklab, var(--surface) 72%, transparent);
  backdrop-filter: blur(16px) saturate(130%);
  -webkit-backdrop-filter: blur(16px) saturate(130%);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  margin-bottom: 14px; overflow: hidden;
}
.panel.lift { box-shadow: var(--lift); }
.p-hd {
  display: flex; align-items: center; gap: 10px; padding: 11px 16px;
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-secondary);
}
.p-hd .more { margin-left: auto; color: var(--accent); text-decoration: none; }
.p-bd { padding: 14px 16px; }
.p-bd.flush { padding: 0; }
.empty { padding: 22px 16px; text-align: center; color: var(--ink-muted); font-size: .9rem; }

/* ---------------- prose ----------------
   Admin-authored Markdown, rendered by shared/markdown.js. Three surfaces show
   it now — a division's curriculum summary, the practice plan inside the home
   calendar's popup, and the coach's week page — so it stops being three
   near-identical local blocks and becomes one component.

   Covers exactly what the renderer can emit (headings, bold, italic, code,
   links, lists, blockquotes, rules) and nothing else; there is no point styling
   tables or images it cannot produce.

   Measure is capped in ch, not px. A practice plan is read on a phone at the
   gym and on a laptop at a kitchen table, and 1,100px of unbroken instruction
   text is unreadable at the second. */
.prose { color: var(--ink-secondary); font-size: 1rem; line-height: 1.75;
  max-width: 68ch; word-break: break-word; }
.prose > :first-child { margin-top: 0; }
.prose > :last-child { margin-bottom: 0; }
.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
  font-family: var(--font-display); font-weight: 800; color: var(--ink);
  font-variation-settings: 'wdth' var(--display-width);
  text-transform: uppercase; letter-spacing: var(--display-tracking);
  line-height: 1.12; margin: 24px 0 10px;
}
.prose h1 { font-size: 1.5rem; }
.prose h2 { font-size: 1.25rem; }
.prose h3 { font-size: 1.08rem; }
.prose h4, .prose h5, .prose h6 { font-size: .98rem; color: var(--ink-secondary); }
.prose p { margin: 0 0 12px; }
.prose ul, .prose ol { margin: 0 0 14px; padding-left: 22px; }
.prose li { margin: 5px 0; }
.prose li::marker { color: var(--accent); }
.prose strong { color: var(--ink); font-weight: 700; }
.prose a { color: var(--accent); }
.prose code { font-family: var(--font-mono); font-size: .88em; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px; }
/* A quote is a coaching note set aside from the drill it belongs to, so it gets
   an accent rule rather than a box — a second bordered rectangle inside a
   bordered panel reads as a nesting mistake. */
.prose blockquote { margin: 0 0 14px; padding: 2px 0 2px 14px;
  border-left: 2px solid var(--accent-edge, var(--border-strong)); color: var(--ink-secondary); }
.prose hr { border: none; border-top: 1px solid var(--border); margin: 22px 0; }
/* Tighter, for the calendar popup where the plan is a preview rather than the
   page's subject. */
.prose.sm { font-size: .88rem; line-height: 1.6; }
.prose.sm h1, .prose.sm h2, .prose.sm h3 { font-size: 1rem; margin: 14px 0 6px; }

/* A table wider than its panel scrolls INSIDE the panel. Load-bearing on
   mobile: .panel sets overflow:hidden to keep its rounded corners, so without
   this wrapper the columns past the fold are not merely off-screen, they are
   unreachable — and 320/390 is where most of this site is read. The divisions
   comparison table loses four of its eight columns that way.

   min-width:max-content rather than a magic pixel value, so the rule works for
   every table: on a wide viewport max-content is narrower than the panel and
   `width:100%` wins, and on a narrow one it is wider and the container scrolls.

   preview/site.css does not have this yet — it has the same bug. */
.tscroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.tscroll > table { min-width: max-content; }

/* division edges — the one place divisions own a hue */
/* Qualified by the components that carry them, and that is the whole point:
   as bare `.d-rookies` these lost to `.dcard`/`.dtile`'s `border` shorthand on
   source order — same specificity, declared later — so the feature was dead
   everywhere it was used and nobody could see that from reading this block.
   Naming the two components makes the coupling greppable instead of an
   invisible dependency on where the rules sit in the file. A third component
   that wants a division edge adds itself here.

   preview/site.css still has the unqualified version, so the edges do not
   paint there yet. */
.dcard.d-rookies,  .dtile.d-rookies  { border-top: 2px solid var(--event-camp); }
.dcard.d-starters, .dtile.d-starters { border-top: 2px solid #E53E30; }
.dcard.d-allstars, .dtile.d-allstars { border-top: 2px solid var(--accent); }

/* ---------------- tables ---------------- */
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
thead th {
  text-align: left; font-family: var(--font-mono); font-weight: 400; font-size: 9.5px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-muted);
  padding: 8px 16px; border-bottom: 1px solid var(--border); white-space: nowrap;
}
tbody td { padding: 9px 16px; border-bottom: 1px solid var(--border); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--surface-2); }
td.rk { width: 26px; font-family: var(--font-mono); font-size: 11px; color: var(--ink-muted); }
tbody tr.top td.rk { color: var(--gold); }
td.nm { font-weight: 600; }
td.nm a { color: var(--accent); }
td.tm { color: var(--ink-secondary); font-family: var(--font-mono); font-size: 11px; }
td.n, th.n { text-align: right; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
td.n b { font-weight: 600; color: var(--ink); }
.w { color: var(--win); font-weight: 700; }
.l { color: var(--loss); font-weight: 700; }

/* ---------------- next game ---------------- */
.vs { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 12px; }
.vs .t { text-align: center; min-width: 0; }
.vs .t b { display: block; font-family: var(--font-display); font-weight: 800;
  font-size: 1.2rem; text-transform: uppercase; line-height: 1.1;
  font-variation-settings: 'wdth' var(--display-width); }
.vs .t a { color: inherit; }
.vs .t span { font-family: var(--font-mono); font-size: .72rem; color: var(--ink-secondary); }
.vs .mid { font-family: var(--font-mono); font-size: .68rem; color: var(--ink-muted); letter-spacing: .14em; }
.when {
  display: flex; gap: 10px; justify-content: center; flex-wrap: wrap;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border);
  font-family: var(--font-mono); font-size: .76rem; color: var(--ink-secondary);
}
.when b { color: var(--ink); font-weight: 600; }
.live { display: inline-flex; align-items: center; gap: 6px; color: var(--win); }
.live i { width: 7px; height: 7px; border-radius: 50%; background: var(--win); display: block;
  animation: blink 1.6s infinite; }
@keyframes blink { 0%,100% { opacity: 1; } 50% { opacity: .3; } }
@media (prefers-reduced-motion: reduce) { .live i { animation: none; } }

/* ---------------- buttons ---------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 48px; padding: 0 26px;
  font-family: var(--font-display); font-weight: 700; font-size: .92rem; text-transform: uppercase;
  border: 1px solid transparent; border-radius: var(--cta-radius); cursor: pointer;
  font-variation-settings: 'wdth' var(--display-width); text-decoration: none;
}
.btn-primary { background: var(--accent); color: var(--on-accent);
  clip-path: var(--slant); padding-left: calc(26px + var(--slant-pad)); }
.btn-primary:hover { background: var(--accent-deep); text-decoration: none; }
.btn-quiet { background: transparent; color: var(--ink); border-color: var(--border-strong); }
.btn-quiet:hover { background: var(--surface-2); text-decoration: none; }
.btn-block { width: 100%; }
/* the one place franchise red carries a label — 1.2rem/700 = 19.2px bold
   clears AA Large, where white-on-red is 3:1. Do not shrink it. */
.btn-brand { background: #E53E30; color: #fff; font-size: 1.05rem;
  clip-path: var(--slant); padding-left: calc(26px + var(--slant-pad)); }
.btn-brand:hover { background: #C4291F; text-decoration: none; }

/* ---------------- chips / tags ---------------- */
.tags { display: flex; gap: 8px; flex-wrap: wrap; }
.tag {
  display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px;
  border-radius: var(--r-pill); border: 1px solid var(--border-strong);
  font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-secondary); text-decoration: none;
}
.tag:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
.tag.on { background: var(--accent-dim); border-color: var(--accent-edge); color: var(--accent); }

/* ---------------- stat grid ---------------- */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); }
.stats > div { padding: 14px 10px; text-align: center; border-right: 1px solid var(--border); }
.stats > div:last-child { border-right: 0; }
.stats b { display: block; font-family: var(--font-display); font-weight: 800; font-size: 1.55rem;
  line-height: 1; font-variation-settings: 'wdth' var(--display-width); }
.stats i {
  display: block; margin-top: 5px; font-style: normal;
  font-family: var(--font-mono); font-size: 9px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-muted);
}
.stats .rank { display: block; margin-top: 4px; font-family: var(--font-mono); font-size: 9px;
  color: var(--accent); }
.stats > div.lead b { color: var(--accent); }

/* ---------------- calendar ---------------- */
.cal-hd { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 11px 16px;
  border-bottom: 1px solid var(--border); }
.cal-month { font-family: var(--font-display); font-weight: 700; font-size: 1rem;
  font-variation-settings: 'wdth' var(--display-width); min-width: 9ch; }
.cal-nav { display: flex; gap: 4px; }
.cal-nav button, .cal-tab {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
  padding: 6px 11px; border-radius: var(--r-pill); cursor: pointer;
  background: var(--surface-2); color: var(--ink-secondary); border: 1px solid var(--border);
}
.cal-tabs { margin-left: auto; display: flex; gap: 4px; flex-wrap: wrap; }
.cal-views { display: flex; gap: 4px; }
.cal-tab[aria-selected="true"] { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; padding: 10px; }
.cal-dow { font-family: var(--font-mono); font-size: 9px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-muted); text-align: center; padding-bottom: 4px; }
.cal-cell { min-height: 76px; padding: 5px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid transparent; font-size: 11px; }
.cal-cell.dim { opacity: .3; }
.cal-cell.today { border-color: var(--accent); background: var(--accent-wash); }
.cal-date { font-family: var(--font-mono); color: var(--ink-secondary); font-size: 10px; }
/* The event colour is carried by the tint AND the label colour, so the
   chip needs no edge stripe on top of that — it would be a third copy
   of the same signal. */
.cal-ev {
  display: block; width: 100%; margin-top: 3px; padding: 3px 6px; border-radius: 4px;
  font-size: 9.5px; font-weight: 600; line-height: 1.3; text-align: left; cursor: pointer;
  background: color-mix(in oklab, var(--ec) 20%, transparent);
  color: var(--ec); border: 1px solid color-mix(in oklab, var(--ec) 32%, transparent);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-family: var(--font-body);
}
.cal-more { font-family: var(--font-mono); font-size: 9px; color: var(--ink-muted); padding-left: 2px; }
@media (max-width: 560px) { .cal-cell { min-height: 54px; } .cal-ev { font-size: 0; padding: 3px; } }

/* ---------------- calendar: week view ----------------
   Rows are days, columns are hour slots. The month grid answers "which days
   are busy"; this answers "what is on, and when". Same chips, same colours,
   same click target — only the arrangement changes, so switching views does
   not feel like arriving at a different feature.

   The grid can be wider than the panel when a week uses many hours, so it
   scrolls horizontally inside .cal-scroll rather than squeezing columns until
   the labels are unreadable. The month grid never overflows, so the wrapper
   costs it nothing. */
.cal-scroll { overflow-x: auto; }

.cal-week { display: grid; gap: 3px; padding: 10px; min-width: max-content; }

.wk-corner { }
.wk-hr {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-muted);
  text-align: center; padding-bottom: 4px; align-self: end;
}

/* The day label is the row's anchor, so it carries the weight the month grid
   gives its date numbers. */
.wk-day {
  display: flex; align-items: baseline; gap: 6px;
  padding: 6px 8px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid transparent;
}
.wk-day b {
  font-family: var(--font-display); font-weight: 800; font-size: .82rem;
  text-transform: uppercase; color: var(--ink);
  font-variation-settings: 'wdth' var(--display-width);
}
.wk-day span { font-family: var(--font-mono); font-size: 9.5px; color: var(--ink-muted); }

.wk-cell {
  min-height: 42px; padding: 4px; border-radius: var(--r-sm);
  background: color-mix(in oklab, var(--surface-2) 55%, transparent);
  border: 1px solid transparent;
}
.wk-day.today, .wk-cell.today { border-color: color-mix(in oklab, var(--accent) 45%, transparent); }
.wk-day.today { background: var(--accent-wash); }

/* Chips wrap to the full cell width here rather than being clipped to one
   line: a week view has the vertical room a month cell does not, and an
   ellipsised "Team Jourdan vs. T…" helps nobody. */
.cal-week .cal-ev {
  white-space: normal; overflow: visible; text-overflow: clip;
  font-size: 9px; line-height: 1.25; margin-top: 0;
}
.cal-week .cal-ev + .cal-ev { margin-top: 3px; }

/* The mobile month grid hides chip text to fit seven columns; the week view
   scrolls instead, so its labels stay readable at every width. */
@media (max-width: 560px) {
  .cal-week .cal-ev { font-size: 9px; padding: 3px 6px; }
}

.legend { display: flex; gap: 12px; flex-wrap: wrap; padding: 0 16px 12px;
  font-family: var(--font-mono); font-size: 9px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-muted); }
.legend span { display: inline-flex; align-items: center; gap: 5px; }
.legend i { width: 8px; height: 8px; border-radius: 2px; background: var(--ec); display: block; }

/* event detail popup */
.pop { position: fixed; inset: 0; z-index: var(--z-modal); display: none;
  align-items: center; justify-content: center; padding: 20px;
  background: color-mix(in oklab, var(--bg) 78%, transparent); }
.pop[open] { display: flex; }
.pop-card { background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--r-lg); padding: 20px; max-width: 380px; width: 100%; box-shadow: var(--lift); }
/* Event type, carried by the same colour the calendar grid and legend use.
   Tinted rather than solid-filled: on a translucent-panel theme a solid pill
   reads as a button, and this is a label. */
.pop-card .pop-type {
  display: inline-block; margin-bottom: 10px;
  padding: 3px 9px; border-radius: 999px;
  background: color-mix(in oklab, var(--ec) 18%, transparent);
  border: 1px solid color-mix(in oklab, var(--ec) 42%, transparent);
  color: var(--ec);
  font-family: var(--font-mono); font-size: 9.5px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase;
}
.pop-card .pop-type[hidden] { display: none; }
.pop-card h3 { margin: 0 0 6px; font-size: 1.1rem; font-weight: 700; }
.pop-card p { margin: 0 0 4px; color: var(--ink-secondary); font-size: .9rem; }

/* Final score. Its own line rather than appended to the title, which on a real
   matchup ("Team Jeremy vs. Team Jourdan · 43–26") wraps to two lines and
   buries the number that people opened the popup to read. Winner carried by
   --win, matching the Latest results panel; the loser stays neutral. */
.pop-card .pop-score {
  display: flex; align-items: baseline; gap: 8px;
  margin: 0 0 10px;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 1.35rem; font-weight: 600; color: var(--ink-secondary);
}
.pop-card .pop-score[hidden] { display: none; }
.pop-card .pop-score .fin {
  font-size: 9.5px; font-weight: 700; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-muted);
}
.pop-card .pop-score .dash { color: var(--ink-muted); }
.pop-card .pop-score .win { color: var(--win); font-weight: 700; }
.pop-card .pop-actions { display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
.pop-card .pop-actions .btn[hidden] { display: none; }

/* ---------------- player search list ---------------- */
.search {
  width: 100%; padding: 11px 14px; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--r-md); color: var(--ink);
  font-family: var(--font-body); font-size: .92rem;
}
.search::placeholder { color: var(--placeholder); }
.search:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
/* ---------------- form fields ----------------
   .search was the only input the public site had. An auth page needs labelled
   fields, and shared/pages/login.html will need the same shapes when it is
   converted — so these are written as a general form vocabulary rather than as
   a login-page special case, and are built from tokens ONLY so they can be
   lifted into a shared stylesheet without rewriting.

   The focus ring is an outline, not a border colour change: a border swap moves
   nothing but is easy to miss, and an outline is drawn outside the box so the
   field does not shift by a pixel when it takes focus. */
.field { display: block; margin-bottom: 14px; }
.field > span {
  display: block; margin-bottom: 6px;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-secondary);
}
.field input, .field select, .field textarea {
  width: 100%; padding: 12px 14px; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--r-md); color: var(--ink);
  font-family: var(--font-body); font-size: .95rem;
}
.field input::placeholder { color: var(--placeholder); }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.field input:disabled { opacity: .55; cursor: not-allowed; }

/* An error belongs to the form, not to one field — the ones these pages produce
   ("this login is not linked to a coach profile") are about the attempt, not
   about which box was wrong. role="alert" so a screen reader is told without
   having to be looking at it. */
.form-error {
  margin: 0 0 14px; padding: 11px 13px; border-radius: var(--r-md);
  background: color-mix(in oklab, var(--loss) 12%, transparent);
  border: 1px solid color-mix(in oklab, var(--loss) 38%, transparent);
  color: var(--loss); font-size: .88rem;
}
.form-error:empty { display: none; }

/* ---------------- auth card ----------------
   A single centred panel on an otherwise empty field. Deliberately NOT inside
   .shell: a sign-in page has no masthead, no back-links and nothing to scroll,
   so the page's whole job is one card and the shell's column would just offset
   it. min-height uses dvh so mobile Safari's collapsing toolbar cannot leave
   the card sitting under the fold. */
.auth-wrap {
  min-height: 100dvh; display: grid; place-items: center;
  padding: 24px 16px; position: relative; z-index: 1;
}
.auth-card {
  width: 100%; max-width: 380px; padding: 28px 24px;
  background: color-mix(in oklab, var(--surface) 82%, transparent);
  backdrop-filter: blur(16px) saturate(130%);
  -webkit-backdrop-filter: blur(16px) saturate(130%);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--lift);
}
.auth-card .lockup { display: block; width: 118px; height: auto; margin: 0 auto 18px; }
.auth-card h1 {
  margin: 0 0 4px; text-align: center;
  font-family: var(--font-display); font-weight: 800; text-transform: uppercase;
  font-variation-settings: 'wdth' var(--display-width);
  letter-spacing: var(--display-tracking); font-size: 1.5rem;
}
.auth-card .sub {
  margin: 0 0 22px; text-align: center; color: var(--ink-secondary); font-size: .9rem;
}
.auth-card .btn { width: 100%; margin-top: 4px; }
.auth-foot {
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border);
  text-align: center; font-family: var(--font-mono); font-size: 10px;
  letter-spacing: .1em; text-transform: uppercase;
}

.search-wrap { padding: 12px 16px; position: sticky; top: 0; z-index: 2;
  background: color-mix(in oklab, var(--surface) 94%, transparent);
  backdrop-filter: blur(10px); border-bottom: 1px solid var(--border); }
.scroll { max-height: 460px; overflow-y: auto; }

/* ---------------- coach grid ---------------- */
/* The floor has to be low enough for TWO columns inside the narrowest shell.
   At 320px the panel has 256px of usable width, so a 128px floor needs
   128+12+128 = 268 and auto-fit silently drops to ONE column — five coaches
   became five 256px-square photos stacked, 1,660px of scrolling on the width
   this site is mostly read at. 96px fits two there and three at 390. */
.coaches { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 12px; padding: 14px 16px; }
.coach { text-align: center; text-decoration: none; color: inherit; }
.coach:hover { text-decoration: none; }
.coach .av {
  width: 100%; aspect-ratio: 1; border-radius: var(--r-md); display: grid; place-items: center;
  background: var(--surface-2); border: 1px solid var(--border); margin-bottom: 8px;
  font-family: var(--font-display); font-weight: 800; font-size: 1.5rem; color: var(--ink-muted);
  font-variation-settings: 'wdth' var(--display-width);
}
.coach:hover .av { border-color: var(--accent); color: var(--accent); }
.coach b { display: block; font-size: .88rem; font-weight: 600; }
.coach span { font-family: var(--font-mono); font-size: 9.5px; color: var(--ink-muted);
  letter-spacing: .08em; text-transform: uppercase; }

.avatar-lg {
  width: 132px; height: 132px; border-radius: var(--r-pill); flex: 0 0 auto;
  display: grid; place-items: center; background: var(--surface-2);
  border: 1px solid var(--border-strong); font-family: var(--font-display);
  font-weight: 800; font-size: 2.6rem; color: var(--ink-muted);
  font-variation-settings: 'wdth' var(--display-width);
}

/* ---------------- latest results ---------------- */
/* The one block where --accent sits directly beside --win, which is the
   decisive test for a tenant accent: if a franchise colour reads as "the
   winner" it has to be rejected. Keep them adjacent — that adjacency is the
   point, not an accident of layout. */
/* The whole row is the link, so it is an <a> laid out as a grid rather than a
   table row — see the note in index.html for why a click handler on a <tr>
   was the worse trade. */
.res-row {
  display: grid; grid-template-columns: 1fr auto; align-items: start; gap: 12px;
  padding: 10px 16px; border-bottom: 1px solid var(--border);
  color: inherit; text-decoration: none;
}
.res-row:last-child { border-bottom: 0; }
.res-row:hover { background: var(--surface-2); text-decoration: none; }
.res-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.res { font-size: 13.5px; min-width: 0; }
.res .win { color: var(--win); font-weight: 700; }
.res .lose { color: var(--ink-secondary); }
.res .vs-def { color: var(--ink-muted); }
.res .res-meta {
  display: block; margin-top: 3px;
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-muted);
}
.res .res-div { color: var(--accent); }

.res-score {
  white-space: nowrap;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 13.5px; color: var(--ink-secondary);
}
.res-score b { color: var(--win); font-weight: 700; }
.res-score .dash { color: var(--ink-muted); padding: 0 1px; }

/* ---------------- announcements ---------------- */
.ann { padding: 12px 16px; border-bottom: 1px solid var(--border); }
.ann:last-child { border-bottom: 0; }
/* The DATE is the headline. `announcements` is (id, ann_date, body) with no
   title column, so there is nothing else to promote — and for a parent
   scanning the panel, "is this new?" is the actual question. */
.ann time {
  display: block; margin-bottom: 5px;
  font-family: var(--font-display); font-weight: 700; font-size: 1rem;
  letter-spacing: -.01em; color: var(--ink);
}
.ann p { margin: 0; color: var(--ink-secondary); font-size: .86rem; }

/* ---------------- potw ---------------- */
.potw { display: flex; gap: 12px; align-items: center; padding: 12px 16px; border-bottom: 1px solid var(--border); }
/* Items are nested inside .potw-group now, so a bare :last-child would strip
   the rule from the last row of EVERY group. Only the final row of the final
   group should lose it. */
.potw-group:last-child .potw:last-child { border-bottom: 0; }
.potw .m { width: 38px; height: 38px; flex: 0 0 auto; border-radius: var(--r-sm); display: grid;
  place-items: center; background: var(--accent-dim); border: 1px solid var(--accent-edge);
  color: var(--accent); font-family: var(--font-mono); font-size: 9px; font-weight: 700; }
.potw b { display: block; font-weight: 700; font-size: .95rem; }
.potw span { font-family: var(--font-mono); font-size: .74rem; color: var(--ink-secondary); }

/* ---------------- layout ---------------- */
@media (min-width: 900px) {
  .grid { display: grid; grid-template-columns: 1.4fr .6fr; gap: 16px; align-items: start; }
  .grid > aside { position: sticky; top: 16px; }
  /* A grid item's min-width is `auto`, meaning "no smaller than my content",
     so anything wide inside main — the transposed game log is the one that
     bites — refuses to shrink and shoves the sidebar off the right edge
     instead. The player profile lost its whole aside column that way, and the
     page reported 50px of horizontal overflow rather than scrolling the table.
     With min-width:0 the track wins and the scroll container does its job. */
  .grid > main, .grid > aside { min-width: 0; }
}

/* ---------------- division page bits ---------------- */
.play-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(84px, 1fr)); }
.play-strip > div { padding: 12px 8px; text-align: center; border-right: 1px solid var(--border); }
.play-strip > div:last-child { border-right: 0; }
.play-strip b { display: block; font-family: var(--font-display); font-weight: 800; font-size: 1.05rem;
  font-variation-settings: 'wdth' var(--display-width); }
.play-strip i { display: block; margin-top: 4px; font-style: normal; font-family: var(--font-mono);
  font-size: 9px; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-muted); }
.skill { padding: 11px 16px; border-bottom: 1px solid var(--border); }
.skill:last-child { border-bottom: 0; }
.skill b { display: block; font-weight: 700; font-size: .92rem; }
.skill p { margin: 2px 0 0; color: var(--ink-secondary); font-size: .85rem; }
details > summary { list-style: none; cursor: pointer; }
details > summary::-webkit-details-marker { display: none; }

/* ---------------- division tiles (home page) ----------------
   Compact clickable card: photo, grade range, team count. Distinct from
   .dcard on all-divisions.html, which is the long-form version with tagline
   and comparison stats — this one has to sit inside a panel alongside a
   calendar and a player list without dominating them.

   auto-fit rather than a fixed 3-up: three tiles fit the 520px mobile shell
   at a 140px floor, and the same rule spreads them across the wide column on
   desktop. One grid, no breakpoint. */
.dtiles { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.dtile {
  display: block; text-decoration: none; color: inherit; overflow: hidden;
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: color-mix(in oklab, var(--surface-2) 60%, transparent);
  transition: border-color .15s ease, transform .15s ease;
}
.dtile:hover { border-color: var(--border-strong); text-decoration: none; transform: translateY(-2px); }
.dtile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .dtile { transition: none; } .dtile:hover { transform: none; } }

/* Two background layers. The real photo sits on top; when the file does not
   exist that layer paints nothing and the placeholder shows through, so a
   division with no photo yet degrades to art instead of a broken icon. */
.dtile .shot {
  display: block; aspect-ratio: 4 / 3;
  background-image: var(--shot, none), url('img/court-placeholder.svg');
  /* The placeholder is 4:3 in a 4:3 frame, so at `cover` it fits exactly and
     there is nothing to pan. Oversizing it gives the focal point something to
     act on, so a crop chosen here is visible now and still correct once a real
     photo replaces it. */
  background-size: cover, 125%;
  background-position: var(--shot-pos, 50% 50%), var(--shot-pos, 50% 50%);
  background-repeat: no-repeat, no-repeat;
  border-bottom: 1px solid var(--border);
}
.dtile .meta { display: block; padding: 10px 12px 12px; }
.dtile .meta b {
  display: block; font-family: var(--font-display); font-weight: 800;
  font-size: 1.05rem; text-transform: uppercase; line-height: 1.1;
  font-variation-settings: 'wdth' var(--display-width);
}
.dtile .grade {
  display: block; margin-top: 3px;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .1em;
  color: var(--accent);
}
.dtile .count {
  display: block; margin-top: 6px;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-muted);
}

/* ---------------- division index cards ---------------- */
/* Cards stretch to the tallest in the row, and each one lays its bands out as
   a column with the tagline block absorbing the slack. Both halves are needed:
   stretch alone would leave a 3-line tagline pushing its stats bar 23px below
   the neighbours', and the column alone would bottom-align bands inside cards
   that were not the same height to begin with. Together, the stats row and the
   practice footer land on the same line across all three, whatever the copy
   does — including a fourth division with a one-line tagline. */
.dcards { display: grid; gap: 14px; align-items: stretch; }
@media (min-width: 820px) { .dcards { grid-template-columns: repeat(3, 1fr); } }
.dcard { display: flex; flex-direction: column; text-decoration: none; color: inherit;
  background: color-mix(in oklab, var(--surface) 72%, transparent);
  backdrop-filter: blur(16px); border: 1px solid var(--border);
  border-radius: var(--r-lg); overflow: hidden; }
/* The one band allowed to grow, so everything below it is pinned to the base. */
.dcard .top { flex: 1 0 auto; }
.dcard:hover { border-color: var(--border-strong); text-decoration: none; }
.dcard .top { padding: 18px 18px 14px; }
.dcard .num { font-family: var(--font-mono); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-muted); }
.dcard h2 { margin: 8px 0 4px; font-size: 1.5rem; font-weight: 800; text-transform: uppercase; }
/* Grades and format are the two facts a parent is actually scanning these
   cards for — "is this my kid's age, and what do they play?" — so they carry
   more weight than the tagline beneath them. Tracking eases off as the size
   goes up; .1em was set for 10.5px and reads as gappy above that. */
.dcard .grade {
  font-family: var(--font-mono); font-size: 12px; font-weight: 700;
  color: var(--accent); letter-spacing: .06em;
}
.dcard p { margin: 10px 0 0; color: var(--ink-secondary); font-size: .88rem; }

/* ---------------- location / hours ---------------- */
.kv { display: grid; grid-template-columns: auto 1fr; gap: 6px 16px; padding: 14px 16px; font-size: .88rem; }
.kv dt { font-family: var(--font-mono); font-size: 10px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-muted); padding-top: 3px; }
.kv dd { margin: 0; }

/* ---------------- footer ---------------- */
.foot { border-top: 1px solid var(--border); margin-top: var(--sp-xl); padding: 20px 0 10px;
  display: flex; gap: 14px; flex-wrap: wrap; align-items: center;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-muted); }
.foot .sm { margin-left: auto; }
/* Text-legal on --bg (5.22:1), which is where this footer sits. If the footer
   ever gains a panel background it must stop being --statmason — the same
   colour measures 4.47:1 on --surface-2. See plan §2.1. */
.foot .sm b { color: var(--statmason); font-weight: 500; }

/* Coach and Admin sign-in. Deliberately quiet: these are staff doors on a
   parents' page, so they read as footer text until hovered rather than as
   accent-blue links competing with the content above. */
.foot .links { display: flex; gap: 14px; }
.foot .links a { color: var(--ink-secondary); text-decoration: none; }
.foot .links a:hover { color: var(--accent); text-decoration: underline; }

/* The build stamp. Its own full-width line so it never competes for the row
   with the StatMason lockup, and so it is in the same place at every width —
   this is the string someone reads back when diagnosing a stale deploy. */
.foot .ver { flex-basis: 100%; color: var(--ink-muted); opacity: .7; }


/* ---------------- players of the week: grouped by division ----------------
   A rule-and-label break rather than a nested panel, so the box stays as
   small as it was while the grouping is still obvious. */
/* Shared group header — a rule-and-label break used by Players of the Week
   and Next game day. One component rather than one per panel, so the two
   sidebar panels that group by division cannot drift apart. */
.grp-hd {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 16px 6px;
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-muted);
}
.grp-hd::after { content: ''; flex: 1; height: 1px; background: var(--border); }
.grp-hd .dot { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); }
.potw-group:not(:first-child) .grp-hd,
.ngd-group:not(:first-child) .grp-hd { padding-top: 14px; }

/* ---------------- social buttons with marks ---------------- */
.btn .glyph { width: 15px; height: 15px; display: block; flex-shrink: 0; }
.btn-social { display: inline-flex; align-items: center; gap: 8px; }

/* ---------------- next game day ---------------- */
/* This panel's header carries more weight than the others. Scoped to the
   panel rather than changing .p-hd, which would inflate the header of every
   panel on the page and flatten the hierarchy this is trying to create.
   Display face instead of mono, so the extra size reads as a title rather
   than as a louder label; the mono tracking is dropped with it, because wide
   letterspacing at this size pulls the words apart. The .live treatment
   inside keeps its colour and its pulse. */
#next-game-panel .p-hd {
  padding: 14px 16px 12px;
  font-family: var(--font-display);
  font-size: 1.05rem; font-weight: 800;
  letter-spacing: .01em;
  font-variation-settings: 'wdth' var(--display-width);
}
#next-game-panel .p-hd .live i { width: 8px; height: 8px; }

.ngd-date {
  padding: 12px 16px 2px;
  font-family: var(--font-display); font-weight: 800; font-size: 1.15rem;
  text-transform: uppercase; letter-spacing: -.01em; color: var(--ink);
  font-variation-settings: 'wdth' var(--display-width);
}
.ngd-row {
  display: flex; align-items: baseline; gap: 10px;
  padding: 7px 16px;
}
.ngd-row + .ngd-row { border-top: 1px solid color-mix(in oklab, var(--border) 60%, transparent); }
/* The matchup takes the slack and the time stays put, so a long pair of team
   names wraps instead of shoving the start time out of alignment — the times
   need to form a readable column down the panel. */
.ngd-vs { flex: 1; min-width: 0; font-size: 13.5px; font-weight: 600; color: var(--ink); }
.ngd-vs .vs-def { font-weight: 400; color: var(--ink-muted); }
.ngd-time {
  flex: 0 0 auto; font-family: var(--font-mono); font-size: 11.5px;
  font-variant-numeric: tabular-nums; color: var(--accent);
}
.ngd-group:last-child .ngd-row:last-child { padding-bottom: 14px; }

/* ---------------- division play note ----------------
   The rules line beneath the court/rim/ball/format specs. Set quieter than
   body copy and separated by a rule: it qualifies the specs above it rather
   than starting a new thought, so it should read as a footnote to that strip,
   not as another paragraph competing with the overview. */
.play-note {
  margin: 0; padding: 12px 16px 14px;
  border-top: 1px solid var(--border);
  color: var(--ink-secondary); font-size: .84rem; line-height: 1.5;
}
.play-note[hidden] { display: none; }

/* ---------------- coach photos ----------------
   The avatar is the frame; the image inside is cropped and scaled to it.
   overflow:hidden is what makes the zoom a crop rather than an overflow, and
   the img is absolutely sized so object-fit has a box to cover. */
/* Keyed off .has-photo alone rather than a list of avatar classes. The list
   version missed .m — the coach box on a team page — and an unconstrained
   img with no overflow:hidden rendered at its natural size, which read as a
   large blank panel rather than as a broken photo. Any avatar class that
   gains a photo later is covered without editing this. */
.has-photo { padding: 0; overflow: hidden; position: relative; }
.has-photo > img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---------------- game log (transposed) ----------------
   Games are columns and stats are rows, so this table grows sideways rather
   than downwards and has to scroll horizontally. The category column is
   sticky: scrolled away, a grid of bare numbers has nothing to say which row
   is rebounds and which is steals. */
.gl-scroll { overflow-x: auto; }
.gamelog { width: auto; min-width: 100%; border-collapse: separate; border-spacing: 0; }

.gamelog th, .gamelog td { border-bottom: 1px solid var(--border); }
.gamelog tr:last-child th, .gamelog tr:last-child td { border-bottom: 0; }

.gl-corner, .gl-cat {
  position: sticky; left: 0; z-index: 1;
  background: var(--surface);
  border-right: 1px solid var(--border);
  text-align: left; white-space: nowrap;
  padding: 9px 14px;
  font-family: var(--font-mono); font-size: 9.5px; font-weight: 400;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-muted);
}
.gl-corner { border-bottom: 1px solid var(--border); }

.gl-col {
  padding: 8px 14px; text-align: center; white-space: nowrap;
  border-left: 1px solid color-mix(in oklab, var(--border) 55%, transparent);
}
.gl-col b {
  display: block; font-family: var(--font-mono); font-size: 11px; font-weight: 600;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.gl-col span {
  display: block; margin-top: 2px;
  font-family: var(--font-mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-muted);
}
.gl-col.today b { color: var(--accent); }

.gl-v {
  padding: 10px 14px; text-align: center; white-space: nowrap;
  border-left: 1px solid color-mix(in oklab, var(--border) 55%, transparent);
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 13px; font-weight: 600; color: var(--ink);
}
.gl-res { font-size: 11.5px; font-weight: 600; color: var(--ink-secondary); }
.gl-res.w { color: var(--win); }
.gl-res.l { color: var(--loss); }
.gl-res .rl { margin-right: 4px; font-weight: 700; }

/* Not played, or played without a line. Set back rather than dashed out, so
   the eye skips it and the numbers that matter stay the figure. */
.gl-none { color: var(--ink-muted); font-weight: 400; font-size: 11px; }

/* A new personal best. Ringed rather than filled: these sit in a dense grid of
   numbers, and a solid gold cell at this size reads as an error state. */
.gl-best {
  color: var(--gold);
  background: color-mix(in oklab, var(--gold) 12%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--gold) 55%, transparent);
}

/* ---------------- accolades ----------------
   Deliberately NOT a .panel. Every other block on the page is a bordered card
   cut from the field; this one sits on the field itself, so the trophies read
   as objects on a shelf rather than as another row of data. It is the one
   place the page should feel like a display case. */
.accolades { margin-bottom: 14px; }
.acc-hd {
  display: flex; align-items: center; gap: 10px; padding: 4px 4px 2px;
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-secondary);
}
.acc-hd .btn { margin-left: auto; }
.acc-hd .btn i { font-style: normal; }

.trophies { display: flex; flex-wrap: wrap; gap: 18px; padding: 20px 8px 22px; justify-content: center; }
.trophy { display: flex; flex-direction: column; align-items: center; gap: 10px; min-width: 96px; }
/* The count is anchored to the ICON, not the tile, so it stays pinned to the
   artwork's bottom-right however wide the label underneath runs. */
/* The artwork is the league's own badge set, not an emoji. Emoji are rendered
   by the OPERATING SYSTEM, so the same trophy was Apple's on a phone,
   Microsoft's on a laptop and Google's on Android — three different pictures of
   one award, none of them the league's. The PNGs in Icons/milestones/ are what
   shared/share-card.js has always drawn for exactly these awards, so this also
   stops the profile and the card generated from it from disagreeing. */
.trophy .ic {
  position: relative; display: inline-block;
  width: 62px; height: 62px; line-height: 1;
  filter: drop-shadow(0 3px 10px color-mix(in oklab, var(--gold) 30%, transparent));
}
.trophy .ic img { width: 100%; height: 100%; object-fit: contain; display: block; }
.trophy .ct {
  position: absolute; right: -10px; bottom: -2px;
  padding: 2px 7px; border-radius: var(--r-pill);
  background: var(--gold); color: #2A1F04;
  font-family: var(--font-mono); font-size: 11px; font-weight: 700; line-height: 1.3;
}
.trophy .lb {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink); text-align: center;
}

/* The full record. This one IS a card — it is a list of data, and it needs an
   edge to separate it from the shelf above. */
.acc-list {
  background: color-mix(in oklab, var(--surface) 72%, transparent);
  backdrop-filter: blur(16px);
  border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden;
}
.acc-list[hidden] { display: none; }
.acc-row {
  display: flex; align-items: center; gap: 12px; padding: 13px 16px;
  border-bottom: 1px solid var(--border);
}
.acc-row:last-child { border-bottom: 0; }
.acc-row .ic { width: 26px; height: 26px; line-height: 1; flex-shrink: 0; }
.acc-row .ic img { width: 100%; height: 100%; object-fit: contain; display: block; }
.acc-row .nm { flex: 1; min-width: 0; font-weight: 600; font-size: .98rem; }
.acc-row .mt {
  text-align: right; flex-shrink: 0;
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .1em;
  color: var(--ink-secondary);
}
.acc-row .mt span { display: block; color: var(--ink-muted); }

/* Legend for the game log's ring. A colour that carries meaning needs saying
   once, or it is decoration that some readers happen to decode. */
.gl-key {
  display: flex; align-items: center; gap: 8px;
  margin: 0; padding: 10px 16px 14px;
  border-top: 1px solid var(--border);
  font-size: .78rem; color: var(--ink-muted);
}
.gl-key .swatch {
  width: 13px; height: 13px; border-radius: 3px; flex-shrink: 0;
  background: color-mix(in oklab, var(--gold) 12%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--gold) 55%, transparent);
}

/* ---------------- career milestones ----------------
   Three columns: the tier held, the run toward the next, and the tier being
   chased. The badges are the point — they are what a ten-year-old screenshots
   — so they get real size, and the bar between them is the connective tissue
   rather than the feature. */
.milestones { padding: 6px 0 4px; }
.ms-row {
  display: grid; grid-template-columns: 64px 1fr 64px;
  align-items: center; gap: 14px;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
}
.ms-row:last-child { border-bottom: 0; }

.ms-tier { display: flex; justify-content: center; }
.ms-tier img { width: 100%; max-width: 56px; height: auto; display: block; }
/* The next tier, and any not yet earned, sit back — visible enough to be a
   target, quiet enough not to compete with the one actually held. */
.ms-tier img.dim { opacity: .38; filter: grayscale(.5); }
.ms-tier.next { justify-content: center; }

.ms-track { min-width: 0; }
.ms-top { display: flex; align-items: baseline; gap: 10px; margin-bottom: 6px; }
/* Tier colours. Measured against the navy field rather than picked by eye —
   every one clears AA on --surface, the darkest ground a label sits on:
   bronze 6.16:1, silver 10.10:1, gold 10.36:1, diamond 9.98:1, elite 6.50:1.

   Declared here rather than in shared/tokens.css because a milestone tier is a
   StatMason concept, not a tenant's: a franchise varies its accent, not what
   bronze means. They would move into the engine on the port. */
:root {
  --tier-bronze:  #D98E4F;
  --tier-silver:  #C6CCD6;
  --tier-gold:    var(--gold);
  --tier-diamond: #7DD8E8;
  --tier-elite:   #C08BFF;
}

/* The category name carries the tier held, so a glance down the column reads
   as a ladder. Untiered stays --ink-secondary: a player yet to reach bronze
   should not be given a colour that implies they hold something. */
.ms-label {
  font-family: var(--font-mono); font-size: 12px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-secondary);
}
.ms-label.t-bronze  { color: var(--tier-bronze); }
.ms-label.t-silver  { color: var(--tier-silver); }
.ms-label.t-gold    { color: var(--tier-gold); }
.ms-label.t-diamond { color: var(--tier-diamond); }
.ms-label.t-elite   { color: var(--tier-elite); }
.ms-count {
  margin-left: auto; font-family: var(--font-mono); font-size: 11px;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
/* The bar is the thing a player watches move, so it is built like a gauge
   rather than a hairline: a deep inset channel with the fill floating inside
   it, lit from within. The recessed track is what makes the fill read as
   glowing — light needs somewhere dark to come from. */
.ms-bar {
  height: 18px; border-radius: var(--r-pill); padding: 3px;
  background: color-mix(in oklab, var(--bg) 82%, #000);
  border: 1px solid var(--border);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, .55),
              inset 0 -1px 0 color-mix(in oklab, var(--ink) 6%, transparent);
}
.ms-bar > span {
  display: block; height: 100%; border-radius: var(--r-pill);
  /* A floor so a 4%-complete category still shows a lit nub instead of a
     sliver too thin to have a colour. */
  min-width: 12px;
  background: linear-gradient(180deg,
    color-mix(in oklab, var(--accent) 62%, #fff), var(--accent) 55%,
    color-mix(in oklab, var(--accent) 82%, #000));
  box-shadow: 0 0 12px color-mix(in oklab, var(--accent) 60%, transparent),
              0 0 3px color-mix(in oklab, var(--accent) 85%, transparent),
              inset 0 1px 0 color-mix(in oklab, #fff 45%, transparent);
}
/* An empty category gets no lit nub — a glow implies progress that is not
   there, and zero should look like zero. */
.ms-bar > span[style*="width:0%"] { min-width: 0; box-shadow: none; }

/* Nothing left to chase: the bar reads as a finished thing, not a stalled one,
   and the channel is ringed in gold the way the earned badge is. */
.ms-row.maxed .ms-bar {
  border-color: color-mix(in oklab, var(--gold) 55%, transparent);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, .55),
              0 0 10px color-mix(in oklab, var(--gold) 25%, transparent);
}
.ms-row.maxed .ms-bar > span {
  background: linear-gradient(180deg,
    color-mix(in oklab, var(--gold) 62%, #fff), var(--gold) 55%,
    color-mix(in oklab, var(--gold) 82%, #000));
  box-shadow: 0 0 12px color-mix(in oklab, var(--gold) 60%, transparent),
              inset 0 1px 0 color-mix(in oklab, #fff 50%, transparent);
}
.ms-row.maxed .ms-count { color: var(--gold); }

@media (max-width: 460px) {
  .ms-row { grid-template-columns: 48px 1fr 48px; gap: 10px; padding: 12px; }
  .ms-tier img { max-width: 44px; }
}

/* ---------------- achievements ----------------
   A shelf of feats. Locked ones stay in the grid rather than being replaced by
   a generic padlock: seeing the artwork for "25 points in a game" is most of
   the reason to want it, and a wall of identical padlocks tells a player
   nothing about what is actually on offer. */
.ach-grid {
  display: grid; gap: 10px; padding: 16px;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
}
.ach {
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  padding: 12px 8px 10px; text-align: center;
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: color-mix(in oklab, var(--surface-2) 45%, transparent);
}
.ach .ic { display: block; width: 56px; height: 56px; }
.ach .ic img { width: 100%; height: 100%; object-fit: contain; display: block; }
.ach .lb { font-size: .76rem; font-weight: 600; line-height: 1.25; color: var(--ink-secondary); }
.ach .st {
  font-family: var(--font-mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-muted);
}

/* Earned. The gold edge is the same signal the career-high ring and the maxed
   milestone use, so "you own this" reads the same everywhere on the page. */
.ach.got {
  border-color: color-mix(in oklab, var(--gold) 45%, transparent);
  background: color-mix(in oklab, var(--gold) 8%, transparent);
}
.ach.got .lb { color: var(--ink); }
.ach.got .st { color: var(--gold); }
.ach.got .ic img { filter: drop-shadow(0 2px 8px color-mix(in oklab, var(--gold) 35%, transparent)); }

/* Not yet earned: dimmed, not hidden. */
.ach:not(.got) .ic img { opacity: .32; filter: grayscale(.7); }

.p-hd .btn.more {
  margin-left: auto; padding: 4px 10px;
  font-size: 9.5px; letter-spacing: .1em;
}
.p-hd .btn.more[hidden] { display: none; }

/* A game the player sat out. The whole column is set back so the eye skips it
   as a unit — greying the numbers alone leaves the date and opponent looking
   like a game that counted. */
.gl-out { background: color-mix(in oklab, var(--ink) 5%, transparent); }
th.gl-out b, th.gl-out span { opacity: .5; }
td.gl-out.gl-res { opacity: .55; }

/* Share card sits under the jersey, where the number already draws the eye. */
.jersey-col { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.jersey-col .btn { white-space: nowrap; }

/* Header layout. The share button belongs BESIDE the name, not stacked under
   the jersey: on a phone the jersey column wrapped to its own line and pushed
   the button above the player's name, so the first thing on a child's profile
   was a call to action rather than the child.

   The structure is a column, not one long row: the NAME ROW holds the name and
   the button, and the division/team tags run full width underneath. Putting the
   tags on the same row is what starved the name — at 320px the jersey and a
   169px button left it 39px, and an unbreakable "QUINTIN" simply overflowed and
   ran under the button.

   min-width:0 on the name is what lets it shrink at all; without it a flex item
   refuses to go below its content width and pushes the button off instead. */
.namewrap .who { display: flex; flex-direction: column; gap: 10px; flex: 1; min-width: 0; }
.namewrap .name-row { display: flex; align-items: center; gap: 12px; }
.namewrap .name-row h1 { flex: 1; min-width: 0; }
.namewrap .name-row .btn { flex: 0 0 auto; }

/* Compact on a phone. "Share card" at full button size is 169px of a 288px
   line; the word "card" is doing none of the work that the word "share" does,
   and the label is restored from 560px up where there is room for it. */
/* Belt and braces, at every width: a flex item does not clip, it overflows, so
   a name longer than its box slides straight under the button. Allowing a break
   inside the word makes the overlap structurally impossible rather than tuned
   away — "QUINTIN" is 220px at the desktop display size and no amount of
   arithmetic survives a longer name than the ones in the league today. */
.namewrap .name-row h1 { overflow-wrap: break-word; }

/* Compact until the layout goes two-column at 900px. Below that the shell is
   520px, and "Share card" at full button size takes 177px of it — the word
   "card" does none of the work the word "share" does. The name steps down with
   it, because it is sharing the line rather than owning it.

   padding-left keeps --slant-pad even when compact. This tenant sets --slant to
   none so the value is 0, but a tenant that DOES slant its CTAs would otherwise
   get the diagonal cut sliced through the first letter. */
@media (max-width: 899px) {
  .namewrap .name-row .btn {
    min-height: 40px; font-size: .8rem;
    padding: 0 14px 0 calc(14px + var(--slant-pad));
  }
  .namewrap .name-row .btn .long { display: none; }
  .namewrap .name-row h1 { font-size: clamp(1.35rem, 6.5vw, 1.9rem); }
}

/* ── The share button's one-time pulse ──────────────────────────────────────
   Fires twice, on a person's first player profile, and then never again for
   them. Not a permanent floating badge: anything that pulses forever becomes
   furniture within one visit, stops being seen, and spends the whole time
   competing with the child's name — which is the one thing this page is for.

   drop-shadow rather than box-shadow. A box-shadow is drawn on the border box
   and is CLIPPED AWAY by the clip-path that --slant applies to a CTA; a
   drop-shadow follows the element's rendered shape and paints outside it, so
   this works whether or not a tenant slants its buttons.

   The scale is deliberately tiny. The button sits inline with the name, and
   anything bigger shoves the text on every frame. */
@keyframes share-pulse {
  0%, 100% { transform: none; filter: drop-shadow(0 0 0 transparent); }
  50% {
    transform: scale(1.045);
    filter: drop-shadow(0 0 15px color-mix(in oklab, var(--accent) 65%, transparent));
  }
}
.share-pulse { animation: share-pulse 1.25s var(--ease-soft, ease-out) 2; }
/* Someone who asked the system for less motion is told by colour and shape
   alone — the button is already the accent-filled CTA on the page. */
@media (prefers-reduced-motion: reduce) { .share-pulse { animation: none; } }

/* ---------------- career totals ----------------
   A fixed four-column grid rather than .stats' auto-fit, so the bands are
   deliberate: 4 + 2 + 1 all divide into it cleanly and the cells stay aligned
   down the panel. auto-fit would reflow to three columns in this sidebar and
   leave a single orphan cell on the last row. */
.stats.career { grid-template-columns: repeat(4, 1fr); }
.stats.career > .hero { grid-column: 1 / -1; }
.stats.career > .half { grid-column: span 2; }

/* The headline gets the size to match. */
.stats.career > .hero b { font-size: 2.1rem; }
.stats.career > .hero i { margin-top: 3px; }

/* Rows need a rule of their own now that there is more than one. The last
   cell of each band drops its right border; the final band drops the bottom. */
.stats.career > div { border-bottom: 1px solid var(--border); }
.stats.career > .hero,
.stats.career > .half:last-of-type { border-right: 0; }
.stats.career > div:nth-last-child(-n+4) { border-bottom: 0; }
.stats.career > div:last-child { border-right: 0; }

/* The four box-score cells are peers and sit tighter than the bands above. */
.stats.career > div:nth-last-child(-n+4) { padding: 11px 6px; }
.stats.career > div:nth-last-child(-n+4) b { font-size: 1.15rem; }

/* ---------------- box score ----------------
   The banner is the page's headline: two names, two numbers, and which one
   won. Everything below it is detail. */
.bs-banner {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: 12px; padding: 20px 16px;
}
.bs-side { text-align: center; min-width: 0; }
.bs-side a {
  display: block; color: var(--ink);
  font-family: var(--font-display); font-weight: 800; font-size: 1.05rem;
  text-transform: uppercase; line-height: 1.15;
  font-variation-settings: 'wdth' var(--display-width);
}
.bs-side b {
  display: block; margin-top: 6px;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 2.6rem; font-weight: 700; line-height: 1; color: var(--ink-secondary);
}
.bs-side a:hover { color: var(--accent); text-decoration: none; }
.bs-side.win b { color: var(--win); }
.bs-side .tag-win {
  display: inline-block; margin-top: 8px; padding: 2px 8px; border-radius: var(--r-pill);
  background: color-mix(in oklab, var(--win) 16%, transparent);
  color: var(--win); font-family: var(--font-mono); font-size: 9px;
  letter-spacing: .14em; text-transform: uppercase;
}
.bs-mid {
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .18em;
  text-transform: uppercase; color: var(--ink-muted);
}

/* Player of the game. Gold, matching every other "you earned this" on the
   site — the profile's career-high ring, the maxed milestone, the trophies. */
.bs-potg { border-top: 2px solid var(--gold); }
.bs-potg .p-hd .star { color: var(--gold); }
.bs-potg-id { display: flex; align-items: baseline; gap: 10px; }
.bs-potg-id a {
  font-family: var(--font-display); font-weight: 800; font-size: 1.5rem;
  text-transform: uppercase; color: var(--ink); line-height: 1.05;
  font-variation-settings: 'wdth' var(--display-width);
}
.bs-potg-id .jr {
  font-family: var(--font-mono); font-size: 1rem; color: var(--accent);
}
.bs-potg-meta {
  margin-top: 4px; font-family: var(--font-mono); font-size: 10px;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-muted);
}
/* Separate cards rather than a divided strip. The strip reads as one block of
   data; five cards read as five things this player did, which is the point of
   singling them out at all. */
.bs-potg-stats {
  display: grid; gap: 8px; margin-top: 16px;
  grid-template-columns: repeat(auto-fit, minmax(74px, 1fr));
}
.bs-stat {
  padding: 12px 8px; text-align: center;
  border: 1px solid var(--border); border-radius: var(--r-md);
  background: color-mix(in oklab, var(--surface-2) 55%, transparent);
}
.bs-stat b {
  display: block; font-family: var(--font-display); font-weight: 800;
  font-size: 1.6rem; line-height: 1; color: var(--ink);
  font-variation-settings: 'wdth' var(--display-width);
}
.bs-stat i {
  display: block; margin-top: 6px; font-style: normal;
  font-family: var(--font-mono); font-size: 9px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-muted);
}
/* Points leads, the way it does in every other stat group on the site. */
.bs-stat.lead b { color: var(--accent); }

/* These were links already, but inheriting .p-hd's grey made them read as a
   caption. A destination nobody can see is the same as no destination. */
.bs-team-hd a {
  color: var(--accent); font-family: var(--font-display); font-weight: 800;
  font-size: 1rem; text-transform: uppercase; letter-spacing: 0;
  font-variation-settings: 'wdth' var(--display-width);
}
.bs-team-hd a:hover { text-decoration: underline; }
.bs-team-hd .sc {
  margin-left: auto; font-family: var(--font-mono); font-size: 1.15rem;
  font-variant-numeric: tabular-nums; color: var(--ink-secondary);
}
.bs-team-hd .sc.win { color: var(--win); font-weight: 700; }

.bs-jr {
  display: inline-block; min-width: 2.2ch; margin-right: 8px;
  font-family: var(--font-mono); font-size: 11px; color: var(--accent);
}
.bs-potg-tag { margin-left: 6px; color: var(--gold); }

tfoot td {
  border-top: 1px solid var(--border-strong);
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-secondary);
}
tfoot .tm { letter-spacing: .12em; text-transform: uppercase; }

/* Stated, not hidden: a roster line exists for players who did not turn up,
   and leaving them out silently makes the box score look short. */
.bs-absent {
  padding: 9px 16px; border-top: 1px solid var(--border);
  font-family: var(--font-mono); font-size: 9.5px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-muted);
}
