/* The Classic at Tennessee High School — site styles.
 *
 * Hand-written, no framework, no build step. EVERY COLOUR IS A TOKEN ON :root and there
 * is no raw hex below those blocks — that rule is what makes check:contrast possible at
 * all, and what stops a one-off shade creeping in at 11pm in December.
 *
 * THE BRAND, from the logo and the Viking Hall photographs: deep maroon script on an
 * orange basketball with a gold highlight, over a dark, packed gym. So the header, the
 * hero and the footer are always dark and photographic, whatever the colour scheme,
 * and the content in between is warm paper (or warm dark under prefers-color-scheme).
 *
 * Mobile-first: the audience is on phones on one cell tower in a building with bad
 * signal, and the operator drives the admin from a baseline.
 */

:root {
  /* Brand. Maroon is Tennessee High's, as SportsPress recorded it (#6e1a32). */
  --brand: #6e1a32;          /* 9.4:1 on --paper */
  --brand-deep: #4a1022;     /* 13.5:1 on --paper */
  --brand-ink: #ffffff;      /* 9.4:1 on --brand */
  --orange: #e8792a;         /* the basketball — rules and marks only, never text on light */
  --gold: #f2b134;           /* the highlight — text-safe ONLY on the dark surfaces */
  --accent-ink: #9a4408;     /* 6.3:1 on --paper — the text-safe orange */

  /* Paper: the content sections. */
  --paper: #fbf7f3;
  --paper-alt: #f1e9e3;
  --ink: #1a1114;            /* 16.4:1 on --paper */
  --ink-soft: #584a4e;       /* 7.4:1 on --paper */
  --heading: #4a1022;
  --link: #6e1a32;
  --line: #e0d5cd;
  --line-strong: #7d6a66;    /* 4.3:1 on --paper — a visible rule needs 3:1 */
  --accent-line: #6e1a32;    /* card and panel edges */
  --btn-outline: #6e1a32;    /* secondary button border and label; gold under dark */
  --focus: #0b5cd5;          /* 5.4:1 on --paper, and not a brand colour */
  --ok: #1c6b3c;
  --soon: #9a4408;

  /* The dark surfaces: header, hero, footer. Same in both schemes. */
  --dark: #120c0e;
  --dark-rgb: 18, 12, 14;    /* --dark as a triple, for rgba() overlays */
  --dark-2: #1f1417;
  --dark-3: #2c1d22;
  --dark-line: #3b2a30;
  --dark-text: #f6efe9;      /* 17.5:1 on --dark */
  --dark-text-soft: #d6c9c2; /* 12.6:1 on --dark */
  --focus-on-dark: #f2b134;  /* 9.6:1 on --dark */
  --live: #ff6b6b;           /* the "live" dot only — decorative beside the words "Live now" */

  /* A TEAM'S COLOURS (D-28) are NOT defined here. A themed page gets --team, --team-ink
   * and --team-stripe from a nonce'd <style> the server writes, and only the rules under
   * `.has-team` below read them. --team-ink is pure white or pure black, chosen by
   * src/lib/theme.ts so it always clears 4.5:1 on --team; check:theme proves that. A
   * team colour is never text on the paper and never carries meaning alone. */

  /* The ground a school logo sits on, and the ONE colour that is deliberately identical
   * in both schemes. These masters are drawn for white backgrounds: a dark navy crest
   * with no outline disappears entirely on the dark paper, and about a third of the 102
   * are exactly that. So the pad stays white and the logo stays legible, in light mode,
   * in dark mode, and on top of any team colour. Nothing else in this file may do this
   * — it is a token so check:contrast can see it, not an excuse for a raw hex. */
  --logo-pad: #ffffff;

  --measure: 68ch;
  --gutter: 1rem;
  --radius: 6px;
  --shadow-rgb: 0, 0, 0;
  --shadow: 0 10px 30px -12px rgba(var(--shadow-rgb), 0.35);

  /* Body stays on the system face: it is the bulk of the text, it costs nothing to
   * download, and it is already the face the reader's phone renders everything else in. */
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  /* Display type is bold condensed uppercase, which is what the printed bracket and the
   * old site both use. It is SELF-HOSTED rather than a system stack, because a system
   * stack resolved to a different face on every platform: Arial Narrow on a Mac, Impact
   * on Windows and Android (a poster face, wrong at this size), and an unrelated
   * non-condensed system-ui where neither existed. Three machines, three sites.
   *
   * One file, one weight, 15KB, served from our own origin — no Google request, no third
   * party, nothing for the CSP to allow beyond `default-src 'self'`. The fallbacks below
   * still matter: `font-display: swap` paints them first on a slow gym connection. */
  --font-display: "Barlow Condensed", "Roboto Condensed", "Arial Narrow", system-ui, sans-serif;
  /* The script on the hero's "Presented by". Its fallback is deliberately a real
   * alternative rather than the body face: if the 2.5KB subset never arrives, cursive
   * still reads as the same kind of lettering. */
  --font-script: "Dancing Script", "Snell Roundhand", "Segoe Script", cursive;
}

/* ONLY EVER THE WORDS "Presented by". This file is SUBSET to the ten glyphs those two
 * words need — 2.5KB instead of 25KB — so any other string set in it renders as blanks
 * or silently falls back mid-word. The `unicode-range` below is the guard: a character
 * outside it never triggers the download and never uses this face.
 *
 * It replaced an image of the same words. Real type scales with the reader's font size,
 * takes its colour from a token so it works on any hero, and is not an image of text
 * (WCAG 1.4.5) — and it came out a quarter the size of the PNG it replaced. */
@font-face {
  font-family: "Dancing Script";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/fonts/dancing-script-700-subset.woff2") format("woff2");
  unicode-range: U+20, U+50, U+62, U+64-65, U+6e, U+72-74, U+79;
}

/* The only weight the site uses is 700 — every rule below that reaches for --font-display
 * asks for bold. A second weight is a second download for a distinction nobody asked for.
 * Latin subset only: the vietnamese and latin-ext subsets Google serves are bytes that
 * will never render here. */
@font-face {
  font-family: "Barlow Condensed";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/fonts/barlow-condensed-700.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
                 U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
                 U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Dark scheme: the paper turns to warm dark, and maroon — which is unreadable on dark —
 * hands its text roles to the gold. Buttons keep maroon behind white, which works on
 * anything. Nothing here is pink. */
@media (prefers-color-scheme: dark) {
  :root {
    --paper: #1c1216;
    --paper-alt: #26191e;
    --ink: #f6efe9;
    --ink-soft: #cdbfb8;
    --heading: #f6efe9;
    --link: #f2b134;
    --accent-ink: #f2b134;
    --line: #3b2a30;
    --line-strong: #8a777d;
    --accent-line: #f2b134;
    --btn-outline: #f2b134;
    --focus: #f2b134;
    --ok: #7fd6a0;
    --soon: #f2b134;
  }
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.6;
}

img { max-width: 100%; height: auto; display: block; }

.wrap {
  width: 100%;
  max-width: 76rem;
  margin-inline: auto;
  padding-inline: var(--gutter);
}
@media (min-width: 40rem) { .wrap { padding-inline: 1.5rem; } }

/* ---- focus -------------------------------------------------------------- */
/* Visible on everything focusable, never removed (2.4.7). On the dark surfaces the
 * ring turns gold, because blue on near-black does not reach 3:1. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
  border-radius: 2px;
}
.on-dark :where(a, button, [tabindex]):focus-visible { outline-color: var(--focus-on-dark); }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 20;
  background: var(--paper); color: var(--ink);
  padding: 0.75rem 1rem; border: 3px solid var(--focus);
  font-weight: 700;
}
.skip-link:focus { left: 0.5rem; top: 0.5rem; }

/* ---- type --------------------------------------------------------------- */
h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1.05;
  margin: 0 0 0.5em;
  color: var(--heading);
  text-wrap: balance;
}
h1 { font-size: clamp(2.2rem, 7vw, 4.25rem); text-transform: uppercase; }
h2 { font-size: clamp(1.6rem, 4vw, 2.4rem); text-transform: uppercase; }
h3 { font-size: 1.25rem; }

p { margin: 0 0 1rem; }
a { color: var(--link); text-underline-offset: 3px; text-decoration-thickness: 2px; }
a:hover { color: var(--heading); }

.muted { color: var(--ink-soft); }
.kicker {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--accent-ink);
  margin: 0 0 0.4rem;
}
.on-dark .kicker { color: var(--gold); }
.on-dark, .on-dark h1, .on-dark h2, .on-dark h3 { color: var(--dark-text); }
.on-dark .muted { color: var(--dark-text-soft); }
.on-dark a { color: var(--dark-text); }
.on-dark a:hover { color: var(--gold); }

/* ---- the announcement bar (D-68) ---------------------------------------- */
/* A full-bleed MAROON band above the header on every page. It is the one thing on the
 * site that speaks for the tournament rather than reporting a result, so it wears the
 * brand colour rather than the paper: a light notice sitting above a near-black header
 * read as part of the page furniture, which is the opposite of what an announcement is
 * for.
 *
 * WHITE ON MAROON IS THE BUTTON PAIR, already proved at 9.4:1 and already in
 * check-contrast's list. `--brand` is NOT redefined in the dark scheme, so the band is
 * identical in both — one appearance to reason about, like the header and the footer.
 *
 * NO ORANGE RULE UNDER IT. The header below already carries one, and two stacked rules
 * a few pixels apart read as a mistake. The maroon-to-near-black edge is its own
 * boundary.
 *
 * It renders nothing at all when the setting is empty — its state for forty-nine weeks
 * a year — so there is no band and nothing is pushed down out of season.
 *
 * Capped at the reading measure, as it was when it was a card: an announcement is a
 * SENTENCE, and a sentence set across 76rem is a line a reader loses their place in. */
.announcement-bar {
  background: var(--brand);
  color: var(--brand-ink);
  padding-block: 0.8rem;
}
.announcement {
  margin: 0;
  max-width: var(--measure);
  font-weight: 500;
}
/* The link is UNDERLINED, not just a different shade — on a single-colour band there is
 * no other colour to give it, and colour alone may never carry meaning (invariant 11).
 * The offset keeps the rule off the descenders. */
.announcement-bar a {
  color: var(--brand-ink);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
.announcement-bar a:hover { text-decoration-thickness: 2px; }
/* The page's blue focus ring is nearly invisible on maroon; the gold one the dark
 * surfaces use clears 3:1 on it. */
.announcement-bar :where(a, button, [tabindex]):focus-visible {
  outline-color: var(--focus-on-dark);
}

/* ---- header ------------------------------------------------------------- */
.site-header {
  background: var(--dark);
  border-bottom: 4px solid var(--orange);
}
.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-block: 0.6rem;
  flex-wrap: wrap;
}
.brand-link {
  display: flex; align-items: center; gap: 0.75rem;
  text-decoration: none;
  min-height: 44px;
}
.brand-logo { width: 56px; height: 56px; }
.brand-words { display: flex; flex-direction: column; line-height: 1.05; }
.brand-name {
  font-family: var(--font-display); text-transform: uppercase;
  font-weight: 700; font-size: 1.25rem; letter-spacing: 0.04em;
  color: var(--dark-text);
}
.brand-sub { font-size: 0.75rem; color: var(--dark-text-soft); letter-spacing: 0.06em; text-transform: uppercase; }

/* ---- the main nav (D-101) ------------------------------------------------- */
/* NARROW FIRST, and on any touch screen: a MENU button beside the logo, a native
 * <details>, opening a vertical list over the page with each section's pages indented
 * under it. No JavaScript (invariant 10), and the browser announces it expanded or
 * collapsed. Every target is 44px (2.5.5). */
.site-header { position: relative; }
/* Logo and Menu share one row on a phone: the name gives way (its second line may wrap)
 * rather than pushing the button onto a row of its own. */
.header-inner { flex-wrap: nowrap; }
.brand-link { min-width: 0; }
.brand-words { min-width: 0; }
.site-nav { flex: 0 0 auto; }
.nav-toggle {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 0.55rem;
  min-height: 44px; padding: 0 0.9rem;
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em;
  font-weight: 700; font-size: 0.95rem; color: var(--dark-text);
  border: 1px solid var(--dark-line); border-radius: var(--radius);
}
.nav-toggle::-webkit-details-marker { display: none; }
/* Three bars, drawn in CSS and decorative: the word "Menu" is the name. */
.nav-toggle::before {
  content: ""; width: 1.1rem; height: 2px; background: currentColor;
  box-shadow: 0 -6px 0 currentColor, 0 6px 0 currentColor;
}
.nav-toggle:hover, .nav-menu[open] > .nav-toggle { color: var(--gold); border-color: var(--gold); }

.nav { list-style: none; margin: 0; padding: 0; }
.nav-menu[open] > .nav {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 30;
  display: grid; gap: 0;
  padding: 0.25rem var(--gutter) 0.75rem;
  background: var(--dark); border-bottom: 4px solid var(--orange); box-shadow: var(--shadow);
  max-height: calc(100vh - 6rem); overflow-y: auto;
}
.nav a {
  display: flex; align-items: center;
  min-height: 44px; padding: 0 0.7rem;
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em;
  font-weight: 700; font-size: 0.95rem; text-decoration: none;
  color: var(--dark-text);
  border-left: 3px solid transparent;
}
.nav a:hover { color: var(--gold); }
/* Current page: the colour AND a rule, never colour alone. */
.nav a[aria-current="page"] { color: var(--gold); border-left-color: var(--gold); }
.nav-sub { list-style: none; margin: 0 0 0.35rem 0.7rem; padding: 0; border-left: 2px solid var(--dark-line); }
.nav-sub a { font-size: 0.82rem; color: var(--dark-text-soft); padding-left: 1rem; }

/* WIDE, WITH A MOUSE: the bar. The list shows whatever the <details> says, the Menu button
 * goes, and a parent's pages are a dropdown on :hover and on :focus-within, so Tab
 * reaches every child with nothing to load. Only where the browser can style the inside
 * of a <details> (::details-content): an older one keeps the Menu button, which works.
 * `hover: hover`, because a touch screen fires neither hover nor focus before the tap
 * navigates, so a dropdown is the one thing a tablet cannot open. */
@media (min-width: 46rem) and (hover: hover) {
  @supports selector(::details-content) {
    /* The bar is long, so it takes the row under the logo. */
    .header-inner { flex-wrap: wrap; }
    .site-nav { flex: 1 1 auto; }
    .nav-toggle { display: none; }
    .nav-menu::details-content { content-visibility: visible; display: contents; }
    .nav-menu > .nav, .nav-menu[open] > .nav {
      position: static; display: flex; flex-wrap: wrap; gap: 0 0.25rem;
      padding: 0; background: none; border: 0; box-shadow: none; max-height: none; overflow: visible;
    }
    .nav > li > a { border-left: 0; border-bottom: 3px solid transparent; }
    .nav > li > a[aria-current="page"] { border-bottom-color: var(--gold); }
    .nav-parent { position: relative; }
    .nav-sub {
      position: absolute; top: 100%; left: 0; z-index: 20;
      display: none; min-width: max-content; margin: 0; padding: 0.25rem 0;
      background: var(--dark); border: 1px solid var(--dark-line); box-shadow: var(--shadow);
    }
    .nav-parent:hover > .nav-sub,
    .nav-parent:focus-within > .nav-sub { display: block; }
    .nav-sub a { padding: 0 1rem; border-left: 0; }
    .nav-sub a[aria-current="page"] { text-decoration: underline; text-underline-offset: 0.3em; }
  }
}

/* ---- hero --------------------------------------------------------------- */
.hero {
  position: relative;
  isolation: isolate;
  background: var(--dark);
  color: var(--dark-text);
  overflow: hidden;
}
.hero-media {
  position: absolute; inset: 0; z-index: -2;
}
.hero-media img {
  width: 100%; height: 100%; object-fit: cover; object-position: center 35%;
}
/* The photo alone fails contrast for any text; the gradient is what makes the copy
 * legible, so it is not decoration and it is not optional. The top stop is 0.7, not
 * lighter: measured behind the words on a wide screen, the hall's lights at 0.55 took the
 * small gold kicker to 2.7:1 at its worst pixel. A checker cannot measure text over a
 * photo, so this number is the only thing holding it at AA. */
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(180deg, rgba(var(--dark-rgb), 0.7) 0%, rgba(var(--dark-rgb), 0.8) 55%, rgba(var(--dark-rgb), 0.96) 100%);
}
.hero-inner {
  display: grid;
  gap: 1.25rem;
  padding-block: 2.25rem 2.5rem;
}
@media (min-width: 52rem) {
  .hero-inner {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
    align-items: center;
    gap: 2.5rem;
    padding-block: 3.5rem 4rem;
  }
}
@media (min-width: 52rem) { .hero-logo { margin: 0; width: 100%; max-width: 340px; } }

/* The tournament+sponsor lockup. A white card, because the sponsor's mark is designed
 * for white and its black type vanishes on the dark hero. Big on purpose: the
 * presenting sponsor is the second most important thing on the homepage. */
/* THE HERO LOCKUP, COMPOSED IN HTML rather than baked into one image.
 *
 * Three pieces stacked: the tournament logo, the words "Presented by" as real type, and
 * the sponsor's mark as a link. Composing it here rather than shipping a flattened
 * lockup is what makes the sponsor clickable at all, lets the words scale and take a
 * token colour, and means a new sponsor is a settings edit and a logo file rather than
 * a trip back to the designer.
 *
 * No white card behind any of it: the sponsor mark is the white-text variant, drawn for
 * a dark ground, and the script takes --dark-text. */
.hero-lockup-col { display: grid; justify-items: center; gap: 0.75rem; }
.hero-logo-main { width: clamp(200px, 52vw, 320px); height: auto; }
.hero-presented { display: grid; justify-items: center; gap: 0.35rem; margin: 0; }
.presented-script {
  font-family: var(--font-script);
  /* The script's x-height runs small, so it is set larger than a label of this weight
   * would normally be and still reads as secondary next to the logo above it. */
  font-size: clamp(1.4rem, 3.4vw, 1.9rem);
  font-weight: 700;
  line-height: 1;
  color: var(--dark-text);
}
/* The sponsor's mark is the link, and the only thing in this block that is one. The
 * target sits well over 44px (2.5.5) because the mark itself is that big. */
.presented-mark { display: block; line-height: 0; border-radius: 4px; }
.presented-mark img { width: clamp(190px, 46vw, 290px); height: auto; }
@media (min-width: 52rem) {
  .hero-lockup-col { justify-items: start; }
  .hero-presented { justify-items: start; }
}

.hero h1 { color: var(--dark-text); margin-bottom: 0.25em; }
.hero-tagline {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em;
  font-size: clamp(1rem, 2.4vw, 1.3rem); font-weight: 700;
  color: var(--gold); margin-bottom: 0.9rem;
}
.hero-dates {
  font-size: clamp(1.15rem, 2.8vw, 1.5rem); font-weight: 700; margin-bottom: 0.25rem;
}
.hero-venue { display: block; font-weight: 400; font-size: 1rem; color: var(--dark-text-soft); }
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1.25rem; }

/* Right now: the live game, or the next one (D-27). Meaning is in the words — "Live
 * now", "In progress · 3rd quarter" — and the dot and rule are decoration beside them. */
.now {
  margin-top: 1.25rem; padding: 0.9rem 1rem 1rem;
  background: rgba(var(--dark-rgb), 0.55);
  border: 1px solid var(--dark-line); border-left: 4px solid var(--gold);
  border-radius: var(--radius);
  max-width: 34rem;
}
.now-live { border-left-color: var(--live); }
.now p { margin: 0 0 0.3rem; }
.now p:last-child { margin-bottom: 0; }
.now-kicker { display: flex; align-items: center; gap: 0.5rem; }
.live-dot { width: 0.65rem; height: 0.65rem; border-radius: 50%; background: var(--live); flex: 0 0 auto; }
.now-head {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.04em;
  font-weight: 700; font-size: 1.15rem; color: var(--dark-text);
}
.now-state { font-size: 0.95rem; letter-spacing: 0.06em; color: var(--gold); margin-left: 0.5rem; }
.now-matchup { font-weight: 700; font-size: 1.1rem; }
.now-score {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.4rem, 4vw, 2rem); line-height: 1.1; color: var(--dark-text);
  font-variant-numeric: tabular-nums;
}
.now-where { color: var(--dark-text-soft); font-size: 0.95rem; }
.now .countdown-wrap { margin-top: 0.75rem; }
/* NOW beside NEXT (D-100): side by side where there is room, stacked on a phone. Each card
 * is two rows, logo / short name / score, so the pair stays short. */
.now-games {
  display: grid; gap: 0.75rem; margin-top: 1.25rem; max-width: 46rem;
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
}
.now-games .now { margin-top: 0; max-width: none; }
.now-head a { color: inherit; text-decoration-color: var(--gold); }
.now-teams { list-style: none; margin: 0.5rem 0; padding: 0; display: grid; gap: 0.4rem; }
.now-team { display: flex; align-items: center; gap: 0.6rem; min-width: 0; }
.now-team-name { flex: 1 1 auto; min-width: 0; font-weight: 600; color: var(--dark-text); overflow-wrap: anywhere; }
.now-team.is-winner .now-team-name, .now-team.is-winner .now-team-score { font-weight: 800; }
.now-team-score {
  font-family: var(--font-display); font-weight: 700; font-size: 1.5rem; line-height: 1;
  color: var(--dark-text); font-variant-numeric: tabular-nums;
}

/* Countdown. The sentence is server-rendered; the ticking cells are added by JS and are
 * aria-hidden, because the sentence is the accessible version. */
.countdown-wrap { margin-top: 1.25rem; }
.countdown-sentence {
  display: inline-block; margin: 0 0 0.6rem;
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.08em;
  font-weight: 700; color: var(--gold);
}
.countdown {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.5rem;
  max-width: 26rem;
}
.countdown-cell {
  background: rgba(var(--dark-rgb), 0.55);
  border: 1px solid var(--dark-line);
  border-top: 3px solid var(--orange);
  border-radius: var(--radius);
  padding: 0.55rem 0.25rem 0.5rem;
  text-align: center;
}
.countdown-num {
  display: block; font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.6rem, 6vw, 2.6rem); line-height: 1; color: var(--dark-text);
  font-variant-numeric: tabular-nums;
}
.countdown-label {
  display: block; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--dark-text-soft); margin-top: 0.25rem;
}
/* The pause control WCAG 2.2.2 requires for anything that auto-updates for more than
 * five seconds. Kept deliberately quiet: a text button, 44px tall for the touch target
 * (2.5.5), styled like a footnote rather than a call to action. */
.countdown-toggle {
  margin-top: 0.15rem; min-height: 44px; padding: 0 0.25rem;
  background: transparent; color: var(--dark-text-soft);
  border: 0; border-radius: 2px;
  font: inherit; font-size: 0.8rem; text-decoration: underline; text-underline-offset: 3px;
  cursor: pointer;
}
.countdown-toggle:hover { color: var(--dark-text); }

/* Present for assistive tech and no-JS, gone from the screen. Used for the countdown
 * sentence once the ticking cells say the same thing visually. */
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Presented by. Sponsor marks are designed for white, so they sit on a white card. */

/* ---- buttons ------------------------------------------------------------ */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; padding: 0.65rem 1.4rem;
  border: 2px solid var(--btn-outline); border-radius: var(--radius);
  background: var(--paper); color: var(--btn-outline);
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em;
  font-weight: 700; font-size: 1.05rem; text-decoration: none; cursor: pointer;
}
.btn:hover { background: var(--paper-alt); color: var(--heading); }
.btn-primary { background: var(--brand); border-color: var(--brand); color: var(--brand-ink); }
.btn-primary:hover { background: var(--brand-deep); border-color: var(--brand-deep); color: var(--brand-ink); }
/* On the dark hero the secondary button is an outline in the light text colour. */
.on-dark .btn:not(.btn-primary) { background: transparent; color: var(--dark-text); border-color: var(--dark-text-soft); }
.on-dark .btn:not(.btn-primary):hover { background: var(--dark-2); color: var(--gold); border-color: var(--gold); }
.on-dark .btn-primary { border-color: var(--orange); background: var(--brand); }
.on-dark .btn-primary:hover { background: var(--brand-deep); }

/* ---- main --------------------------------------------------------------- */
main > .wrap { padding-block: 2.5rem 3rem; }
section + section { margin-top: 2.75rem; }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; }
.section-head h2 { margin: 0; }

/* ---- panels ------------------------------------------------------------- */
.panel-grid { display: grid; gap: 1rem; grid-template-columns: 1fr; }
@media (min-width: 46rem) { .panel-grid { grid-template-columns: repeat(3, 1fr); } }
.panel {
  background: var(--paper-alt);
  border: 1px solid var(--line);
  border-top: 4px solid var(--accent-line);
  border-radius: var(--radius);
  padding: 1.15rem 1.2rem;
}
.panel h3 { margin-bottom: 0.4rem; }
.panel p:last-child { margin-bottom: 0; }

/* A status never relies on colour alone: it carries a word and a shape too. */
.status { display: flex; align-items: baseline; gap: 0.5rem; font-weight: 700; }
.status-dot { flex: 0 0 auto; width: 0.6rem; height: 0.6rem; border-radius: 50%; background: currentColor; transform: translateY(-0.1rem); }
.status-soon { color: var(--soon); }
.status-open { color: var(--ok); }

/* ---- prose and facts ---------------------------------------------------- */
.prose { max-width: var(--measure); }
.two-col { display: grid; gap: 2rem; }
@media (min-width: 52rem) { .two-col { grid-template-columns: 1.3fr 1fr; align-items: start; } }

/* ---- the game video (D-82) ------------------------------------------------- */
/* 16:9 before and after the player loads, so the page never moves. Dark, because a
 * video player is, and the panel is a link that turns into one. */
.game-video {
  position: relative; width: 100%; max-width: 48rem; aspect-ratio: 16 / 9;
  margin: 1.25rem 0; background: var(--dark); border-radius: var(--radius); overflow: hidden;
}
.game-video iframe { width: 100%; height: 100%; border: 0; display: block; }
.video-facade {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.75rem;
  text-decoration: none; color: var(--dark-text);
}
/* The play mark: an orange disc with a triangle, drawn in CSS. Decorative; the words
 * beside it carry the meaning. */
.video-play {
  width: 4.5rem; height: 4.5rem; border-radius: 50%; background: var(--orange);
  display: flex; align-items: center; justify-content: center;
  transition: transform 0.15s ease;
}
.video-play::after {
  content: ""; margin-left: 0.35rem;
  border-style: solid; border-width: 0.9rem 0 0.9rem 1.5rem;
  border-color: transparent transparent transparent var(--dark);
}
.video-facade:hover .video-play, .video-facade:focus-visible .video-play { transform: scale(1.08); }
@media (prefers-reduced-motion: reduce) { .video-play { transition: none; } }
.video-label { font-family: var(--font-display); font-weight: 700; font-size: 1.25rem; letter-spacing: 0.03em; }
.video-facade:hover .video-label { color: var(--gold); }

/* ---- Getting here (D-81) ---------------------------------------------------- */
/* A card in the right-hand column beside About; under it on a phone. */
.about-row { align-items: start; }
.getting-here {
  background: var(--paper-alt); border: 1px solid var(--line);
  border-top: 4px solid var(--accent-line); border-radius: var(--radius);
  padding: 1rem 1.25rem 1.25rem;
}
.getting-here h2 { margin-top: 0; }
/* Address on the left, Directions on the right, wrapping under it when narrow. */
.getting-here-head {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 0.75rem 1rem; margin-bottom: 1rem;
}
.getting-here-name { font-weight: 700; font-size: 1.15rem; margin: 0 0 0.15rem; color: var(--heading); }
.getting-here address { font-style: normal; }
/* The map fills the card's width at 4:3, sized before it loads so nothing moves. */
.map-embed {
  width: 100%; aspect-ratio: 4 / 3;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden;
}
.map-embed iframe { width: 100%; height: 100%; border: 0; display: block; }

/* ---- the Holiday Hoops front page (D-80) ------------------------------------ */
.hoops-lede { font-size: 1.15rem; color: var(--ink); }
/* Dark, like the hero and the numbers band, because the countdown's cells are drawn for
 * a dark surface — the same block the homepage hero carries. */
.hoops-aside {
  background: var(--dark); border-top: 4px solid var(--orange);
  border-radius: var(--radius); padding: 1.1rem 1.25rem 1.25rem;
}
.hoops-aside .kicker { margin: 0; }
.hoops-aside .countdown-wrap { margin-top: 0.5rem; }
.hoops-actions { display: flex; flex-wrap: wrap; gap: 0.6rem; margin: 1rem 0 0; }
/* Newest first, in columns: two dozen one-line entries read as a list, not a scroll. */
.champion-list {
  list-style: none; margin: 1rem 0 2.5rem; padding: 0;
  columns: 2 18rem; column-gap: 2rem;
}
.champion-list li {
  break-inside: avoid; display: grid; grid-template-columns: 3.25rem 1fr; gap: 0.5rem;
  padding: 0.35rem 0; border-bottom: 1px solid var(--line);
}
.champion-year { font-family: var(--font-display); font-weight: 700; color: var(--heading); }

/* ---- "By the numbers" (D-79) ---------------------------------------------- */
/* A dark panel of boxed figures, on the homepage (the Classic) and the Holiday Hoops page.
 * Gold on the dark surfaces, the hero's own pairing, so the band reads as part of the
 * brand rather than a widget dropped in. */
.stats-band {
  background: var(--dark); color: var(--dark-text);
  border-top: 4px solid var(--orange); border-radius: var(--radius);
  padding: 2rem var(--gutter) 2.25rem; margin-block: 2.5rem;
}
.stats-band h2 { margin: 0; text-align: center; }
/* FLEX, NOT GRID, so a short last row CENTRES instead of hugging the left: nine boxes go
 * five and four on a wide screen, and Holiday Hoops' two sit in the middle. Every box
 * is the same width, which is what makes the rows read as one balanced block. */
.stats-grid {
  list-style: none; margin: 1.5rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 0.75rem;
}
.stats-grid > .stat { flex: 0 1 13rem; }
/* Two across on a phone, the odd one out centred beneath. */
@media (max-width: 30rem) {
  .stats-grid > .stat { flex-basis: calc(50% - 0.375rem); }
}
.stat {
  /* A query container, so the figure below sizes to its BOX, not to the window: at a
   * fixed size "30,000+" and "Hundreds" ran out of their boxes on a laptop. */
  container-type: inline-size;
  display: flex; flex-direction: column; align-items: center; gap: 0.5rem;
  text-align: center; padding: 1.4rem 0.9rem 1.2rem;
  background: var(--dark-2); border: 1px solid var(--dark-line);
  border-top: 3px solid var(--orange); border-radius: var(--radius);
}
.stat-figure {
  font-family: var(--font-display); font-weight: 700; line-height: 1;
  /* The first line is for a browser without container units; it fits the narrowest box.
   * 21% of the box's width keeps eight condensed characters inside it at any size. */
  font-size: 2rem;
  font-size: clamp(1.75rem, 21cqi, 3.25rem); color: var(--gold);
  font-variant-numeric: tabular-nums;
}
/* Decorative and orange, like the box's top rule: a 3:1 graphic on --dark-2 (1.4.11). */
.stat-icon {
  width: 2.25rem; height: 2.25rem; color: var(--orange);
  fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
.stat-label { color: var(--dark-text-soft); font-size: 0.95rem; line-height: 1.35; }

/* The odometer (odometer.js): each digit is a one-digit window over a column of 0-9s
 * that slides up. A window is as wide as its widest digit, so nothing moves sideways. */
.odo { display: inline-flex; align-items: flex-start; }
.odo-wheel { display: inline-block; height: 1em; overflow: hidden; }
.odo-column {
  display: flex; flex-direction: column;
  transition-property: transform;
  /* Fast off the mark, then a long settle onto the digit, like a wheel losing speed. */
  transition-timing-function: cubic-bezier(0.12, 0.65, 0.18, 1);
}
.odo-column > span { display: block; height: 1em; line-height: 1; text-align: center; }
.odo-still { display: inline-block; height: 1em; line-height: 1; }
@media (prefers-reduced-motion: reduce) { .odo-column { transition: none; } }

/* ---- sponsors (D-99) ------------------------------------------------------- */
/* Logos sit on the white logo pad for the same reason school marks do: they are drawn
 * for white, and a dark wordmark vanishes on the dark paper. A tier sets two sizes, the
 * tile's width and the logo's height, so going down the sponsors page the logos get
 * smaller and more fit on a row. Rows are centred, so one presenting sponsor sits in the
 * middle rather than in a column of empty tracks. Every tile is a link, and at its
 * smallest still a 44px target. */
.sponsors-section { margin-top: 2.5rem; }
.sponsors-section .sponsor-list + .sponsor-list { margin-top: 0.75rem; }
.sponsors-all { margin-top: 1rem; text-align: center; }
.sponsor-list {
  --tile: 9rem; --logo-h: 4rem;
  list-style: none; margin: 1rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 0.75rem;
}
.sponsor { flex: 0 1 var(--tile); min-width: 0; text-align: center; }
.sponsor-mark {
  display: flex; align-items: center; justify-content: center;
  min-height: max(44px, calc(var(--logo-h) + 1.5rem)); padding: 0.75rem; box-sizing: border-box;
  background: var(--logo-pad); border: 1px solid var(--line); border-radius: var(--radius);
}
.sponsor-mark img { max-height: var(--logo-h); max-width: 100%; width: auto; object-fit: contain; }
.sponsor-mark .sponsor-name { font-weight: 700; color: var(--brand); }
.sponsor-tier-presenting { --tile: 20rem; --logo-h: 7rem; }
.sponsor-tier-co_sponsor { --tile: 16rem; --logo-h: 5.5rem; }
.sponsor-tier-event      { --tile: 13rem; --logo-h: 4.5rem; }
.sponsor-tier-platinum   { --tile: 10rem; --logo-h: 4rem; }
.sponsor-tier-gold       { --tile: 8.5rem; --logo-h: 3.25rem; }
.sponsor-tier-silver     { --tile: 7rem; --logo-h: 2.75rem; }
.sponsor-tier-bronze, .sponsor-tier-food_court { --tile: 5.75rem; --logo-h: 2.25rem; }
.sponsor-tier { margin-top: 2.5rem; }
.sponsor-tier h2 { text-align: center; }
.sponsor-event { margin: 0.35rem 0 0; font-size: 0.9375rem; }
.contest-sponsors { margin-top: 1rem; }
.contest-sponsors-label { margin: 0; color: var(--ink-soft); text-align: center; }
.contest-sponsors .sponsor-list { margin-top: 0.5rem; }
.food-vendor { margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid var(--line); max-width: var(--measure); }
.food-vendor-head { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }
.food-vendor-head h2 { margin: 0; }
.food-vendor-logo {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  width: 5.5rem; height: 4rem; padding: 0.5rem; box-sizing: border-box;
  background: var(--logo-pad); border: 1px solid var(--line); border-radius: var(--radius);
}
.food-vendor-logo img { max-height: 100%; max-width: 100%; object-fit: contain; }

/* ---- teams page --------------------------------------------------------- */
.crumbs { font-size: 0.875rem; margin-bottom: 1rem; }
.crumbs ol { list-style: none; display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0; padding: 0; }
.crumbs li + li::before { content: "/"; margin-right: 0.5rem; color: var(--line-strong); }

.page-head { padding-bottom: 1.25rem; border-bottom: 1px solid var(--line); margin-bottom: 1.5rem; }
.page-dates { font-weight: 700; margin: 0; }
.field-summary { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; color: var(--heading); margin-bottom: 1.25rem; }

.team-grid { list-style: none; margin: 0 0 2rem; padding: 0; display: grid; gap: 1rem; grid-template-columns: 1fr; }
@media (min-width: 38rem) { .team-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 62rem) { .team-grid { grid-template-columns: repeat(3, 1fr); } }
.team-card {
  border: 1px solid var(--line); border-left: 5px solid var(--accent-line);
  border-radius: var(--radius); padding: 1rem; background: var(--paper-alt);
}
.team-name { font-size: 1.2rem; margin-bottom: 0.5rem; text-transform: none; }
.team-meta { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 0.15rem 0.6rem; font-size: 0.95rem; }
.team-meta dt { color: var(--ink-soft); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; align-self: baseline; }
.team-meta dd { margin: 0; }

.callout {
  background: var(--paper-alt); border: 1px solid var(--line); border-left: 5px solid var(--orange);
  border-radius: var(--radius); padding: 1.25rem; margin-bottom: 2rem; max-width: var(--measure);
}
.callout h1, .callout h2 { margin-top: 0; }
.callout p:last-child { margin-bottom: 0; }
.callout-inline { padding: 0.85rem 1.1rem; }

/* ---- editorial pages and history ------------------------------------------ */
.page-summary { color: var(--ink-soft); margin: 0; max-width: var(--measure); }
.page-body h2 { font-size: clamp(1.3rem, 3vw, 1.7rem); margin-top: 1.75rem; }
.page-body h2:first-child { margin-top: 0; }
.page-body ul, .page-body ol { padding-left: 1.4rem; }
.page-body li { margin-bottom: 0.3rem; }
.page-body li strong { color: var(--heading); }
/* What the admin's page editor can write (D-97). Size and alignment are classes because
 * the CSP drops inline styles; the editor shows its text with these same rules. */
.page-body .text-small { font-size: 0.875em; }
.page-body .text-large { font-size: 1.25em; }
.page-body .text-huge { font-size: 1.6em; line-height: 1.3; }
.page-body .align-center { text-align: center; }
.page-body .align-right { text-align: right; }
.page-body img { margin: 1rem 0; border-radius: var(--radius); }
.page-body .align-center img { margin-inline: auto; }
.page-body .align-right img { margin-left: auto; }
.page-body blockquote, .ql-editor.page-body blockquote {
  margin: 0 0 1rem; padding: 0.25rem 0 0.25rem 1rem;
  border-left: 4px solid var(--accent-line); color: var(--ink-soft);
}
.page-body hr, .ql-editor.page-body hr { border: 0; border-top: 1px solid var(--line-strong); margin: 1.75rem 0; }

.index-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; max-width: var(--measure); }
.index-list li { background: var(--paper-alt); border: 1px solid var(--line); border-left: 5px solid var(--accent-line); border-radius: var(--radius); padding: 1rem 1.15rem; }
.index-list a { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.04em; font-weight: 700; font-size: 1.15rem; text-decoration: none; display: inline-block; min-height: 44px; line-height: 44px; }
.index-list a:hover { text-decoration: underline; }
.index-list p { margin: 0; color: var(--ink-soft); }

/* One card per year. A heading per year is what a fan looking for 1996 wants, and what
 * a screen reader can jump between; one 41-year table is one object to both. */
.year-grid { display: grid; gap: 1rem; grid-template-columns: 1fr; }
@media (min-width: 40rem) { .year-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64rem) { .year-grid { grid-template-columns: repeat(3, 1fr); } .year-grid-wide { grid-template-columns: repeat(2, 1fr); } }
.year-card { background: var(--paper-alt); border: 1px solid var(--line); border-top: 4px solid var(--accent-line); border-radius: var(--radius); padding: 1rem 1.15rem; }
.year-card h2 { font-size: 1.35rem; margin-bottom: 0.6rem; }
.placing-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.3rem; }
.placing { display: grid; grid-template-columns: 6.5rem 1fr; gap: 0.5rem; align-items: baseline; }
.placing-label, .mvp .placing-label {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.08em;
  font-size: 0.75rem; font-weight: 700; color: var(--accent-ink);
}
.placing-1 .placing-label { color: var(--heading); }
.mvp { margin: 0 0 0.6rem; }
.mvp .placing-label { margin-right: 0.4rem; }
.name-list { list-style: none; margin: 0; padding: 0; columns: 2; column-gap: 1.5rem; font-size: 0.95rem; }
.name-list li { break-inside: avoid; margin-bottom: 0.2rem; }
@media (max-width: 30rem) { .name-list { columns: 1; } }

/* The city and state after a school name: present, but never competing with the name
   itself, which is the thing a reader is scanning for. */
.placing-place { color: var(--muted); font-size: 0.9em; }
/* On the champions page the place takes its own line under the school, so every row
   reads the same way: name, then where it is. */
.placing-place-below { display: block; font-size: 0.85em; line-height: 1.3; }

/* The record book (D-87). One card per section, a record per row: its name, the mark
   large, and whoever holds it under that. */
.record-section { margin-bottom: 2rem; }
.record-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.75rem; max-width: var(--measure); }
.record { background: var(--paper-alt); border: 1px solid var(--line); border-left: 5px solid var(--accent-line); border-radius: var(--radius); padding: 0.8rem 1.1rem; }
.record-name {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.04em;
  font-weight: 700; font-size: 1rem; text-decoration: none;
  display: inline-block; min-height: 44px; line-height: 44px;
}
.record-name:hover { text-decoration: underline; }
.record-mark { margin: 0; }
.record-value { font-family: var(--font-display); font-weight: 700; font-size: 1.6rem; font-variant-numeric: tabular-nums; }
.record-unit { color: var(--ink-soft); font-size: 0.9rem; }
.record-shared {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em;
  font-size: 0.7rem; font-weight: 700; color: var(--accent-ink);
  border: 1px solid var(--rule); border-radius: 0.2rem; padding: 0 0.3rem; margin-left: 0.3rem;
}
.record-holders { list-style: none; margin: 0.2rem 0 0; padding: 0; }
.record-who { font-weight: 700; }
.record-vs, .record-year { color: var(--ink-soft); }
.record-table { max-width: var(--measure); }
.record-rank, .record-cell-value { white-space: nowrap; font-variant-numeric: tabular-nums; }
.record-cell-value { font-weight: 700; }

/* The paper record on a team page. A narrower label column than the champions list
   because it holds a year, not the word "Runner-up". */
.placing-list-inline .placing { grid-template-columns: 4rem 1fr; }

.honor-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.45rem; }
.honor-list > li {
  display: grid; grid-template-columns: 4rem 1fr; gap: 0.5rem; align-items: baseline;
}
.honor-names { font-size: 0.95rem; }
/* MVP is marked with a word, never colour alone (invariant 11). */
.honor-mvp {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.08em;
  font-size: 0.68rem; font-weight: 700; color: var(--accent-ink);
  border: 1px solid var(--rule); border-radius: 0.2rem; padding: 0 0.25rem;
}
@media (max-width: 30rem) {
  .placing-list-inline .placing, .honor-list > li { grid-template-columns: 3.2rem 1fr; }
}

/* ---- a team's page wears its colours (D-28) ------------------------------ */
/* Only under .has-team, which the layout sets when the server wrote a theme. Everything
 * else on the page is untouched: the dark header, hero and footer are the tournament's,
 * the paper and its text are the site's. The team colour is a band, a button and the
 * accent rules — surfaces with derived ink, never text on paper. */
.has-team .page-head {
  background: var(--team); color: var(--team-ink);
  border: 1px solid var(--line); border-left: 8px solid var(--team-stripe);
  border-radius: var(--radius); padding: 1.25rem 1.25rem 1.1rem;
}
.has-team .page-head h1, .has-team .page-head .kicker, .has-team .page-head .page-dates,
.has-team .page-head .hero-venue, .has-team .page-head a { color: var(--team-ink); }
.has-team .btn-primary { background: var(--team); border-color: var(--team); color: var(--team-ink); }
.has-team .btn-primary:hover { background: var(--team); box-shadow: inset 0 0 0 2px var(--team-ink); }
.has-team .panel { border-top-color: var(--team); }
.has-team .team-card, .has-team .callout, .has-team .index-list li { border-left-color: var(--team); }
.has-team .year-card { border-top-color: var(--team); }
/* A two-colour swatch beside a team's name. Decorative: the name carries the meaning. */
.swatch {
  display: inline-block; width: 1.1em; height: 1.1em; vertical-align: -0.15em;
  background: linear-gradient(135deg, var(--team) 0 62%, var(--team-stripe) 62%);
  border: 1px solid var(--line-strong); border-radius: 2px;
}

/* ---- footer ------------------------------------------------------------- */
.site-footer {
  background: var(--dark); color: var(--dark-text);
  border-top: 4px solid var(--orange);
  padding-block: 2rem 2.5rem; font-size: 0.95rem;
}
.footer-inner { display: flex; flex-direction: column; gap: 1.25rem; }
.footer-title { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; margin-bottom: 0.25rem; }
/* The logo-ownership notice. Smaller than the copyright and given a measure so it wraps
 * to two or three lines rather than running the full width of the footer — but NOT
 * dimmed further: --ink-soft on the dark footer is already at the AA floor, and taking
 * legal fine print below it is exactly the "nobody reads it because nobody can" pattern
 * that invariant 11 exists to prevent. */
.fine-print { font-size: 0.85rem; max-width: 46ch; margin-top: 0.6rem; }
/* The accessibility statement: the right-hand column on a wide screen, under the copyright
 * on a phone. Left-aligned even on the right, because a ragged left edge on a paragraph
 * this long is hard to read. The heading is the footer title's style at a body size, and
 * its fine print keeps the same AA colour as the logo notice. */
.footer-a11y { max-width: 52ch; }
.footer-a11y h2 { font-size: 0.95rem; margin: 0; }
.footer-a11y .fine-print { max-width: none; color: var(--dark-text-soft); }
@media (min-width: 46rem) {
  .footer-inner { flex-direction: row; justify-content: space-between; gap: 2.5rem; }
}

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

/* ---- print -------------------------------------------------------------- */
/* Schedules and brackets get printed and pinned up in the gym. Ink is black on white
 * whatever the screen tokens say. */
@media print {
  .site-header nav, .site-footer nav, .skip-link, .announcement, .hero-actions, .hero-media,
  .countdown, .countdown-toggle, .live-dot { display: none; }
  .now, .has-team .page-head { background: #fff; color: #000; border-color: #999; }
  .hero, .site-header, .site-footer { background: #fff; color: #000; }
  .hero::before { display: none; }
  body { font-size: 11pt; color: #000; background: #fff; }
  a { text-decoration: none; color: #000; }
  .panel, .team-card, .callout { border: 1px solid #999; break-inside: avoid; }
}

/* ---- seasonal takeover (D-35) -------------------------------------------- */
/* The block directly under the hero that carries the news of the moment: the field is
 * announced, the bracket is drawn. It sits on the alternate paper so it reads as a
 * distinct band between the dark hero and the panels below, without another card
 * border — the hero's edge is already doing that work. */
.takeover {
  background: var(--paper-alt);
  border-bottom: 1px solid var(--line);
  padding-block: 2.25rem 2.5rem;
}
.takeover h2 { margin-bottom: 0.5rem; }
.takeover-lede { color: var(--ink-soft); max-width: var(--measure); margin-bottom: 1.5rem; }
/* auto-fit rather than a fixed column count: the field has been 8, 16 and 18 teams and
 * the girls' event 4 to 8, so the grid has to hold any of them without a media query
 * per size. */
.takeover-teams {
  list-style: none; margin: 0 0 1.5rem; padding: 0;
  display: grid; gap: 0.6rem 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
/* CENTRED, NOT BASELINE-ALIGNED. The row used to align on the text baseline, which was
 * right when it held only text. A logo chip has no baseline of its own — a flex
 * container falls back to the box's bottom edge — so baseline alignment drops the name
 * to sit level with the bottom of the crest. Centring is what makes the mark read as
 * beside the name rather than under it, and it costs the text nothing: both items are
 * one line at the same size. */
.takeover-teams li {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0 0.5rem;
  padding-block: 0.35rem;
  border-bottom: 1px solid var(--line);
}
/* THE ROW IS THE CHIP'S HEIGHT WHETHER OR NOT THERE IS A CHIP. A 28px `logo-chip` is
 * taller than the 1.6 line box the name makes on its own, so without this a program
 * with no logo on file would rule a shorter row than the one beside it — and the list
 * is a grid, so the gap would show as one ragged column. `min-height` on every row is
 * the fix; the chip's own size is not touched, because the four logo sizes are set in
 * one place and nothing else may override them. */
.takeover-teams li { min-height: 28px; }
.takeover-teams .logo-box { margin-right: 0.15rem; }
.takeover-team { font-weight: 700; color: var(--heading); }
.takeover-home { color: var(--ink-soft); font-size: 0.9rem; }

/* ---- team page ----------------------------------------------------------- */
/* The one surface that wears the program's colours (D-28). --team-ink is pure white or
 * pure black, chosen by src/lib/theme.ts so it always clears AA against --team, which
 * is why no rule here sets a text colour conditionally. Without a theme the band falls
 * back to the site's own dark, which every page already carries. */
.team-banner {
  background: var(--dark);
  color: var(--dark-text);
  border-radius: var(--radius);
  margin-bottom: 1.5rem;
  padding: 1.5rem;
  border-left: 8px solid var(--gold);
  display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap;
}
/* FIXED HEIGHT, FLEXIBLE WIDTH. A square box was wrong: these masters run from 0.8:1
 * crests to 3.3:1 wordmarks, and forcing a 2.5:1 wordmark into a square renders it at a
 * third of the available height — Tri-Cities Christian came out 96x29 and read as
 * broken. Matching on height instead makes every logo optically the same size, which is
 * what "the same size" means for mixed shapes.
 *
 * The white pad is deliberate: a lot of these are dark navy with no outline and would
 * vanish into their own team colour. */
/* ONE BOX, FOUR SIZES, AND THE LOGO SCALES TO FILL IT.
 *
 * The space a logo occupies is FIXED and the artwork scales into it, rather than the
 * artwork setting the size. That is what makes eighteen different logos look like one
 * set: a wide wordmark and a tall crest end up optically the same weight instead of one
 * being twice the other. `object-fit: contain` does the scaling and keeps the aspect
 * ratio; the box's own size never moves.
 *
 * THE PADDING IS BUILT IN, because the masters do not have any. They are cropped tight
 * to the artwork with no transparent margin, so without an inset every logo would touch
 * its own edges — and on a card, sit flush against the corner radius. --logo-inset is
 * that margin, and it is a real `padding` on a border-box, so the content area is always
 * exactly (size - 2 × inset) however the box is sized.
 *
 * Percentages resolve here where they used not to: the box has a definite width AND
 * height, so the image's 100%/100% resolve against a definite content box. That was the
 * old bug — a portrait logo like St Joseph (151x200) bound only by width kept its
 * intrinsic height and hung out of the bottom. */
.logo-box {
  --logo-size: 72px;
  --logo-inset: 7px;
  flex: 0 0 auto;
  box-sizing: border-box;
  width: var(--logo-size);
  height: var(--logo-size);
  padding: var(--logo-inset);
  display: block;
  background: var(--logo-pad);
  border-radius: var(--radius);
  /* Hard stop. Everything above should make this unnecessary; it is here because an
   * escaping logo is the failure this box has had three times. */
  overflow: hidden;
}
/* A <picture> is INLINE, so where one sits between the box and the image it lands on the
 * text baseline and leaves descender space underneath — a logo with more room below it
 * than above, which is exactly the uneven padding. Block-ify whatever is in there. */
.logo-box > * { display: block; line-height: 0; }
.logo-box img {
  display: block;
  width: 100%; height: 100%;
  /* Scale into the space, never crop and never distort. */
  object-fit: contain;
}

/* The five spaces a logo is given. Nothing else may set these. */
.logo-team  { --logo-size: 104px; --logo-inset: 10px; }
.logo-card  { --logo-size: 64px;  --logo-inset: 6px; }
.logo-score { --logo-size: 76px;  --logo-inset: 8px; }
.logo-chip  { --logo-size: 28px;  --logo-inset: 3px; }
.logo-now   { --logo-size: 44px;  --logo-inset: 4px; }

/* THE GROUND A LOGO SITS ON (0016) is per program and set by the operator, because the
 * right answer is in the artwork: a flat mark floats, a dark crest needs a light card, a
 * white wordmark needs a dark one. All three keep the same box and the same inset — only
 * what is behind the image changes — so a program can be switched between them and
 * nothing else on any page moves. */
.logo-bg-light { background: var(--logo-pad); }
.logo-bg-dark  { background: var(--dark); }
/* No card at all: the logo sits directly on whatever is behind it. The inset STAYS, so a
 * floating mark still lines up with a carded one beside it in the same row. */
.logo-bg-none  { background: none; }

.team-banner-inner { flex: 1 1 16rem; min-width: 0; }
.has-team .team-banner {
  background: var(--team);
  color: var(--team-ink);
  border-left-color: var(--team-stripe);
}
/* The rest of the page picks the colour up in small doses: the rules beside the
 * headline numbers and the section edges. Deliberately restrained — the band is the
 * statement and everything else is an accent, so a bright team colour does not turn the
 * results table into a highlighter. --team is never TEXT on the paper (D-28): it is
 * only ever a surface or a rule, which is what makes it safe without knowing the hue. */
.has-team .team-facts div { border-left-color: var(--team); }
.has-team .team-games thead th { border-bottom-color: var(--team); }
.has-team .roster-name, .has-team .takeover-team { color: var(--heading); }
.team-banner h1 { color: inherit; margin-bottom: 0.35rem; }
.team-banner .kicker { color: inherit; opacity: 0.85; }
.team-banner-meta {
  display: flex; flex-wrap: wrap; gap: 0.35rem 1rem;
  margin: 0; color: inherit; opacity: 0.9; font-weight: 700;
}

/* The headline numbers. auto-fit so three facts sit in a row on a laptop and stack on a
 * phone without a breakpoint per count — a first-time program has one, a 40-year one
 * has three. */
.team-facts {
  display: grid; gap: 0.75rem 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  margin: 0 0 1.25rem; padding: 0;
}
.team-facts div {
  border-left: 3px solid var(--accent-line);
  padding-left: 0.85rem;
}
.team-facts dt {
  font-family: var(--font-display); text-transform: uppercase;
  letter-spacing: 0.06em; font-size: 0.8rem; color: var(--ink-soft);
}
.team-facts dd {
  margin: 0; font-family: var(--font-display); font-weight: 700;
  font-size: 1.6rem; line-height: 1.1; color: var(--heading);
  font-variant-numeric: tabular-nums;
}
.team-fact-note {
  display: block; font-family: var(--font-body); font-weight: 400;
  font-size: 0.9rem; color: var(--ink-soft);
}

/* Icon-only links to the program's own places. 44x44 (2.5.5) — an icon is a small
 * target and this row is the one place on the page a thumb is likely to miss. */
.team-links {
  display: flex; flex-wrap: wrap; gap: 0.4rem; list-style: none;
  margin: 0 0 1.5rem; padding: 0;
}
.team-links a {
  width: 44px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-soft);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.team-links a:hover, .team-links a:focus-visible {
  color: var(--brand-ink); background: var(--accent-line); border-color: var(--accent-line);
}
.has-team .team-links a:hover, .has-team .team-links a:focus-visible {
  color: var(--team-ink); background: var(--team); border-color: var(--team);
}
/* currentColor throughout the SVG, so one icon works on paper, on dark and on a team
 * colour without a second copy. */
.team-links .icon { width: 20px; height: 20px; display: block; }

.team-bio { margin-bottom: 1.5rem; }

/* ---- the year selector ---------------------------------------------------- */
/* A horizontally scrollable, scroll-snapped strip of real links — the form D-26 settled
 * on for the sponsor wall, for the same reasons: swipeable on a phone, scrollable by
 * keyboard, and nothing moves on its own. It has to hold six years today and forty-odd
 * once the archive is extended backwards, so it scrolls rather than wraps: a wrapping
 * strip of 43 pills becomes four rows of furniture above the content. */
.team-season { margin-bottom: 2.5rem; }
.year-strip {
  overflow-x: auto;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  border-bottom: 2px solid var(--line);
  margin-bottom: 1.25rem;
}
.year-strip ul {
  display: flex; gap: 0.25rem; list-style: none; margin: 0; padding: 0 0 0.5rem;
  width: max-content; min-width: 100%;
}
.year-strip a {
  scroll-snap-align: center;
  display: flex; align-items: center;
  /* 44px high (2.5.5): this is the main control on the page and a thumb has to hit it. */
  min-height: 44px; padding: 0 0.9rem;
  font-family: var(--font-display); font-weight: 700; font-size: 1.05rem;
  letter-spacing: 0.02em; font-variant-numeric: tabular-nums;
  color: var(--ink-soft); text-decoration: none;
  border-radius: var(--radius) var(--radius) 0 0;
  white-space: nowrap;
}
.year-strip a:hover { color: var(--heading); background: var(--paper-alt); }
/* The selected year is marked by weight, colour AND a 3px underline, never by colour
 * alone (invariant 11). The underline is what survives a greyscale print. */
.year-strip a.is-selected {
  color: var(--heading);
  background: var(--paper-alt);
  box-shadow: inset 0 -3px 0 var(--accent-line);
}
.has-team .year-strip a.is-selected { box-shadow: inset 0 -3px 0 var(--team); }

.year-panel:focus { outline: none; }
.year-panel:focus-visible { outline: 3px solid var(--focus); outline-offset: 4px; }
.year-place { margin: 0; color: var(--heading); }
.year-meta {
  display: flex; flex-wrap: wrap; gap: 0.5rem 2rem; margin: 0 0 1rem;
}
.year-meta dt {
  font-family: var(--font-display); text-transform: uppercase;
  letter-spacing: 0.06em; font-size: 0.75rem; color: var(--ink-soft);
}
.year-meta dd { margin: 0; font-weight: 700; color: var(--heading); }
/* A team photo is capped well short of the panel: at full width it would push the
 * year's games off a laptop screen, and 48rem is what the 1280 copy covers at 1.6x. */
.year-photo { display: block; max-width: 48rem; margin: 0 0 1.25rem; }
.year-photo img { width: 100%; border-radius: var(--radius); background: var(--paper-alt); }
.year-bio { margin-bottom: 1.25rem; }
.year-empty { margin-bottom: 1.25rem; }
.roster-head { margin-top: 1.5rem; }
.team-year-alias { font-family: var(--font-body); font-weight: 400; font-size: 0.95rem; color: var(--ink-soft); }

/* The swap. 140ms out, 200ms in — short enough to read as one motion rather than a
 * transition to sit through. `is-loading` only shows if a fetch is genuinely slow. */
@media (prefers-reduced-motion: no-preference) {
  .year-panel { transition: opacity 140ms ease, transform 140ms ease; }
  .year-panel.slide-out-left  { opacity: 0; transform: translateX(-1.5rem); }
  .year-panel.slide-out-right { opacity: 0; transform: translateX(1.5rem); }
  .year-panel.slide-in-left   { animation: year-in-left 200ms ease both; }
  .year-panel.slide-in-right  { animation: year-in-right 200ms ease both; }
  @keyframes year-in-left  { from { opacity: 0; transform: translateX(1.5rem); } }
  @keyframes year-in-right { from { opacity: 0; transform: translateX(-1.5rem); } }
}
.team-season.is-loading .year-panel { opacity: 0.6; }

/* ---- where this program stands -------------------------------------------- */
.team-standing {
  margin: 0 0 1.25rem; padding: 0.7rem 1rem;
  border-left: 4px solid var(--line-strong);
  background: var(--paper-alt); border-radius: 0 var(--radius) var(--radius) 0;
}
.team-standing-now { border-left-color: var(--ok); }
.team-standing-archive { border-left-color: var(--soon); color: var(--ink-soft); }
.team-standing-archive strong { color: var(--heading); }

.team-game-no { white-space: nowrap; font-weight: 700; }
.team-game-round, .team-game-when {
  display: block; font-weight: 400; font-size: 0.85rem; color: var(--ink-soft);
}
.team-game-result { white-space: nowrap; font-variant-numeric: tabular-nums; }
.team-game-score { font-weight: 700; margin-left: 0.4rem; }
/* The WORD carries the outcome; the colour only reinforces it (invariant 11). */
.result-won { color: var(--ok); font-weight: 700; }
.result-lost { color: var(--ink-soft); font-weight: 700; }

.roster {
  list-style: none; margin: 0 0 0.75rem; padding: 0;
  display: grid; gap: 0.4rem 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
}
.roster li {
  display: flex; align-items: baseline; gap: 0.6rem;
  padding-block: 0.3rem; border-bottom: 1px solid var(--line);
}
.roster-no {
  font-family: var(--font-display); font-weight: 700;
  min-width: 2.2rem; color: var(--ink-soft); font-variant-numeric: tabular-nums;
}
.roster-name { font-weight: 700; color: var(--heading); }
.roster-pos { margin-left: auto; font-size: 0.85rem; color: var(--ink-soft); }

/* A linked team name in the field grid and the homepage takeover keeps the heading's
 * weight and colour: the whole card already reads as one object, so an underline only
 * on hover keeps the grid from looking like a list of links. */
.team-name a, .takeover-team {
  color: var(--heading); text-decoration: none;
}
.team-name a:hover, .team-name a:focus-visible,
.takeover-team:hover, .takeover-team:focus-visible { text-decoration: underline; }

/* The field grid's logo. Smaller than the team page's and lazy-loaded: there are 18 of
 * them on one page, over a gym connection, and none is above the fold on a phone. The
 * white pad is the same reasoning as the banner — dark logos need a ground. */
/* Only what the field grid adds to .logo-box: it floats beside the write-up. */
.team-card-logo { float: right; margin: 0 0 0.5rem 0.75rem; }


/* ---- players to watch ----------------------------------------------------- */
/* Above the write-up, as the printed program sets it. A bordered block rather than a
 * card: it belongs to the year panel around it, and another card edge inside a card
 * would read as a separate object (it is not one). */
.watch {
  border-left: 3px solid var(--accent-line);
  padding: 0.1rem 0 0.1rem 0.9rem;
  margin: 0 0 1.25rem;
}
.has-team .watch { border-left-color: var(--team); }
.watch-head {
  font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-soft); margin: 0 0 0.4rem;
}
.watch ul {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 0.3rem 1.75rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
.watch li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 0.5rem; }
.watch-name { font-weight: 700; color: var(--heading); }
.watch-line { font-size: 0.9rem; color: var(--ink-soft); }

/* ---- player page ---------------------------------------------------------- */
.player-head { display: flex; gap: 1.25rem; align-items: center; flex-wrap: wrap; margin-bottom: 1.25rem; }
.player-photo { width: 96px; height: 96px; object-fit: cover; border-radius: 50%; background: var(--paper-alt); }
.player-meta { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; margin: 0; color: var(--ink-soft); }
.player-aliases { font-size: 0.9rem; margin: 0.35rem 0 0; }
.player-season { margin-bottom: 2rem; }
.player-season-team { font-family: var(--font-body); font-size: 1rem; font-weight: 400; text-transform: none; }

/* The game log. Tabular figures so the columns line up, which is most of what makes a
 * box score readable, and a fixed narrow width for the number columns so a dash and a
 * two-digit total occupy the same space. */
.box-log { width: 100%; border-collapse: collapse; font-size: 0.92rem; }
.box-log th, .box-log td {
  text-align: left; padding: 0.5rem 0.6rem 0.5rem 0;
  border-bottom: 1px solid var(--line);
}
.box-log thead th {
  font-family: var(--font-display); text-transform: uppercase;
  letter-spacing: 0.06em; font-size: 0.75rem; color: var(--ink-soft);
  border-bottom: 2px solid var(--line-strong);
}
.box-log .num { text-align: right; font-variant-numeric: tabular-nums; min-width: 3.2rem; }
.box-log tbody th { font-weight: 700; white-space: nowrap; }

/* A linked roster name keeps the list reading as a list rather than a wall of links:
 * the weight and colour are the heading's, and the underline arrives on hover. */
a.roster-name { color: var(--heading); text-decoration: none; }
a.roster-name:hover, a.roster-name:focus-visible { text-decoration: underline; }

a.watch-name { color: var(--heading); text-decoration: none; }
a.watch-name:hover, a.watch-name:focus-visible { text-decoration: underline; }

/* ---- a game page --------------------------------------------------------- */
/* The scoreline is the page. Two teams with the score or the tip-off time between
 * them, and it has to stay readable at 360px where the two names are long. So: a
 * three-column grid on a wide screen, and on a phone the middle column moves to its
 * own row between the teams rather than squeezing to nothing. */
.game-head { margin-bottom: 1.5rem; }
.scoreline {
  display: grid; grid-template-columns: 1fr auto 1fr;
  gap: 0.75rem 1rem; align-items: center; margin: 0.75rem 0 0.5rem;
}
.scoreline-team { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 0.4rem; }

.scoreline-name { margin: 0; font-family: var(--font-display); font-size: 1.15rem; font-weight: 700; line-height: 1.15; }
.scoreline-side {
  margin: 0; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-soft);
}
/* "Won" in words, because colour and weight never carry meaning alone (invariant 11). */
.scoreline-result { color: var(--accent-ink); font-weight: 700; }
.scoreline-middle { text-align: center; }
.scoreline-score {
  margin: 0; font-family: var(--font-display); font-weight: 700;
  font-size: clamp(2rem, 9vw, 3rem); line-height: 1; font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.score-sep { color: var(--ink-soft); margin: 0 0.15em; }
/* The loser's number is dimmed, but only as reinforcement — "Won" already said it. */
.score-loser { color: var(--ink-soft); }
.scoreline-state, .scoreline-when {
  margin: 0.3rem 0 0; font-size: 0.78rem; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--ink-soft);
}
.scoreline-when { text-transform: none; letter-spacing: 0; font-size: 0.95rem; }
.game-meta { margin: 0; color: var(--ink-soft); font-size: 0.95rem; text-align: center; }
.game-stream { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin: 1.25rem 0; }
.game-section { margin: 2rem 0; }
.game-nobox { margin: 1.5rem 0; }

/* The two stat tables reuse the game log's typography — same tabular figures, same
 * narrow number columns, so a dash and a two-digit total occupy the same space. */
.period-table, .box-table { width: 100%; border-collapse: collapse; font-size: 0.92rem; }
.period-table th, .period-table td, .box-table th, .box-table td {
  text-align: left; padding: 0.45rem 0.6rem 0.45rem 0; border-bottom: 1px solid var(--line);
}
.period-table thead th, .box-table thead th {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em;
  font-size: 0.72rem; color: var(--ink-soft); border-bottom: 2px solid var(--line-strong);
}
.period-table .num, .box-table .num {
  text-align: right; font-variant-numeric: tabular-nums; min-width: 2.8rem;
}
.period-table tbody th, .box-table tbody th { font-weight: 700; white-space: nowrap; }
.box-table tfoot th, .box-table tfoot td {
  border-top: 2px solid var(--line-strong); border-bottom: none; font-weight: 700;
}
.box-jersey {
  display: inline-block; min-width: 1.6rem; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; font-weight: 400;
}
.box-starter { color: var(--ink-soft); }
/* The unattributed line (D-42): a stat with no player to credit. Set apart so it does
 * not read as a person, which is exactly what it is not. */
.box-team-line th, .box-team-line td { color: var(--ink-soft); font-style: italic; }
.box-team-line th { font-weight: 400; }

@media (max-width: 34rem) {
  .scoreline { grid-template-columns: 1fr 1fr; }
  /* The score drops to its own full-width row between the two teams. */
  .scoreline-middle { grid-column: 1 / -1; grid-row: 2; }
  .scoreline-team:last-of-type { grid-row: 1; grid-column: 2; }
  .scoreline-logo { --logo-size: 58px; --logo-inset: 6px; }
  .scoreline-name { font-size: 1rem; }
}

/* ---- the bracket ---------------------------------------------------------- */
/* A REAL BRACKET, laid out the way the printed one is: teams enter down the middle,
 * winners advance right toward the championship, losers go left into consolation and the
 * placement games. Every position is computed from the source edges by
 * src/lib/bracket.ts and arrives as grid coordinates in a nonce'd <style> — CSP blocks
 * inline style attributes, and one page is not a reason to allow them site-wide.
 *
 * SEMANTIC DOM, SPATIAL CSS. The markup is a list of columns, each a list of games, in
 * left-to-right order. Grid alone does the placing, so reading order is DOM order and a
 * screen reader walks the chart column by column. The elbows are aria-hidden: they
 * repeat what every game's accessible name already says. */

/* The one place on the site that scrolls sideways. Focusable so a keyboard user can
 * scroll it (2.1.1) and NAMED so a screen reader says what it is; it traps nothing,
 * because everything inside is a link in source order. */
.bracket-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 0.75rem;
  margin-bottom: 2.5rem;
}
/* BREAKS OUT OF THE READING COLUMN, but only where there is a reading column to break
 * out of. `.wrap` caps the page at 76rem; past that the window has room the chart could
 * be using, and not using it was what forced the sheet to scroll on a large screen.
 *
 * ONLY ABOVE 78rem. Below it the wrapper is already the full window, so the breakout
 * buys nothing — and `50vw` counts the scrollbar while the wrapper does not, so on a
 * phone it pushed the whole PAGE sideways and cut the body text off. A visual flourish
 * must never cost the page its own layout. */
@media (min-width: 78rem) {
  .bracket-scroll-sheet {
    margin-left: calc(50% - 50vw + var(--gutter));
    margin-right: calc(50% - 50vw + var(--gutter));
    padding-left: var(--gutter);
    padding-right: var(--gutter);
  }
  /* The em that goes with this breakout is set BELOW, after the base `.bracket-grid`
   * rule — not here. See the comment on that block: at equal specificity the later rule
   * wins, and a media query buys none, so an override written here is dead. */
}
.bracket-scroll:focus { outline: none; }
.bracket-scroll:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

/* Each chart FITS ITS CONTAINER. Columns are 1fr, so four columns share the width
 * rather than forcing a metre of sideways scroll — which is what one seven-column chart
 * did. The scroll container stays, because a narrow phone still cannot fit four. */
.bracket-chart-wrap { margin-bottom: 2.5rem; }
/* h1/h2 are uppercase site-wide (line 194) but h3 is not, and a corner's heading is an
 * h3 (it is not a top-level section of the page the way the sheet is) — so this is
 * spelled out explicitly rather than inherited, and every chart heading reads the same
 * regardless of which level it is. */
.bracket-chart-head { margin: 0 0 0.6rem; text-transform: uppercase; }
.bracket-grid {
  /* THE WHOLE SHEET SCALES. Seven columns of readable boxes is about 1,500px — that is
   * what the printed bracket is, a metre of paper — so instead of squeezing the columns
   * and breaking school names mid-word, the chart takes its own font size from the
   * viewport and every dimension below is in `em`. On a laptop it shrinks to fit; below
   * the floor it stops shrinking and the container scrolls, because past that point it
   * is not readable anyway; and on a monitor with room to spare it GROWS. */
  /* DIVIDED BY THE CONTAINER, NOT BY THE VIEWPORT. The sheet is 109.85em wide — six
   * columns at 14.25em, one centre at 14.75em and six gaps at 1.6em, which is the track
   * list bracket.ts emits — so the em has to be at most the container's width over that.
   *
   * This was `0.88vw`, tuned by hand against "about 110em", and it was ~3% too big:
   * 109.85 x 0.88vw is 96.7vw, but the wrapper is 100vw MINUS its two 1.5rem gutters,
   * and 100vw also COUNTS THE SCROLLBAR while the wrapper does not — the same trap the
   * 78rem breakout below already documents for `50vw`. At a 1200px window the chart
   * asked for 1160px inside 1137px, so the scroller showed a permanent sliver of
   * sideways scroll at every laptop width.
   *
   * So: subtract the gutters and a scrollbar's worth, and divide by 111 rather than
   * 109.85 so a rounded em can never creep back over the edge. Below 40rem `.wrap`'s
   * gutter is 1rem, not 1.5rem, and this is a rem too conservative — which costs
   * nothing, because by then the em is pinned to the 9px floor and the chart is
   * scrolling on purpose. */
  /* THE CEILING, and the one number to change if the sheet should run bigger still.
   *
   * It was 15px, which made the sheet 1,648px — roughly the printed bracket's own size,
   * and the number this chart was originally tuned to. But the em stops growing there
   * while the WINDOW does not: at 1920 the sheet left 208px of the scroller empty on
   * the right, at 2560 it left 848px, and the chart sat in the corner of a monitor with
   * room for a third more of it. The sheet is the one thing on this site that genuinely
   * wants every pixel available.
   *
   * At 20px the sheet is 2,197px wide and 1,610px tall, and a team name — 0.86em — is
   * 17px, which is body-text scale. That fills a 1920 window exactly (it never reaches
   * the cap there: the width formula asks for 16.7px) and a 2560 one to within 300px.
   *
   * WHY A CEILING AT ALL, rather than letting it fill any window: on a 3440 ultrawide
   * the formula asks for 30px, which puts a team name at 26px and makes the sheet
   * 2,447px tall. At that size a bracket box has stopped being a cell in a chart and
   * become a card, the chart no longer reads at a glance, and the page is mostly
   * vertical scrolling. 20px is where a box still looks like a box.
   *
   * To fill a 2560 monitor completely, this is 22px — nothing else needs to change. */
  --bracket-em-max: 20px;
  font-size: clamp(9px, calc((min(100vw - 1rem, 76rem) - 3rem) / 111), var(--bracket-em-max));

  --bracket-track: 1.15em;    /* a game box is 8 tracks tall — TRACKS in bracket.ts */
  --bracket-gap: 1.6em;       /* the gap columns, where the elbows are drawn */
  /* WIDE ENOUGH FOR THE NAME BUDGET. A slot is the column minus its padding, its logo
   * and its score — about 10.5em of text at 0.86em, or roughly 18 characters, which is
   * exactly what `teams.short_name` is capped at. Narrower and the cap stops meaning
   * anything: at 13em "Calvary Christian" (17) wrapped, which is the one thing the
   * short name exists to prevent. The sheet just scales down to fit instead. */
  --bracket-centre: 14.75em;  /* the first round — the longest team names live here */
  --bracket-col: 14.25em;
  --bracket-line: 2px;

  display: grid;
  /* grid-template-columns and -rows are emitted per edition in the nonce'd <style>: how
   * many columns a chart has is a property of that year's bracket, not of this file. */
  list-style: none; margin: 0; padding: 0.4em 0 0;
  width: max-content;
}

/* THE SHEET'S EM ONCE IT HAS BROKEN OUT — and THIS BLOCK MUST STAY BELOW THE RULE
 * ABOVE. That is not a style preference, it is the whole reason the breakout did
 * nothing for months.
 *
 * `.bracket-grid` and `.bracket-grid` are the same specificity (0,1,0), and WRAPPING A
 * RULE IN A MEDIA QUERY ADDS NONE. So when this override sat above the base rule — up
 * beside the `.bracket-scroll-sheet` margins it belongs with, which is exactly where a
 * reader would put it — the base rule won at every width, and the sheet rendered at the
 * base em (capped by the 76rem wrapper, so ~10.5px) even though its container was the
 * full window. On a 1920 monitor that drew a 1,156px chart inside 1,857px of scroller:
 * a third of the sheet's own space left empty on the right, and every team name at 9px.
 * Nothing about the symptom pointed at the cascade, which is why it says so here.
 *
 * Once the sheet is out of the reading column its container is the WINDOW, not the
 * 76rem wrapper, so the em is free to grow against that instead — which is the whole
 * point of breaking out. Same arithmetic as the base rule with the 76rem cap removed.
 * The corners scale with it: a box is a box wherever it appears.
 *
 * This is also the rule where `--bracket-em-max` actually binds. The base rule can
 * never reach it (its divisor is capped at 76rem, so it tops out near 10.5px). */
@media (min-width: 78rem) {
  .bracket-grid { font-size: clamp(9px, calc((100vw - 1rem - 3rem) / 111), var(--bracket-em-max)); }
}
.bracket-game { min-width: 0; }
.bracket-box {
  display: grid; gap: 0.15em;
  /* SPACE-BETWEEN, NOT START, and that is load-bearing geometry rather than taste. The
   * box is sized for its worst case (8 tracks, TRACKS in bracket.ts), so a box whose
   * names do not wrap has a track and a half of slack. Packed to the start, that slack
   * all fell below the away row: the middle line sat ABOVE the box's centre while every
   * branch arm is drawn AT it, and the two slots sat at different distances from it, so
   * a line into the bottom slot landed lower than the matching line into the top one.
   * Space-between puts the slack between the rows instead — which anchors home to the
   * top, away to the bottom, and (because the two are the same height) centres the
   * middle line exactly on the box's centre, where the arm already points. */
  align-content: space-between;
  /* One track short of its slot, so stacked boxes always have a track of air between
   * them and an elbow's arm never grazes the box beneath the one it points at. */
  height: calc(100% - var(--bracket-track)); min-width: 0; overflow: hidden;
  padding: 0.4em 0.55em;
  background: var(--paper-alt);
  border: 1px solid var(--line);
  /* The one strong edge, so a box reads as a unit at a glance and in greyscale. */
  border-left: 4px solid var(--accent-line);
  border-radius: var(--radius);
  color: var(--ink); text-decoration: none;
}
.bracket-box:hover { border-color: var(--line-strong); }
.bracket-box:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
/* THE MIDDLE LINE — where the printed sheet writes "(16) 4:00 P.M. DEC. 29" between the
 * two slots. Number, trophy, time and state on one ruled line; it is what separates the
 * top team from the bottom one. */
/* FIXED AT TWO LINES — "Game N | Award" over "Date, time … Status" — so every box in a
 * column is the same height whether or not that particular game carries an award. */
.bracket-mid {
  display: grid; gap: 0.1em;
  padding: 0.15em 0; margin: 0.05em 0;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  font-size: 0.7em; color: var(--ink-soft); line-height: 1.3;
}
.bracket-mid-row {
  display: flex; align-items: baseline; gap: 0.15em 0.5em; min-width: 0;
}
.bracket-no {
  font-family: var(--font-display); text-transform: uppercase;
  letter-spacing: 0.06em; font-weight: 700; color: var(--ink-soft);
  flex: 0 0 auto;
}
/* What the game decides. Marked by weight and a rule, not by colour alone — and it is
 * words, so it survives greyscale and a screen reader reads it as part of the link's
 * name rather than as decoration. */
.bracket-award {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--heading); font-weight: 700;
  padding-left: 0.5em; border-left: 2px solid var(--accent-line);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Date and time, left; the state is pushed to the far right of the same line, like the
 * printed sheet's "(16) 4:00 P.M. DEC. 29" against its result. */
.bracket-when {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Invariant 5: the state is the one thing on the line that is never quiet. */
.bracket-state { font-weight: 700; color: var(--ink); flex: 0 0 auto; margin-left: auto; }
.bracket-tba { font-style: italic; }
.bracket-side {
  display: flex; align-items: center; gap: 0.35em; font-size: 0.86em;
  min-width: 0; padding: 0.05em 0;
}
.bracket-side .logo-box { --logo-size: 1.8em; --logo-inset: 0.2em; border-radius: 3px; }
/* A school with no logo still lines its name up with the ones that have one. */
.bracket-chip-gap { flex: 0 0 auto; width: 1.8em; }
.bracket-team {
  flex: 1 1 auto; min-width: 0;
  /* ONE LINE, STRUCTURALLY. `teams.short_name` is capped so a name fits the column, but
   * the cap is a character count and the column is ems, so a name of unusually wide
   * letters can still overrun — and a wrapped name is not a cosmetic problem here: it
   * grows the box past the slot it was given and pushes the tip-off time out of the
   * bottom. Ellipsis is the honest failure of the two, and the full name is one click
   * away on the team page. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.15;
}
.bracket-score { font-variant-numeric: tabular-nums; font-weight: 700; }

/* WIN IS NEVER COLOUR ALONE (invariant 11). The winner's row is bold AND carries the
 * word "Won" — the weight is the quick read, the word is the one that survives
 * greyscale, a colour-blind reader and a screen reader landing mid-box. */
.bracket-side.is-winner .bracket-team,
.bracket-side.is-winner .bracket-score { font-weight: 700; color: var(--heading); }
.bracket-won {
  font-family: var(--font-display); text-transform: uppercase;
  letter-spacing: 0.06em; font-size: 0.72em; color: var(--ok);
}
/* A slot still reading "Winner of Game 24" is not a team yet, and says so in words
 * rather than by being greyer. The italic is reinforcement, not the message. */
.bracket-side.is-pending .bracket-team { color: var(--ink-soft); font-style: italic; }

/* ---- the branches --------------------------------------------------------- */
/* ONE LINE PER FEEDER, never a merged pair converging on a box's centre. A game with two
 * feeders gets two `.bracket-link` elements — the printed sheet routes each feeder to
 * where its team actually lands: game 5 into the TOP of every game it feeds, game 7 into
 * the BOTTOM of the same games, never both meeting at one centre point. Two independent
 * lines is also what keeps them apart: they bend at different heights instead of both
 * reaching for the same one.
 *
 * Each line is an arm (at the FEEDER's own row), a spine, and a stub (into the target's
 * home or away slot — `bracket-link-slot-top`/`-bottom`, matching which one it actually
 * is). The bend sits at a QUARTER of the gap, close to the feeder, not the half a
 * midpoint-converging line would use: every game in a column is a feeder for something,
 * so the gap is full of these end to end, and bending early keeps each line's spine
 * inside the quarter nearest its own feeder — a zone no OTHER line's spine ever reaches
 * into. Only the long stub crosses into the far three-quarters, and only to land on the
 * box it's actually feeding, which is the one place a touch is correct rather than a
 * crossing. */
.bracket-link { position: relative; pointer-events: none; --slot-off: 0; }
.bracket-link::before,
.bracket-link::after { content: ''; position: absolute; }
.bracket-link::before {
  border: var(--bracket-line) solid var(--line-strong);
}
.bracket-link::after {
  border-top: var(--bracket-line) solid var(--line-strong);
}
/* HOW FAR A SLOT SITS FROM THE BOX'S CENTRE, in tracks. Derived, not tuned: with the
 * rows spaced apart (see .bracket-box) a team row is 1.9em tall at 0.86em — 1.63em —
 * so its centre is 0.4em of padding plus half of that from the box's edge, and the
 * box's own centre is 3.5 tracks in. 3.5 − (0.4 + 0.82) / 1.15 = 2.44. */
.bracket-link-slot-top { --slot-off: 2.44; }
.bracket-link-slot-bottom { --slot-off: -2.44; }

.bracket-link-straight::before {
  left: 0; right: 0; bottom: auto; border: 0;
  border-top: var(--bracket-line) solid var(--line-strong);
  /* 3.5 TRACKS, NOT 50%. The grid area is the box's full 8 tracks but the box itself is
   * 7 of them (one track of air below it), so its centre is 3.5 tracks down — half the
   * area is half a track too low. The slot offset then moves that up toward the home
   * row or down toward the away row. */
  top: calc(var(--bracket-track) * (3.5 - var(--slot-off)));
}
.bracket-link-straight::after { display: none; }

/* THE CHART IS MIRRORED, so `right` (winners' side: feeder left of the gap, target
 * right) and `left` (the sixth-place wing: feeder right of the gap, target left) are
 * mirror images of each other, not just relabelled. */
.bracket-link-right.bracket-link-down::before {
  /* The arm stays at the FEEDER's own centre (3.5 tracks — fixed, whatever the slot);
   * only the spine's far end and the stub below move with the slot. */
  left: 0; right: 75%; border-left: 0; border-bottom: 0;
  top: calc(var(--bracket-track) * 3.5);
  bottom: calc(var(--bracket-track) * (4.5 + var(--slot-off)));
}
.bracket-link-right.bracket-link-down::after {
  left: 25%; right: 0; top: auto;
  bottom: calc(var(--bracket-track) * (4.5 + var(--slot-off)));
}
.bracket-link-right.bracket-link-up::before {
  left: 0; right: 75%; border-left: 0; border-top: 0;
  /* 4.5, not 3.5: on an `up` line the FEEDER is the box at the BOTTOM of the grid area,
   * and a box is 7 tracks of the area's 8, so its centre is 4.5 tracks up from the
   * bottom edge. At 3.5 the arm left the feeder a full track below its own centre —
   * the visible symptom was every upward line starting off-box. */
  bottom: calc(var(--bracket-track) * 4.5);
  top: calc(var(--bracket-track) * (3.5 - var(--slot-off)));
}
.bracket-link-right.bracket-link-up::after {
  left: 25%; right: 0;
  top: calc(var(--bracket-track) * (3.5 - var(--slot-off)));
}
.bracket-link-left.bracket-link-down::before {
  right: 0; left: 75%; border-right: 0; border-bottom: 0;
  top: calc(var(--bracket-track) * 3.5);
  bottom: calc(var(--bracket-track) * (4.5 + var(--slot-off)));
}
.bracket-link-left.bracket-link-down::after {
  right: 25%; left: 0; top: auto;
  bottom: calc(var(--bracket-track) * (4.5 + var(--slot-off)));
}
.bracket-link-left.bracket-link-up::before {
  right: 0; left: 75%; border-right: 0; border-top: 0;
  /* 4.5, not 3.5: on an `up` line the FEEDER is the box at the BOTTOM of the grid area,
   * and a box is 7 tracks of the area's 8, so its centre is 4.5 tracks up from the
   * bottom edge. At 3.5 the arm left the feeder a full track below its own centre —
   * the visible symptom was every upward line starting off-box. */
  bottom: calc(var(--bracket-track) * 4.5);
  top: calc(var(--bracket-track) * (3.5 - var(--slot-off)));
}
.bracket-link-left.bracket-link-up::after {
  right: 25%; left: 0;
  top: calc(var(--bracket-track) * (3.5 - var(--slot-off)));
}

/* THE ARROWHEAD. A real child element, not a third pseudo-element (::before and ::after
 * are both spoken for) — a small triangle at the exact spot the stub above already
 * reaches, pointing the way the line flows. This is the visual difference between a
 * play-in's line (dir `right`: its winner walks INTO the main row of games) and the
 * sixth-place bracket's own first-round line sharing that same gap (dir `left`: a loser
 * moving OUT of the first round) — opposite arrows in the same space, rather than
 * shrinking one box to make room to tell them apart.
 *
 * Each rule's vertical position is the SAME formula as that shape's ::after above, then
 * `+ half the line's thickness − half the arrowhead's height`: the line is drawn as a
 * border hanging BELOW the coordinate, so its own centre is half a line-width past it,
 * and that half-width is what the triangle has to be centred on. Copy the two together
 * if one changes.
 *
 * Sized in em, like every other dimension on the sheet — at a fixed 8px the head was
 * half a track tall on a wide screen and a track and a half on a narrow one. */
.bracket-link-arrow {
  --arrow-half: 0.32em;       /* half its height; the tip is this far from either edge */
  --arrow-nudge: calc(var(--bracket-line) / 2 - var(--arrow-half));
  position: absolute;
  width: 0; height: 0;
  border-top: var(--arrow-half) solid transparent;
  border-bottom: var(--arrow-half) solid transparent;
}
.bracket-link-right .bracket-link-arrow { right: 0; border-left: 0.5em solid var(--line-strong); }
.bracket-link-left .bracket-link-arrow { left: 0; border-right: 0.5em solid var(--line-strong); }

.bracket-link-straight .bracket-link-arrow {
  top: calc(var(--bracket-track) * (3.5 - var(--slot-off)) + var(--arrow-nudge));
}
.bracket-link-down .bracket-link-arrow {
  top: auto;
  bottom: calc(var(--bracket-track) * (4.5 + var(--slot-off)) + var(--arrow-nudge));
}
.bracket-link-up .bracket-link-arrow {
  top: calc(var(--bracket-track) * (3.5 - var(--slot-off)) + var(--arrow-nudge));
}

/* ---- the corner brackets -------------------------------------------------- */
/* The small brackets the printed sheet tucks into its margins — one per round whose
 * losers keep playing — drawn beneath the main sheet, side by side where they fit. Same
 * scale as the sheet so a box is a box wherever it appears. */
.bracket-corners {
  display: flex; flex-wrap: wrap; gap: 1.5rem 3rem;
  align-items: flex-start;
  margin-bottom: 2.5rem;
}
/* THE CORNERS BREAK OUT WITH THE SHEET. Above 78rem the sheet leaves the 76rem reading
 * column for the window, and its em — which the corners share — grows with the window. Left
 * in the column, the corners kept growing in a box that did not: at 1,600px the three of
 * them were wider than 76rem, and 3rd/4th place dropped onto a row of its own. Same margins
 * as `.bracket-scroll-sheet`, so they also line up under its left edge. */
@media (min-width: 78rem) {
  .bracket-corners {
    margin-left: calc(50% - 50vw + var(--gutter));
    margin-right: calc(50% - 50vw + var(--gutter));
    padding-left: var(--gutter);
    padding-right: var(--gutter);
  }
}
/* margin-top: 0 because the site's `section + section` spacing (2.75rem) applied to every
 * corner after the first, so the consolation bracket sat 44px above its neighbours and
 * the row read as two. Side by side, they share a top edge. */
.bracket-chart-corner { margin-top: 0; margin-bottom: 0; flex: 0 1 auto; max-width: 100%; }
.bracket-chart-corner .bracket-chart-head { font-size: 1.15rem; }
.bracket-chart-corner .bracket-scroll { margin-bottom: 0; }

/* ---- the horizontal scroller every wide table sits in ------------------- */
/* `.scroll-x` was in four templates — the box scores, the period table, the team game
 * log and the player log — with NO RULE BEHIND IT, so those tables simply overran a
 * narrow screen. That is a reflow failure (1.4.10) on two of the four screens the
 * project calls its real accessibility work, and the fix is the rule the class name
 * always promised. Mirrors .bracket-scroll, the other sideways-scrolling container. */
.scroll-x {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
/* Only the schedule's wrappers are focusable so far. A scrollable region needs to be
 * reachable by keyboard (2.1.1); the other four want the same treatment and a
 * tabindex in their own change, not quietly here. */
.scroll-x[tabindex] { padding-bottom: 0.4rem; }

/* ---- the session schedule, under the bracket ---------------------------- */
/* The other axis of invariant 7, and the reflowing half of this page: the chart has to
 * scroll sideways on a phone, this never does. */
.schedule { margin-top: 1rem; }
.schedule-head { margin-bottom: 0.25rem; }
.schedule-session { margin-bottom: 2rem; }
.schedule-session-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.75rem;
  margin-bottom: 0.5rem; padding-bottom: 0.3rem;
  border-bottom: 2px solid var(--line);
}
.schedule-session-name { font-family: var(--font-body); font-weight: 400; color: var(--ink-soft); }
/* The day is the thing people actually navigate by, so it is not dimmed into furniture. */
.schedule-day {
  font-family: var(--font-body); font-size: 0.85rem; font-weight: 400;
  color: var(--ink-soft); margin-left: auto;
}

.schedule-table { width: 100%; border-collapse: collapse; font-size: 0.92rem; }
.schedule-table th, .schedule-table td {
  text-align: left; padding: 0.5rem 0.75rem 0.5rem 0;
  border-bottom: 1px solid var(--line); vertical-align: baseline;
}
.schedule-table thead th {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em;
  font-size: 0.72rem; color: var(--ink-soft); border-bottom: 2px solid var(--line-strong);
}
.schedule-no { white-space: nowrap; font-weight: 700; }
/* What the game decides, under its number. Not colour-coded and not a pill: it is a
 * label, and invariant 11 means it has to read as one in greyscale too. */
.schedule-award {
  display: block; white-space: normal;
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.05em;
  font-size: 0.7rem; font-weight: 700; color: var(--accent-line);
}
.schedule-time { white-space: nowrap; font-variant-numeric: tabular-nums; }
.schedule-court { display: block; font-size: 0.78rem; color: var(--ink-soft); }
.schedule-matchup { min-width: 12rem; }
/* "vs" is furniture between two names, not a word being read for meaning. */
.schedule-vs { color: var(--ink-soft); padding: 0 0.15rem; }
.schedule-result { white-space: nowrap; }
.schedule-score { font-variant-numeric: tabular-nums; font-weight: 700; }
/* INVARIANT 11: the winner is bold AND the state is spelled out beside it, so weight is
 * never the only carrier. The bracket marks its winner with the word "Won"; here the
 * two numbers sit together and the heavier one is the winner, which is why the state
 * below it is never omitted. */
.schedule-score .is-winner { color: var(--ink); }
.schedule-score span:not(.is-winner) { color: var(--ink-soft); font-weight: 400; }
.schedule-state { display: block; font-size: 0.78rem; color: var(--ink-soft); }
/* A contest is on the floor between games and reads as its own kind of row. */
.schedule-contest .schedule-matchup { font-style: italic; color: var(--ink-soft); }

/* The bracket on paper. The tournament currently hands out a PDF; this is the start of
 * retiring it. The scroll container is not a scroll container on paper, so the whole
 * grid lays out rather than being clipped to the screen's width. */
@media print {
  .bracket-scroll { overflow: visible; padding: 0; }
  .bracket-rounds { width: auto; gap: 0.4rem; }
  .bracket-round { min-width: 0; max-width: none; }
  .bracket-box { break-inside: avoid; }
  .year-strip { display: none; }
  /* The schedule is the half of this page that prints well — it is the sessions sheet
   * the tournament hands out. Nothing is clipped and no row is split across a page. */
  .scroll-x { overflow: visible; }
  .schedule-session { break-inside: avoid; }
  .schedule-table tr { break-inside: avoid; }
}

/* ---- the press page ------------------------------------------------------- */
.press-days ul { list-style: none; padding: 0; margin: 0 0 1.5rem; display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; }
.press-days a { display: inline-flex; align-items: center; min-height: 44px; }
.press-days a[aria-current="page"] { font-weight: 700; color: var(--heading); text-decoration: none; border-bottom: 3px solid var(--accent-line); }
.press-results, .press-schedule { list-style: none; padding: 0; margin: 0 0 1.5rem; max-width: var(--measure); }
.press-results li { padding: 0.5rem 0; border-bottom: 1px solid var(--line); }
.press-line, .press-scorers { margin: 0; }
.press-scorers { color: var(--ink-soft); font-size: 0.95rem; }
.press-schedule li { padding: 0.3rem 0; }
.press-time { display: inline-block; min-width: 6.5rem; font-variant-numeric: tabular-nums; font-weight: 700; }
.press-text {
  display: block; width: 100%; max-width: var(--measure); box-sizing: border-box;
  padding: 0.75rem; font: inherit; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85rem; color: var(--ink); background: var(--paper-alt);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
}

/* ---- where a team plays next (lib/path.ts) ---------------------------------
 * A card under the team's banner: the next game, then where a win and a loss each lead.
 * Wins and losses are the words "Win" and "Lose", never colour. */
.team-next {
  margin: 1rem 0 1.5rem; padding: 1rem 1.25rem; max-width: var(--measure);
  background: var(--paper-alt); border: 1px solid var(--line);
  border-left: 5px solid var(--accent-line); border-radius: var(--radius);
}
.team-next h2 { margin: 0 0 0.35rem; font-size: 1.2rem; }
.team-next-game { margin: 0; font-size: 1.15rem; }
.team-next-when { margin: 0.15rem 0 0.75rem; color: var(--ink-soft); }
.team-next-paths { margin: 0; display: grid; gap: 0.35rem; }
.team-next-paths div { display: grid; grid-template-columns: 3.5rem 1fr; gap: 0.5rem; align-items: baseline; }
.team-next-paths dt {
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em;
  font-weight: 700; color: var(--heading);
}
.team-next-paths dd { margin: 0; }
.team-next-calendar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1.25rem; margin: 1rem 0 0; }
.team-next-calendar a:not(.btn) { display: inline-flex; align-items: center; min-height: 44px; }

/* ---- the bracket challenge (D-102) ----------------------------------------
 * The one public form. Each game is a fieldset of two radio buttons, and each choice is a
 * whole-row label at least 48px tall: the target is the team's name, not a 16px circle,
 * on a phone in a stand. Right and wrong are words in the tables, never colours. */
.challenge-intro p { max-width: var(--measure); }
.challenge-count, .challenge-status { font-weight: 700; color: var(--heading); }
.challenge-names { list-style: none; padding: 0; margin: 0 0 1.5rem; display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; }
.challenge-names a { display: inline-flex; align-items: center; min-height: 44px; }
.challenge-table caption { text-align: left; font-weight: 700; color: var(--heading); padding-bottom: 0.5rem; }
.challenge-table .num { text-align: right; font-variant-numeric: tabular-nums; padding-right: 1rem; }
/* A PERFECT BRACKET'S ROW stands out: its own ground and a bar in the accent colour down
 * its edge. The word "Perfect" stays in the tag beside the name, so the row's colour is
 * never the only thing saying it. */
.challenge-table tr.is-perfect th, .challenge-table tr.is-perfect td { background: var(--paper-alt); }
.challenge-table tr.is-perfect td:first-child { box-shadow: inset 4px 0 0 var(--accent-line); padding-left: 0.6rem; }
.challenge-perfect-count {
  display: inline-block; margin-left: 0.35rem; padding: 0.1rem 0.6rem;
  border: 2px solid var(--accent-line); border-radius: 999px; color: var(--heading);
}
/* "Perfect" is a word in a tag, beside the name — never only a colour. */
.challenge-perfect {
  display: inline-block; margin-left: 0.4rem; padding: 0.05rem 0.45rem;
  font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.06em;
  font-size: 0.72rem; font-weight: 700; vertical-align: middle;
  color: var(--brand-ink); background: var(--brand); border-radius: 999px;
}
/* Find a name above the table; how many to a page below it, beside Previous and Next. */
.challenge-find { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.75rem 1rem; margin: 1rem 0; }
.challenge-find label { display: block; font-weight: 700; margin-bottom: 0.25rem; }
.challenge-per { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; margin-left: auto; }
.challenge-per label { font-weight: 700; }
.challenge-find input, .challenge-per select {
  min-height: 48px; box-sizing: border-box; padding: 0.4rem 0.6rem; font: inherit;
  color: var(--ink); background: var(--paper-alt);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
}
.challenge-find input { width: 16rem; max-width: 100%; }
.challenge-found { font-weight: 700; color: var(--heading); }
.challenge-place { font-size: 1.15rem; color: var(--heading); }
.challenge-pager { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem 1.5rem; margin-top: 1rem; }
.challenge-pager-links { display: flex; gap: 0.75rem; flex-wrap: wrap; }
.challenge-errors {
  border: 2px solid var(--brand); border-radius: var(--radius); padding: 1rem 1.25rem;
  margin-bottom: 1.5rem; max-width: var(--measure); background: var(--paper-alt);
}
.challenge-errors h2 { margin-top: 0; font-size: 1.2rem; }
.challenge-errors ul { margin-bottom: 0; }
.challenge-form { max-width: var(--measure); }
.challenge-game {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 0.75rem 1rem 0.5rem; margin: 0 0 1rem;
}
.challenge-game legend { font-family: var(--font-display); text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700; color: var(--heading); padding: 0 0.35rem; }
.challenge-feed { margin: 0 0 0.35rem; font-size: 0.85rem; color: var(--ink-soft); }
.challenge-choice {
  display: flex; align-items: center; gap: 0.75rem; min-height: 48px;
  padding: 0.25rem 0.5rem; border-radius: var(--radius); cursor: pointer;
}
.challenge-choice:hover { background: var(--paper-alt); }
.challenge-choice input { width: 1.25rem; height: 1.25rem; margin: 0; flex: none; accent-color: var(--brand); }
.challenge-choice span { font-weight: 600; }
.challenge-buttons { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 1.5rem 0; }
.challenge-field { margin: 0 0 1.25rem; }
.challenge-field label { display: block; font-weight: 700; margin-bottom: 0.25rem; }
.challenge-hint { margin: 0 0 0.4rem; font-size: 0.9rem; color: var(--ink-soft); }
.challenge-field input[type="text"] {
  width: 100%; max-width: 24rem; box-sizing: border-box; min-height: 48px;
  padding: 0.4rem 0.6rem; font: inherit;
  color: var(--ink); background: var(--paper-alt);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
}
.challenge-field input#tiebreak { max-width: 8rem; }
.challenge-meta { margin-bottom: 1.25rem; }
.challenge-share { display: inline-block; margin-right: 0.75rem; white-space: nowrap; }
/* The private link, shown so it can be saved. Read-only, selectable, and as wide as the
 * column so the whole address can be seen without scrolling the field. */
.challenge-link { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin: 0.75rem 0; }
.challenge-link input {
  flex: 1 1 22rem; min-width: 0; min-height: 48px; box-sizing: border-box;
  padding: 0.4rem 0.6rem; font: inherit; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85rem; color: var(--ink); background: var(--paper);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
}

/* THE INVITATION, on the bracket page while picks are open (and the leaderboard after).
 * A dark band like the hero rather than a paper callout: it is the one thing on that page
 * asking you to do something, and it runs the full width of the column so the sentence
 * does not wrap at reading width. The button is a real link, styled by `.on-dark`. */
.challenge-banner {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 1rem 2rem;
  margin: 0 0 2rem; padding: 1.25rem 1.5rem;
  background: linear-gradient(120deg, var(--dark) 0%, var(--dark-3) 100%);
  border-left: 6px solid var(--orange); border-radius: var(--radius);
  color: var(--dark-text); box-shadow: var(--shadow);
}
.challenge-banner-text { flex: 1 1 28rem; min-width: 0; }
.challenge-banner-kicker {
  margin: 0 0 0.2rem; font-family: var(--font-display); text-transform: uppercase;
  letter-spacing: 0.08em; font-size: 0.85rem; color: var(--dark-text-soft);
}
.challenge-banner h2 { margin: 0 0 0.25rem; color: var(--gold); font-size: clamp(1.4rem, 3vw, 1.9rem); }
.challenge-banner p:last-child { margin: 0; color: var(--dark-text); }
.challenge-banner .btn { flex: 0 0 auto; }

/* Two ways into the same entry. Game by game is the filled button on a phone — the drawn
 * bracket is a metre of chart there — and the bracket is the filled one on a wide screen.
 * Only the fill swaps; the DOM order (and so the tab order) stays the same everywhere. */
.challenge-ways { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.challenge-way-list { background: var(--brand); border-color: var(--brand); color: var(--brand-ink); }
.challenge-way-list:hover { background: var(--brand-deep); border-color: var(--brand-deep); color: var(--brand-ink); }
@media (min-width: 60rem) {
  .challenge-way-list { background: var(--paper); border-color: var(--btn-outline); color: var(--btn-outline); }
  .challenge-way-list:hover { background: var(--paper-alt); color: var(--heading); }
  .challenge-way-bracket { background: var(--brand); border-color: var(--brand); color: var(--brand-ink); }
  .challenge-way-bracket:hover { background: var(--brand-deep); border-color: var(--brand-deep); color: var(--brand-ink); }
}
/* A submit button that reads as a link: switching views is navigation, not the main act. */
.challenge-linkish {
  background: none; border: 0; padding: 0.5rem 0; min-height: 44px; cursor: pointer;
  font: inherit; color: var(--link); text-decoration: underline;
}
.challenge-lead { max-width: var(--measure); }
.challenge-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem 1.5rem; }
.challenge-tools a { display: inline-flex; align-items: center; min-height: 44px; }

/* THE BRACKET AS A FORM. Each box is a fieldset wearing .bracket-box, so it sits in the
 * chart exactly as the bracket page's links do; these only undo a fieldset's defaults. */
/* position: relative is the containing block for the box's visually-hidden legend. Without
 * it the 1px legend is positioned against the PAGE, escapes the chart's sideways scroller,
 * and stretched a phone's page to the width of the whole sheet. */
.challenge-box { margin: 0; min-inline-size: 0; position: relative; }
.challenge-pick { cursor: pointer; border-radius: 3px; }
.challenge-pick:hover { background: var(--paper); }
.challenge-pick input { width: 1em; height: 1em; margin: 0; flex: none; accent-color: var(--brand); }
/* The pick is the checked button first; bold is the quick read, never the only one. */
.challenge-pick:has(input:checked) .bracket-team { font-weight: 700; color: var(--heading); }
/* THE GAMES STILL TO PICK STAND OUT; the picked and the waiting step back. Done with the
 * border and the ground, never opacity — a faded box would take its text under AA — and
 * never colour alone: the middle line says "Pick a winner", "Picked" or "Waiting" in words. */
.challenge-box.is-to-pick {
  background: var(--paper);
  border: 2px solid var(--accent-line); border-left-width: 6px;
  box-shadow: var(--shadow);
}
.challenge-box.is-to-pick .challenge-box-state { color: var(--accent-ink); }
.challenge-box.is-picked, .challenge-box.is-waiting { border-left-color: var(--line-strong); }
.challenge-box.is-waiting { border-style: dashed; border-left-style: solid; }
.challenge-box-state { font-weight: 700; color: var(--ink-soft); }
/* With the script running the bracket fills as you pick, so the no-script button goes. */
.challenge-bracket-form.is-live .challenge-fill { display: none; }

/* ---- the admin ----------------------------------------------------------- */
/* Server-rendered forms driven from a phone on a baseline (invariant 10), so the
 * layout is one column at phone width and nothing here needs a script. The admin is
 * NOT held to WCAG (D-74) — the 44px controls stay because a thumb on a baseline wants
 * them, not because a checklist does. Past phone width it uses the whole window: two
 * columns of fieldsets, and tables of inputs that need the room.
 * Colours are the site's own tokens — check:contrast has already proven every pair. */

body.admin { background: var(--paper); }
body.admin .wrap { max-width: 110rem; }

.admin-header-inner {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1.25rem;
  /* padding-BLOCK, not the shorthand: `padding: 0.75rem 0` zeroed the side gutter
   * `.wrap` provides, and the admin ran edge to edge. Same for .admin-main below. */
  padding-block: 0.75rem;
}
.admin-brand { margin: 0; font-weight: 700; }
.admin-brand a { color: var(--dark-text); text-decoration: none; }
.admin-nav {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; align-items: center;
}
.admin-nav a {
  color: var(--dark-text-soft); text-decoration: none;
  display: inline-flex; align-items: center; min-height: 44px;
}
.admin-nav a:hover { color: var(--gold); }
.admin-nav a[aria-current="page"] {
  color: var(--dark-text); font-weight: 700;
  box-shadow: inset 0 -3px 0 var(--gold);
}
.admin-logout button {
  background: none; border: none; color: var(--dark-text-soft);
  font: inherit; cursor: pointer; min-height: 44px; padding: 0;
  text-decoration: underline;
}
.admin-logout button:hover { color: var(--gold); }

.admin-main { padding-block: 1rem 3rem; }
.admin-main h1 { color: var(--heading); }

.admin-notice, .admin-error {
  padding: 0.6rem 0.9rem; border-radius: var(--radius); font-weight: 600;
}
/* Ink text with a coloured edge: the words carry the meaning (invariant 11), and --ink
 * on --paper-alt is a proven pair in both schemes — --ok and --brand-deep as text are not. */
.admin-notice { background: var(--paper-alt); border-left: 4px solid var(--ok); color: var(--ink); }
.admin-error { background: var(--paper-alt); border-left: 4px solid var(--accent-line); color: var(--ink); }

/* One column. Labels above inputs, hints below — the reading order a screen reader
 * and a narrow phone both want. */
.admin-form { max-width: 44rem; }
.admin-form fieldset {
  min-width: 0;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  margin: 0 0 1.25rem; padding: 0.75rem 1rem 1rem;
}
.admin-form legend { font-weight: 700; color: var(--heading); padding: 0 0.35rem; }
.admin-form p { margin: 0.75rem 0; }
.admin-form label { display: block; font-weight: 600; margin-bottom: 0.25rem; }
.admin-form small, .admin-table small, .admin-cards small {
  display: block; color: var(--ink-soft); font-weight: 400; margin-top: 0.25rem;
}
.admin-form input[type="text"], .admin-form input[type="search"],
.admin-form input[type="number"], .admin-form input[type="url"],
.admin-form input[type="date"], .admin-form input[type="time"],
.admin-form input[type="datetime-local"], .admin-form input[type="password"],
.admin-form select, .admin-form textarea,
.admin-searchbar input[type="search"] {
  width: 100%; box-sizing: border-box; min-height: 44px;
  padding: 0.4rem 0.6rem; font: inherit;
  /* --paper-alt, not --logo-pad: the logo pad is deliberately white in BOTH schemes,
   * and dark mode lightens --ink — light text on a white input would be invisible. */
  color: var(--ink); background: var(--paper-alt);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
}
.admin-form input[type="number"] { max-width: 10rem; }
.admin-form input[type="date"], .admin-form input[type="time"] { max-width: 14rem; }
.admin-form input[type="datetime-local"] { max-width: 18rem; }
.admin-form textarea { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.admin-inline-options label { display: inline-flex; align-items: center; gap: 0.35rem; margin-right: 1rem; font-weight: 400; }

.admin-form button, .admin-searchbar button, .admin-table button, .admin-cards button {
  font: inherit; min-height: 44px; padding: 0.4rem 1rem; cursor: pointer;
  border: 1px solid var(--btn-outline); border-radius: var(--radius);
  background: var(--paper); color: var(--btn-outline); font-weight: 600;
}
.admin-form button:hover, .admin-searchbar button:hover,
.admin-table button:hover, .admin-cards button:hover { background: var(--paper-alt); }
.admin-primary { background: var(--brand) !important; color: var(--brand-ink) !important; border-color: var(--brand) !important; }
.admin-primary:hover { background: var(--brand-deep) !important; }
/* Danger is words plus a heavier border, never colour alone (invariant 11). */
.admin-danger { border-width: 2px !important; }

.admin-table { border-collapse: collapse; width: 100%; margin: 1rem 0; }
.admin-table th, .admin-table td {
  text-align: left; vertical-align: top;
  padding: 0.5rem 0.75rem 0.5rem 0; border-bottom: 1px solid var(--line);
}
.admin-table thead th { border-bottom: 2px solid var(--line-strong); color: var(--heading); }
.admin-table tbody th { font-weight: 600; }

.admin-rowlinks { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; margin: 0.5rem 0; }
.admin-rowlinks a { display: inline-flex; align-items: center; min-height: 32px; }
/* Inside a table the cell must STAY a table cell. As a flex box it dropped out of the
 * row's layout and sat out of line with its neighbours — the Awards list's last column
 * was the one that showed it. The children wrap inline instead. */
.admin-table td.admin-rowlinks { display: table-cell; margin: 0; }
.admin-table td.admin-rowlinks > * { display: inline-block; vertical-align: middle; margin: 0 0.75rem 0.25rem 0; }

/* A link that leaves the admin opens a new tab, and says so with the usual mark. */
.admin-external svg { width: 0.85em; height: 0.85em; margin-left: 0.3em; vertical-align: -0.05em; }

/* ---- two columns ----
 * `.admin-columns` lays its children out side by side once there is room: fieldsets on
 * a form, or sections on a page. A child with `.admin-span` takes the full row. Below
 * 64rem it is the one column a phone needs. */
.admin-wide { max-width: none; }
.admin-columns { display: grid; gap: 0 1.5rem; grid-template-columns: minmax(0, 1fr); align-items: start; }
@media (min-width: 64rem) {
  .admin-columns { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .admin-columns > .admin-span { grid-column: 1 / -1; }
}
.admin-columns > section > h2:first-child { margin-top: 0.5rem; }

/* ---- tabs ----
 * Plain links styled as tabs: one page per tab, so each works with JavaScript off and
 * has its own address to come back to. The team editor uses them for "the program"
 * and then one tab per year it played. */
.admin-tabs {
  list-style: none; margin: 1rem 0 1.25rem; padding: 0;
  display: flex; flex-wrap: wrap; gap: 0.25rem;
  border-bottom: 2px solid var(--line-strong);
}
.admin-tabs a {
  display: inline-flex; align-items: center; min-height: 40px; padding: 0.25rem 0.9rem;
  border: 1px solid var(--line); border-bottom: none; border-radius: var(--radius) var(--radius) 0 0;
  background: var(--paper-alt); text-decoration: none; color: var(--link); font-weight: 600;
}
.admin-tabs a[aria-current="page"] {
  background: var(--brand); color: var(--brand-ink); border-color: var(--brand);
}
.admin-tabs small { font-weight: 400; margin-left: 0.35rem; }
.admin-tabs a[aria-current="page"] small { color: var(--brand-ink); }
.admin-tab-heading { margin-bottom: 0; }

/* ---- colour fields ----
 * The text box is the field (it can be empty, which a native colour input cannot be);
 * color-field.js adds the picker beside it and keeps the two in step. */
.admin-color { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
/* The logo eyedropper (D-95): a large copy of the logo, a crosshair, the colour under it. */
.admin-eyedrop { margin: 0.5rem 0 1rem; padding: 0.75rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper-alt); max-width: 22rem; }
.admin-eyedrop p { margin: 0 0 0.5rem; font-size: 0.9rem; }
.admin-eyedrop canvas { display: block; width: 100%; max-width: 280px; height: auto; cursor: crosshair; touch-action: none;
  background: repeating-conic-gradient(var(--line) 0% 25%, transparent 0% 50%) 50% / 16px 16px; }
.admin-eyedrop-row { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.5rem; }
.admin-eyedrop-swatch { width: 2rem; height: 2rem; border: 1px solid var(--line-strong); border-radius: 4px; }
.admin-color input[type="text"] { max-width: 9rem; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.admin-color input[type="color"] {
  inline-size: 3.25rem; block-size: 44px; padding: 2px; cursor: pointer;
  border: 1px solid var(--line-strong); border-radius: var(--radius); background: var(--paper-alt);
}

/* ---- the awards set editor ----
 * One table, one form, every row editable at once. Narrow inputs so a whole
 * all-tournament team fits on a laptop screen without scrolling sideways. */
.admin-awards input[type="text"], .admin-awards select {
  width: 100%; box-sizing: border-box; min-height: 40px; padding: 0.3rem 0.5rem; font: inherit;
  color: var(--ink); background: var(--paper-alt);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
}
.admin-awards td { vertical-align: middle; }
.admin-awards .admin-check { text-align: center; width: 4.5rem; }
.admin-awards .admin-check input { inline-size: 1.25rem; block-size: 1.25rem; }
.admin-awards tr.is-removed td { opacity: 0.45; text-decoration: line-through; }
.admin-scroll { overflow-x: auto; }

.admin-cards { list-style: none; margin: 1rem 0; padding: 0; display: grid; gap: 0.5rem; }
.admin-cards li {
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center;
  gap: 0.5rem; border: 1px solid var(--line); border-radius: var(--radius);
  padding: 0.5rem 0.75rem; background: var(--paper-alt);
}
.admin-cards a { display: inline-flex; align-items: center; min-height: 32px; }

.admin-searchbar { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: end; margin: 1rem 0; max-width: 44rem; }
.admin-searchbar label { font-weight: 600; }
.admin-searchbar input[type="search"] { flex: 1 1 12rem; width: auto; }
.admin-searchbar select {
  flex: 1 1 14rem; min-height: 44px; padding: 0.4rem 0.6rem; font: inherit;
  color: var(--ink); background: var(--paper-alt);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
}

/* The courtside page: the two scores are the page, so they are enormous. */
.admin-score { max-width: 30rem; }
.admin-score-line label { font-size: 1.15rem; }
.admin-score-input { font-size: 1.6rem !important; }
.admin-score-save { width: 100%; font-size: 1.25rem; }

/* A long form folded away until it is wanted — the team totals block is fifteen inputs
 * and the box score page carries two of them. <details> is native HTML: it opens with a
 * click, with Enter, and with JavaScript off (invariant 10). */
.admin-details {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--paper-alt); margin: 1rem 0; padding: 0 0.75rem;
  max-width: 44rem;
}
.admin-details > summary {
  font-weight: 600; color: var(--heading); cursor: pointer;
  min-height: 44px; display: flex; align-items: center;
}
.admin-details[open] > summary { border-bottom: 1px solid var(--line); }
.admin-details > *:last-child { margin-bottom: 0.75rem; }

.admin-onsite { padding: 0.25rem 1rem; }
.admin-onsite h2 { margin-top: 0.5rem; }
.admin-setting { border-bottom: 1px solid var(--line); padding-bottom: 0.75rem; }
.admin-setting p { margin: 0.5rem 0; }

/* The inline-markup editor (D-72). The toolbar is added by markup-editor.js and does not
 * exist without it; the textarea and the Preview button are the whole editor then. Each
 * button is a 44px target, like every other admin control. */
.markup-toolbar { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-bottom: 0.35rem; }
.admin-form .markup-toolbar button { min-width: 44px; padding: 0.4rem 0.7rem; }
.markup-toolbar .mt-bold { font-weight: 800; }
.markup-toolbar .mt-italic { font-style: italic; }
.markup-toolbar .mt-strike { text-decoration: line-through; }
.markup-preview { margin: 0.75rem 0; }
.markup-preview .announcement-bar { padding-inline: 0.8rem; border-radius: var(--radius); }
.admin-form .markup-preview-label { margin: 0 0 0.35rem; font-weight: 600; font-size: 0.9rem; }

/* The page editor (D-97): Quill, from public/vendor/, over the body's textarea. Quill's
 * own stylesheet is light-only and sized for a mouse, so these give it the site's tokens
 * (dark mode included) and 44px targets. Without the script none of this exists. */
.page-editor-modes { display: flex; gap: 0.35rem; margin-bottom: 0.5rem; }
.admin-form .page-editor-modes button[aria-pressed="true"] {
  background: var(--brand); color: var(--brand-ink); border-color: var(--brand);
}
.page-editor-visual .ql-toolbar.ql-snow {
  position: sticky; top: 0; z-index: 2; padding: 0.25rem;
  background: var(--paper); border: 1px solid var(--line-strong);
  border-radius: var(--radius) var(--radius) 0 0; font-family: var(--font-body);
}
.page-editor-visual .ql-snow.ql-toolbar .ql-formats { margin-right: 0.5rem; }
.page-editor-visual .ql-snow.ql-toolbar button {
  width: 44px; height: 44px; min-height: 44px; padding: 11px; border: 0; border-radius: var(--radius);
  background: transparent;
}
.page-editor-visual .ql-snow.ql-toolbar button.ql-active { background: var(--paper-alt); }
.page-editor-visual .ql-snow .ql-picker { height: 44px; color: var(--ink); }
.page-editor-visual .ql-snow .ql-picker-label { display: flex; align-items: center; }
.page-editor-visual .ql-snow .ql-stroke { stroke: var(--ink); }
.page-editor-visual .ql-snow .ql-fill, .page-editor-visual .ql-snow .ql-stroke.ql-fill { fill: var(--ink); }
.page-editor-visual .ql-snow .ql-active .ql-stroke, .page-editor-visual .ql-snow button:hover .ql-stroke,
.page-editor-visual .ql-snow .ql-picker-label:hover .ql-stroke { stroke: var(--link); }
.page-editor-visual .ql-snow .ql-active .ql-fill, .page-editor-visual .ql-snow button:hover .ql-fill { fill: var(--link); }
.page-editor-visual .ql-snow .ql-picker-label:hover, .page-editor-visual .ql-snow .ql-picker-label.ql-active,
.page-editor-visual .ql-snow .ql-picker-item:hover, .page-editor-visual .ql-snow .ql-picker-item.ql-selected { color: var(--link); }
.page-editor-visual .ql-snow .ql-picker-options { background: var(--paper); border-color: var(--line-strong); }
.page-editor-visual .ql-snow .ql-picker-item { min-height: 44px; display: flex; align-items: center; }
.page-editor-visual .ql-snow .ql-picker.ql-expanded .ql-picker-label { border-color: var(--line-strong); }
.page-editor-visual .ql-snow .ql-tooltip {
  background: var(--paper); color: var(--ink); border-color: var(--line-strong); z-index: 3;
}
.page-editor-visual .ql-snow .ql-tooltip input[type="text"] {
  width: 14rem; height: 44px; font-size: 1rem; color: var(--ink); background: var(--paper-alt);
  border-color: var(--line-strong);
}
.page-editor-visual .ql-snow a { color: var(--link); }
.page-editor-visual .ql-snow .ql-tooltip a.ql-action, .page-editor-visual .ql-snow .ql-tooltip a.ql-remove {
  display: inline-block; min-height: 44px; line-height: 44px;
}
.page-editor-visual .ql-container.ql-snow {
  border: 1px solid var(--line-strong); border-top: 0; border-radius: 0 0 var(--radius) var(--radius);
  background: var(--paper); font: inherit;
}
/* The text area itself reads like the public page: the site's type, then the .page-body
 * rules above, over Quill's resets. */
.ql-editor.page-body {
  min-height: 24rem; padding: 1rem; color: var(--ink);
  font-family: var(--font-body); font-size: 1.0625rem; line-height: 1.6;
}
.ql-editor.page-body p { margin: 0 0 1rem; }
.ql-editor.page-body h1, .ql-editor.page-body h2, .ql-editor.page-body h3 {
  font-family: var(--font-display); font-weight: 700; letter-spacing: 0.01em; line-height: 1.05;
  color: var(--heading); margin: 1.75rem 0 0.5em;
}
.ql-editor.page-body h2 { font-size: clamp(1.3rem, 3vw, 1.7rem); text-transform: uppercase; }
.ql-editor.page-body h3 { font-size: 1.25rem; }
.ql-editor.page-body > :first-child { margin-top: 0; }
.ql-editor.page-body ol { margin: 0 0 1rem; }
.ql-editor.page-body li { margin-bottom: 0.3rem; }
.ql-editor.page-body a { color: var(--link); }
.ql-editor.page-body img { display: inline-block; }
.ql-snow .ql-editor.page-body.ql-blank::before { color: var(--ink-soft); }
.page-editor-status:empty { display: none; }
.page-editor-status { margin: 0.5rem 0 0; font-weight: 600; }

.admin-page-images { max-width: 44rem; margin-top: 2rem; border-top: 1px solid var(--line); }
.admin-uploaded img { max-width: 16rem; border: 1px solid var(--line); border-radius: var(--radius); }

.admin-chip {
  display: inline-block; border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 0.1rem 0.7rem; margin: 0 0.25rem 0.25rem 0; background: var(--paper-alt);
}
