/* Hooperslist — shared base template chrome (header/footer) + global
   Blacktop+ tokens (docs/color-palette.md). Every public page inherits
   this via templates/base/base.html. landing.css/demo.css pre-date this
   file and keep their own scoped token redeclaration under .landing-page/
   .demo-page (harmless - identical values) rather than being touched here. */

:root {
  --bg: #08080A;
  --bg-raised: #131215;
  --bg-card: #1A181B;
  --border: #2A2520;
  --border-strong: #3D342B;
  --primary: #F26A10;
  --primary-hover: #FF8330;
  --orange-deep: #9C4310;
  --on-primary: #140A03;
  /* Comma-separated R,G,B of --primary (not a 5th independent token) - lets
     tinted glow/hover effects below write rgba(var(--primary-rgb), <alpha>)
     instead of a literal hex baked in at that alpha, so they track a
     league's theme_palette the same way var(--primary) itself already does.
     apps/leagues/theme.py computes this alongside primary for every palette
     entry; templates/base/league_public_base.html overrides it together
     with the other four theme tokens. */
  --primary-rgb: 242, 106, 16;
  --teal: #2FD3C4;
  --violet: #9B7BFF;
  --lime: #B6E24A;
  --gold: #F5C34B;
  --alert: #FF5A4D;
  /* Muted/warm-shifted variants of the five accents above, scoped to
     .hl-badge's position/status chips only (see "Status badges / chips"
     below) - the plain --teal/--violet/--lime/--gold/--alert stay
     untouched everywhere else they're used (avatar-placeholder accents,
     landing/demo illustrations, league_home's announcement icon chips,
     stat_leaders/league_leaderboards' gold-tier rows, ...) since those
     weren't part of this request and still read fine at full brightness.
     Badge chips sit directly on page backgrounds in dense rows/tables
     though, where the original hues read as bright "stock UI kit" colors
     against Blacktop+'s warm black/orange field - these desaturate and
     hue-shift each one slightly warmer while keeping contrast against
     --bg-card comfortably AA (>=4.6:1, checked against WCAG). */
  --badge-teal: #40B59E;
  --badge-violet: #B890D5;
  --badge-lime: #BCC653;
  --badge-gold: #DFB249;
  --badge-alert: #E45144;
  --text: #F5F1EC;
  --text-secondary: #B3A99C;
  --text-muted: #7C7266;

  /* ─── Type scale (docs/color-palette.md) ───────────────────────── */
  --font-heading: "Saira Condensed", sans-serif;
  --font-body: "Inter", Arial, sans-serif;
  --font-mono: "JetBrains Mono", monospace;

  /* ─── Spacing scale (4px base grid, docs/color-palette.md) ─────── */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-7: 40px;
  --space-8: 56px;

  /* Shared minimum column width for "item container" grids that should read
     as the same size sitewide - currently the Showcase League Management
     member cards and the Awards Double-Double / Triple-Double leader cards. */
  --hl-tile-min: min(280px, 100%);
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.5;
}

main {
  flex: 1;
}

a {
  color: inherit;
}

/* Alpine hides [x-cloak] elements itself once it boots, but the browser
   paints them first - global so every page using x-cloak (not just
   player_profile.css, which redeclares this identically) gets the
   pre-hydration flash suppressed, notably leagues/_public_nav.html's More
   flyout, which renders on every public page. */
[x-cloak] {
  display: none !important;
}

/* ─── Headings (Saira Condensed, uppercase, per docs/color-palette.md) */
h1, h2, h3, h4 {
  font-family: var(--font-heading);
  text-transform: uppercase;
  margin: 0;
}

h1 { font-size: 28px; line-height: 1.1; font-weight: 800; letter-spacing: 0.2px; }
h2 { font-size: 22px; line-height: 1.15; font-weight: 800; }
h3 { font-size: 20px; line-height: 1.15; font-weight: 700; }
h4 { font-size: 16px; line-height: 1.2; font-weight: 700; }

p {
  margin: 0 0 var(--space-3);
  color: var(--text-secondary);
}

.legal-page ul {
  margin: 0 0 var(--space-3);
  padding-left: var(--space-5);
  color: var(--text-secondary);
}
.legal-page li {
  margin-bottom: var(--space-2);
}
.legal-page li:last-child {
  margin-bottom: 0;
}

.hl-display {
  font-family: var(--font-heading);
  text-transform: uppercase;
  font-size: clamp(34px, 6vw, 56px);
  line-height: 1.03;
  font-weight: 800;
}

.hl-eyebrow {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--primary);
  margin: 0 0 var(--space-2);
}

.hl-body-lg { font-size: 16px; line-height: 1.6; }
.hl-body-sm { font-size: 13px; line-height: 1.5; }
.hl-label { font-size: 12px; line-height: 1.4; font-weight: 500; }
.hl-caption { font-size: 11px; line-height: 1.4; font-weight: 500; color: var(--text-muted); }
.hl-micro {
  font-size: 10px;
  line-height: 1.3;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-muted);
}
.hl-stat { font-family: var(--font-mono); font-size: 12px; font-weight: 700; }
.hl-muted { color: var(--text-muted); }

/* ─── Page shell / layout primitives ────────────────────────────── */
.hl-page {
  max-width: 960px;
  margin: 0 auto;
  padding: var(--space-6) clamp(20px, 5vw, 64px);
  width: 100%;
  box-sizing: border-box;
}

.hl-page--narrow {
  max-width: 640px;
}

.hl-section {
  margin-bottom: var(--space-7);
}

.hl-section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  flex-wrap: wrap;
}

.hl-section-head h2 {
  margin: 0;
}

/* ─── Section-head title icons ────────────────────────────────────────
   Promoted from league_home.css (Phase-0 of the site-wide Playbook
   conversion) - a small leading icon on a section's <h2> reads as cheap
   wayfinding and breaks up a page's run of visually-identical plain-text
   headings, and every other public page converted after League Home
   wants the same thing, not a copy of these two rules per page. `{% icon
   %}` (apps.core.templatetags.icons) already carries its own
   viewBox/sizing - `.hl-section-icon` just colors/aligns whatever sits
   inside `.hl-section-title`. */
.hl-section-title {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.hl-section-icon {
  color: var(--primary);
  flex-shrink: 0;
}

.hl-link {
  color: var(--primary);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.hl-link:hover {
  color: var(--primary-hover);
}

/* ─── Breadcrumbs (public league pages only - templates/base/
   league_public_base.html's `breadcrumbs` block; not used on the
   landing/demo marketing pages or admin_kit's own trail) ───────────── */
.hl-breadcrumbs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: var(--space-4);
  font-size: 12px;
  font-weight: 500;
}

.hl-breadcrumbs a {
  color: var(--text-muted);
  text-decoration: none;
}

.hl-breadcrumbs a:hover {
  color: var(--primary);
}

.hl-breadcrumbs-sep {
  color: var(--text-muted);
  opacity: 0.5;
}

.hl-breadcrumbs-current {
  color: var(--text);
  font-weight: 600;
}

/* ─── Card ───────────────────────────────────────────────────────── */
.hl-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: var(--space-5);
}

.hl-card + .hl-card {
  margin-top: var(--space-3);
}

/* Grid items are spaced by `gap`; the stacked-list margin above would
   push every card but the first down and make the first look taller. */
.hl-card-grid > .hl-card + .hl-card,
.stl-grid > .hl-card + .hl-card {
  margin-top: 0;
}

.hl-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--space-4);
}

/* ─── Plain edit forms on a public page (e.g. player self-edit) ─────
   Generic enough to reuse anywhere a simple field-per-row form needs to
   render inside .hl-card without the admin console's admin_kit CSS
   available - not tied to one page. */
.hl-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: var(--space-4);
}

.hl-field .hl-input,
.hl-field textarea.hl-input {
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}

.hl-field-error {
  color: var(--alert);
  font-size: 12px;
}

.hl-field-preview {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 12px;
  border: 1px solid var(--border);
  display: block;
  margin-bottom: var(--space-2);
}

/* Show/hide toggle on a password field (templates/accounts/_login_form.html,
   shared by /login/ and the sitewide login modal) - generic enough to sit
   here with the rest of the .hl-field vocabulary rather than login.css,
   which the modal never loads. */
.hl-password-field {
  position: relative;
  display: flex;
}

.hl-password-field .hl-input {
  padding-right: 40px;
}

.hl-password-toggle {
  position: absolute;
  top: 50%;
  right: 4px;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.hl-password-toggle:hover { background: var(--bg-card); color: var(--text); }
.hl-password-toggle:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.hl-password-toggle svg { width: 18px; height: 18px; }

/* Change-password form (templates/accounts/_password_change_form.html,
   shared by /account/password/ and the "Edit profile" section of
   players/player_self_edit.html). New-password field's help text is
   Django's own validator <ul>, so it's flattened to a compact list here. */
.hl-password-help ul {
  margin: 0;
  padding-left: 1.1em;
}

.hl-password-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

/* Post-action confirmation (Django messages framework) - the lime accent
   the rest of the site uses for "good" states, tinted rather than solid so
   it reads on both .hl-card and page backgrounds. */
.hl-alert-success {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid rgba(182, 226, 74, 0.4);
  border-radius: 10px;
  background: rgba(182, 226, 74, 0.1);
  color: var(--lime);
  font-size: 14px;
  font-weight: 600;
}

/* Login form's own submit button (templates/accounts/_login_form.html) -
   shared by the full /login/ page and the sitewide login modal, so it
   lives here rather than login.css. */
.login-submit {
  width: 100%;
  justify-content: center;
  margin-top: var(--space-2);
}

/* "Prefer the full page?" link under the sitewide login modal's form
   (templates/core/_login_modal_body.html) - small, muted, centered, so it
   reads as a secondary escape hatch rather than competing with the
   primary submit button above it. */
.hl-login-modal-alt {
  margin: var(--space-3) 0 0;
  text-align: center;
  font-size: 13px;
  color: var(--text-muted);
}

/* ─── Buttons ────────────────────────────────────────────────────── */
.hl-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 20px;
  border-radius: 10px;
  font-weight: 700;
  font-size: 14px;
  text-decoration: none;
  line-height: 1;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.1s ease;
}

.hl-btn:active {
  transform: scale(0.97);
}

.hl-btn:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

.hl-btn:disabled {
  opacity: 0.6;
  cursor: default;
}

.hl-btn-primary {
  background: var(--primary);
  color: var(--on-primary);
}

.hl-btn-primary:hover {
  background: var(--primary-hover);
}

.hl-btn-secondary {
  background: var(--bg-card);
  color: var(--text);
  border-color: var(--border-strong);
}

.hl-btn-secondary:hover {
  border-color: var(--primary);
  color: var(--primary);
}

/* ─── Table ──────────────────────────────────────────────────────── */
/* position: relative makes this the containing block for any
   position:absolute descendant (e.g. a sr-only "<span
   class="hl-sr-only">Share</span>" header label on a trailing icon-only
   column, see players/player_profile.html's Games-this-season table)
   that has no other positioned ancestor of its own. Without it, that
   absolute descendant's containing block falls through to the initial
   containing block instead - rendering at its natural table-column x
   position (which can sit well past the viewport on a narrow phone,
   since the table itself is wider than this wrap's clipped box) and
   inflating documentElement.scrollWidth into a real, page-level
   horizontal scrollbar, completely bypassing this element's own
   overflow-x: auto containment (confirmed: removing position:relative
   reproduces a 51px page-level overflow on a 390px viewport even though
   this wrap's own box measures fully inside it - overflow-x/-y here only
   clip normal-flow content, not an absolutely-positioned descendant
   that's escaped to a different containing block entirely). */
.hl-table-wrap {
  position: relative;
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: 12px;
}

.hl-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.hl-table th {
  text-align: left;
  font-family: var(--font-body);
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-muted);
  padding: 10px 14px;
  background: var(--bg-raised);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

.hl-table td {
  padding: 12px 14px;
  border-bottom: 1px solid var(--border);
  color: var(--text);
}

.hl-table tbody tr:last-child td {
  border-bottom: none;
}

.hl-table tbody tr {
  transition: background-color 0.15s ease;
}

.hl-table tbody tr:hover,
.hl-table tbody tr:focus-within {
  background: var(--bg-raised);
}

/* ─── Row-as-link tables - opt in per <table> via .hl-table--row-link.
   Without this, a row's hover highlight above implies the WHOLE row is
   clickable when really only one small inline link inside it is (or, on
   some pages, none at all) - dead space everywhere else. This turns the
   row's one designated link (.hl-table-row-link-target, put on whichever
   <a> represents "this row" - the game a schedule row is for, the team a
   standings row is for) into a full-row "stretched link" via ::after, so
   tapping anywhere in the row/card navigates. A row that ALSO has other,
   separate links (e.g. a schedule row's opponent link alongside its game
   link) marks that link's <td> with .hl-table-cell-interactive to keep it
   independently clickable above the overlay - same two-layer pattern
   static/css/players.css established first for the player directory,
   generalized here since the underlying gap recurs across
   standings/schedule/team-detail/etc. A real <a>, not a JS click handler,
   so ctrl/cmd-click, middle-click, and keyboard activation all keep
   working for free.

   Not used by game_detail.css's box score table - that table's first
   column is separately made `position: sticky` for horizontal scroll
   (see game_detail.css), which becomes the nearest positioned ancestor
   for anything inside it and would confine the stretched-link overlay to
   just that column's width instead of the full row; the two techniques
   don't compose on the same cell, so that table keeps just its one
   inline player-name link instead. */
.hl-table--row-link tbody tr:not(.hl-table-group-row) {
  position: relative;
  /* `position: relative` alone doesn't reliably make a <tr> (table-row
     box) the containing block for the absolutely-positioned stretched-link
     ::after below in Safari/WebKit (webkit.org bug 240961 - fixed in STP
     243, but still live on most shipping iPadOS/iOS Safari, and shared by
     desktop Safari) - every row's overlay falls back to the nearest
     containing block that DOES work, which is shared by every row, so
     they all end up covering the same rect and paint order (last row
     wins) decides which one intercepts the tap/click - clicking any
     player row navigates to that same wrong row instead of the one
     actually clicked. `transform` and `clip-path` don't share that bug
     and are containing-block triggers in their own right, so pairing them
     with `position: relative` fixes Safari while staying a no-op
     everywhere `position: relative` already worked correctly - same fix
     static/css/players.css's `.lp-row` already applies for the player
     directory table. */
  transform: translate(0);
  clip-path: inset(0);
  cursor: pointer;
}

.hl-table--row-link .hl-table-row-link-target::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}

.hl-table--row-link .hl-table-cell-interactive {
  position: relative;
  z-index: 2;
}

.hl-table .num {
  font-family: var(--font-mono);
  text-align: right;
  font-variant-numeric: tabular-nums;
  /* th already gets this above - td didn't, so a squeezed table (narrow
     viewport, many columns) could wrap a value like "10-17" onto two
     lines instead of just growing .hl-table-wrap's intended horizontal
     scroll. Numeric data should never wrap. */
  white-space: nowrap;
}

/* ─── Center modifier - opt in per <table> via .hl-table--center for
   pages where every column reads better centered (team_detail.html's
   roster/recent/upcoming tables) than the shared left-align default above.
   `.num` cells still opt out of the shared right-align but land centered
   too, not left, so a table can mix numeric and text columns without one
   column reading out of step with the rest. Only a desktop concern - the
   `.hl-table--stack` breakpoint below always forces `text-align: left` on
   stacked "label: value" cells regardless of this modifier, since a
   centered label/value pair reads worse stacked than left-aligned does. */
.hl-table--center th,
.hl-table--center td {
  text-align: center;
}

.hl-table--center .num {
  text-align: center;
}

/* ─── Compact modifier - denser rows for long lists (schedule) ──────── */
.hl-table--compact th,
.hl-table--compact td {
  padding: 7px 12px;
}

/* ─── Day-group header row (schedule page groups games by date so the
   date itself isn't repeated on every row) ──────────────────────────── */
.hl-table-group-row td {
  background: var(--bg-raised);
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  padding: 6px 14px;
}

.hl-table-group-row td::before {
  content: none;
}

/* ─── Schedule table header - left-aligned to match every column's own
   data (Time/Matchup/Venue/Status all render left-aligned; `.num`
   already right-aligns Score's <td> below, and its <th> above now
   carries the same class), not centered - centered headers over
   left-aligned data made every column heading visibly drift away from
   the values underneath it (most noticeable on Time/Matchup, where the
   header sat well right of where "6:00 PM"/the team names actually
   start). ─────────────────────────────────────────────────────────── */
.hl-schedule-table th {
  text-align: left;
}

/* Score's <th> carries the shared `.num` class too now (template), which
   already right-aligns via the generic `.hl-table .num` rule above (two
   classes beats this rule's one class+element, regardless of source
   order) - no extra rule needed here, just the class on the element. */

/* Trailing icon-only header (see the template's own comment) - centered
   in its narrow column since it's a single small glyph, not text that
   needs to line up with a value below it the way the other headers do. */
.hl-schedule-table th.hl-schedule-ics-header {
  text-align: center;
  color: var(--text-muted);
}

.hl-schedule-table td[data-label="Time"] {
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: 12px;
}

.hl-schedule-table td[data-label="Venue"] {
  color: var(--text-secondary);
}

/* ─── Schedule matchup (team names + winner emphasis) ───────────────── */
.hl-schedule-matchup a {
  white-space: normal;
  display: inline-flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px;
}

.hl-schedule-team {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
}

.hl-schedule-team .hl-avatar,
.hl-schedule-team .hl-avatar-placeholder {
  width: 20px;
  height: 20px;
  font-size: 9px;
}

.hl-schedule-team--win {
  color: var(--text);
  font-weight: 800;
}

.hl-schedule-vs {
  color: var(--text-muted);
  font-weight: 400;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
}

/* ─── "Tips off in X" next-game highlight (view-computed `game.is_next`,
   apps.games.views.season_calendar_view) - 5% background tint read as
   almost no different from a plain row against this theme's near-black
   page background (verified: rgba(240,180,41,0.05) over rgb(8,8,10) is
   barely perceptible), leaving the amber "Tips off in..." text as the
   only real signal. Bumped to 9% and paired with a left accent bar for a
   highlight that reads at a glance instead of needing to be read.
   Desktop (non-stacked table) only - each `<td>` is a real table cell
   there, so per-cell backgrounds tile seamlessly into one solid block.
   Mobile's stacked cards handle the same highlight differently, right
   after the card-polish block below - painting every `<td>` there hits
   two cells that don't span the card's full width (Status/Score, now
   sitting inline together, and the absolutely-positioned icon cell),
   which left gaps showing the plain card color through and a small
   separately-tinted box behind the calendar icon. Tinting the card
   itself instead avoids all of that. ─────────────────────────────────── */
@media (min-width: 641px) {
  .hl-schedule-row--next td {
    background: rgba(var(--primary-rgb), 0.09);
  }

  /* Left accent bar. `::after`, not `::before` - static/css/players.css's
     `.lp-row` left-accent bar uses the same convention for the same
     reason: `.hl-table--stack td::before` already owns that pseudo-
     element on every stacked cell for its mobile label prefix (this
     table's rules use the same class names at any width, so the
     convention carries over even though this block is desktop-only).
     Positioning context is the row itself (already `position: relative`
     via `.hl-table--row-link`, which this table carries), not the td -
     same reasoning as game_detail.css's `.gd-table-leader` equivalent. */
  .hl-schedule-row--next td:first-child::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 3px;
    background: var(--primary);
  }
}

/* ─── Add-to-calendar icon link (apps.games.views.game_ics_view) - a
   plain icon-only affordance, same weight as a secondary table action
   rather than a full .hl-btn, since it sits in its own narrow trailing
   column on every eligible row. */
.hl-schedule-ics-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  color: var(--text-muted);
  text-decoration: none;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.hl-schedule-ics-link:hover {
  background: var(--bg-raised);
  color: var(--primary);
}

.hl-schedule-next-badge {
  display: inline-block;
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--primary);
  white-space: nowrap;
}

/* ─── Scroll-reveal (see static/css/standings.css's identical comment -
   shared [data-lh-reveal] mechanism, loads on every public page). */
html.lh-anim-ready [data-lh-reveal] .hl-schedule-table tbody tr:not(.hl-table-group-row) {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.4s ease, transform 0.4s ease, background-color 0.15s ease;
  transition-delay: calc(var(--i, 0) * 0.02s);
}

html.lh-anim-ready [data-lh-reveal].lh-is-visible .hl-schedule-table tbody tr:not(.hl-table-group-row) {
  opacity: 1;
  transform: none;
}

/* ─── Schedule score (winner's number emphasized, same signal color as
   the "completed" status badge so the two reinforce each other) ─────── */
.hl-schedule-score {
  color: var(--text-muted);
  font-weight: 600;
}

.hl-schedule-score--win {
  color: var(--lime);
  font-weight: 800;
}

.hl-schedule-score-sep {
  color: var(--text-muted);
  margin: 0 2px;
}

.hl-schedule-forfeit-note {
  color: var(--alert);
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
}

/* ─── Pagination ─────────────────────────────────────────────────────── */
.hl-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.hl-pagination-status {
  font-size: 12px;
  color: var(--text-muted);
  font-family: var(--font-mono);
}

.hl-pagination-disabled {
  opacity: 0.4;
  pointer-events: none;
}

/* ─── Status badges / chips ─────────────────────────────────────── */
.hl-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  white-space: nowrap;
  background: rgba(179, 169, 156, 0.16);
  color: var(--text-secondary);
}

.hl-badge::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.hl-badge[data-status="scheduled"],
.hl-badge[data-status="upcoming"],
.hl-badge[data-status="pending"] { background: rgba(184, 144, 213, 0.16); color: var(--badge-violet); }

.hl-badge[data-status="in_progress"],
.hl-badge[data-status="active"] { background: rgba(64, 181, 158, 0.16); color: var(--badge-teal); }

.hl-badge[data-status="completed"],
.hl-badge[data-status="paid"],
.hl-badge[data-status="win"] { background: rgba(188, 198, 83, 0.16); color: var(--badge-lime); }

.hl-badge[data-status="forfeited"],
.hl-badge[data-status="cancelled"],
.hl-badge[data-status="overdue"],
.hl-badge[data-status="loss"] { background: rgba(228, 81, 68, 0.16); color: var(--badge-alert); }

.hl-badge[data-status="waived"] { background: rgba(223, 178, 73, 0.16); color: var(--badge-gold); }

/* Position color-coding (paired with .hl-badge--plain below, and
   apps.core.templatetags.badges.position_slug which turns Player's free
   -text position into one of these three curated slugs - anything else
   falls back to the plain neutral badge). */
.hl-badge[data-position="guard"] { background: rgba(64, 181, 158, 0.16); color: var(--badge-teal); }
.hl-badge[data-position="forward"] { background: rgba(188, 198, 83, 0.16); color: var(--badge-lime); }
.hl-badge[data-position="center"] { background: rgba(223, 178, 73, 0.16); color: var(--badge-gold); }

/* Modifier for a chip that labels a plain attribute (e.g. a player's
   position) rather than a live/changing status - drops the leading dot,
   which otherwise reads as a status indicator. */
.hl-badge--plain::before { content: none; }

/* ─── Live-updated badge (templates/core/_live_updated_badge.html) ──
   Small pulsing-dot + "Updated X ago" trust signal for a page whose data
   is Celery-recalculated rather than written live (Standings/Leaders) -
   reads as "this is a live dashboard", not a plain caption. Shares the
   same teal used for .hl-badge[data-status="in_progress"]/"active" above
   (this codebase's one color for "currently live/ongoing"), just as a
   dot instead of a filled chip since this sits in a page header, not a
   table cell. Pulse is opt-in via prefers-reduced-motion: no-preference -
   a reduced-motion visitor still sees the dot, just steady. */
.hl-live-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--text-muted);
}

.hl-live-dot {
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--badge-teal);
}

@media (prefers-reduced-motion: no-preference) {
  .hl-live-dot {
    animation: hl-live-pulse 2s ease-in-out infinite;
  }
}

@keyframes hl-live-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(64, 181, 158, 0.45); }
  50% { box-shadow: 0 0 0 4px rgba(64, 181, 158, 0); }
}

/* ─── Accessibility utility ──────────────────────────────────────── */
/* Visually hidden but still readable by screen readers - e.g. a label
   for an icon-only control, or context text a sighted user gets from
   surrounding layout alone. Moved here from the now-removed
   static/css/skeleton.css, which used to hold the announcement list's
   loading-skeleton pattern (templates/leagues/_announcement_list.html) -
   this was the one class from that file still in use elsewhere
   (templates/leagues/league_home.html, templates/games/season_calendar.html). */
.hl-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ─── Skip to content ────────────────────────────────────────────── */
/* Off-screen until keyboard-focused (Tab from a fresh page load lands
   here first) - lets keyboard/screen-reader users jump past the header
   nav straight to <main id="main-content"> instead of tabbing through
   every nav link on every page load. */
.skip-to-content {
  position: absolute;
  top: -100%;
  left: 8px;
  z-index: 100;
  padding: 10px 16px;
  background: var(--primary);
  color: var(--on-primary);
  border-radius: 0 0 8px 8px;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  transition: top 0.15s ease;
}
.skip-to-content:focus {
  top: 0;
}

/* ─── Empty state ────────────────────────────────────────────────── */
.hl-empty {
  text-align: center;
  padding: var(--space-8) var(--space-4);
  color: var(--text-muted);
}

.hl-empty .icon {
  display: block;
  margin: 0 auto var(--space-3);
  opacity: 0.6;
}

.hl-empty p {
  color: var(--text-muted);
  margin: 0 0 var(--space-4);
}

.hl-empty .hl-btn {
  margin-top: var(--space-2);
}

/* ─── Shared [data-lh-reveal] row/card stagger ──────────────────────
   league_home.css defines the base [data-lh-reveal] section-level
   fade+rise (static/js/league-home.js's IntersectionObserver toggles
   .lh-is-visible on it). This is the generic per-item version every
   OTHER converted page reaches for instead of writing its own copy: any
   direct table row inside `.hl-table-wrap`, direct child of
   `.hl-card-grid`, or row inside one of the hairline-ledger panels
   (`.gd-leaders-panel`/`.gd-awards-card`, Game Detail's own versions of
   League Home's `.lh-leaders-panel`) sitting inside a `[data-lh-reveal]`
   section gets the same fade+rise. Delay is `calc(var(--i, 0) * ...)` -
   standings.css's `.std-standings-table` established this pattern first
   (own row index set inline per <tr> from the loop counter) specifically
   because a fixed nth-child cutoff leaves a long list's later rows with
   no stagger at all; this shared rule reuses the same technique rather
   than reintroducing that cap. Pass `style="--i: {{ forloop.counter0
   }};"` on any row/card that can realistically run past 5-6 items (a
   roster, a full-width card grid); short, fixed-length lists (a 3-5 item
   leaders/awards strip) can skip it and every item just shares delay 0 -
   still fades in together, only the stagger itself is skipped. */
html.lh-anim-ready [data-lh-reveal] .hl-table-wrap tbody tr,
html.lh-anim-ready [data-lh-reveal] .hl-card-grid > *,
html.lh-anim-ready [data-lh-reveal] .gd-leaders-panel > *,
html.lh-anim-ready [data-lh-reveal] .gd-awards-card > * {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.4s ease, transform 0.4s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
  transition-delay: calc(var(--i, 0) * 0.04s);
}

html.lh-anim-ready [data-lh-reveal].lh-is-visible .hl-table-wrap tbody tr,
html.lh-anim-ready [data-lh-reveal].lh-is-visible .hl-card-grid > *,
html.lh-anim-ready [data-lh-reveal].lh-is-visible .gd-leaders-panel > *,
html.lh-anim-ready [data-lh-reveal].lh-is-visible .gd-awards-card > * {
  opacity: 1;
  transform: none;
}

/* ─── Header band (league/team/player identity) ─────────────────── */
.hl-header-band {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
  flex-wrap: wrap;
}

.hl-avatar {
  width: 64px;
  height: 64px;
  border-radius: 14px;
  object-fit: cover;
  background: var(--bg-card);
  border: 1px solid var(--border);
  flex-shrink: 0;
  transition: border-color 0.15s ease;
}

/* Smaller variant for inline use (table rows, list items) - same shape,
   scaled down radius to match. Pair with `.hl-avatar-placeholder` below
   when there's no image to show. */
.hl-avatar--sm {
  width: 36px;
  height: 36px;
  border-radius: 8px;
}

.hl-avatar-placeholder {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 8px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  color: var(--text-muted);
  flex-shrink: 0;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 13px;
}

/* Colored-initials monogram variant (apps.core.templatetags.badges'
   `initials`/`accent_rotate` filters) shown in place of a logo_url/
   photo_url that hasn't been uploaded - a deterministic rotation through
   the Supporting accents (docs/color-palette.md) so a list of rows reads
   as lively/distinct rather than a wall of identical gray circles. Team
   rows pass an inline `style` for Team.primary_color instead, when set,
   which wins by source order over these attribute rules. */
.hl-avatar-placeholder[data-accent="teal"] { background: rgba(47, 211, 196, 0.16); color: var(--teal); border-color: rgba(47, 211, 196, 0.35); }
.hl-avatar-placeholder[data-accent="violet"] { background: rgba(155, 123, 255, 0.16); color: var(--violet); border-color: rgba(155, 123, 255, 0.35); }
.hl-avatar-placeholder[data-accent="lime"] { background: rgba(182, 226, 74, 0.16); color: var(--lime); border-color: rgba(182, 226, 74, 0.35); }
.hl-avatar-placeholder[data-accent="gold"] { background: rgba(245, 195, 75, 0.16); color: var(--gold); border-color: rgba(245, 195, 75, 0.35); }
.hl-avatar-placeholder[data-accent="coral"] { background: rgba(255, 90, 77, 0.16); color: var(--alert); border-color: rgba(255, 90, 77, 0.35); }

/* Player identity link (avatar + name) used inline in table rows - e.g.
   the league players directory. Name stays the default text color until
   hover, unlike `.hl-link`, since it's the primary cell content rather
   than a secondary "view more" link. */
.hl-player-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text);
  font-weight: 600;
  text-decoration: none;
}

.hl-player-link:hover {
  color: var(--primary);
}

.hl-player-link:hover .hl-avatar,
.hl-player-link:hover .hl-avatar--sm {
  border-color: var(--primary);
}

/* ─── Clickable card (season/team cards etc.) ───────────────────── */
.hl-card-link {
  display: block;
  text-decoration: none;
  transition: border-color 0.15s ease;
}

.hl-card-link:hover {
  border-color: var(--primary);
}

/* Same hover feedback as .hl-card-link, for a plain (non-anchor) .hl-card
   that still has real interactive content inside it (e.g. the Awards
   Library grid - each card isn't itself a link, but often has one inside
   it) - a static card in a list of otherwise-hoverable ones reads as
   broken/inert without this. */
.hl-card-hoverable {
  transition: border-color 0.15s ease, transform 0.15s ease;
}

.hl-card-hoverable:hover {
  border-color: var(--primary);
  transform: translateY(-2px);
}

.hl-card-link .hl-badge {
  margin-bottom: var(--space-2);
}

.hl-card-link h3 {
  margin-top: var(--space-1);
}

/* ─── Sponsor card ───────────────────────────────────────────────── */
.hl-sponsor-card {
  display: flex;
  flex-direction: column;
  /* .hl-card-grid's default (grid) align-items: stretch doesn't reach this
     card - its own overflow: hidden below takes it out of the auto-row
     sizing pass that stretch depends on, so shorter cards in the same row
     just shrink to their own content instead of matching the tallest.
     An explicit height: 100% sidesteps that and fills the row directly. */
  height: 100%;
  padding: 0;
  overflow: hidden;
  transition: border-color 0.15s ease, transform 0.15s ease;
}

.hl-sponsor-card:hover {
  border-color: var(--border-strong);
  transform: translateY(-2px);
  box-shadow: 0 12px 28px -16px rgba(0, 0, 0, 0.5);
}

.hl-sponsor-logo-box {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 96px;
  padding: var(--space-4);
  background: var(--bg-raised);
  border-bottom: 1px solid var(--border);
}

.hl-sponsor-logo {
  max-height: 56px;
  max-width: 100%;
  object-fit: contain;
}

.hl-sponsor-logo-placeholder {
  color: var(--text-muted);
}

.hl-sponsor-card-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  flex: 1;
}

.hl-sponsor-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
}

.hl-sponsor-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.hl-sponsor-website-btn {
  margin-top: auto;
  padding: 8px 14px;
  font-size: 13px;
  justify-content: center;
}

/* ─── Sponsor summary stat panel (templates/leagues/league_sponsors.html)
   - a flexible-count sibling of League Home's own six-cell "at a Glance"
   .lh-glance-panel (static/css/league_home.css): that component's
   divider math assumes exactly 6 cells (nth-child(3n)/nth-last-child),
   which breaks for this page's variable cell count (1 total + up to 4
   tier cells + an optional "since" cell), so this is flexbox with a
   right-border-except-last-child rule instead of a fixed grid. ────────── */
.hl-sponsor-stats {
  display: flex;
  flex-wrap: wrap;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  margin-bottom: var(--space-5);
}

.hl-sponsor-stat {
  flex: 1 1 120px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: var(--space-4) var(--space-3);
  text-align: center;
  border-right: 1px solid var(--border);
}

.hl-sponsor-stat:last-child {
  border-right: none;
}

.hl-sponsor-stat-value {
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 700;
  color: var(--text);
  line-height: 1.15;
}

.hl-sponsor-stat-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-muted);
}

/* ─── "Become a Sponsor" CTA ─────────────────────────────────────── */
.hl-sponsor-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: var(--space-5);
}

/* ─── Scroll-reveal (see static/css/standings.css's identical comment -
   same shared [data-lh-reveal] mechanism, static/js/league-home.js,
   loads on every public page). Sponsors had no motion at all before -
   the plainest of the six public pages relative to the design language
   established elsewhere. */
html.lh-anim-ready [data-lh-reveal] .hl-sponsor-card {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.4s ease, transform 0.4s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}

html.lh-anim-ready [data-lh-reveal].lh-is-visible .hl-sponsor-card {
  opacity: 1;
  transform: none;
}

html.lh-anim-ready [data-lh-reveal] .hl-sponsor-stat {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.4s ease, transform 0.4s ease;
}

html.lh-anim-ready [data-lh-reveal].lh-is-visible .hl-sponsor-stat {
  opacity: 1;
  transform: none;
}

.hl-badge[data-tier="founding"] { background: rgba(245, 195, 75, 0.16); color: var(--gold); }
.hl-badge[data-tier="season"] { background: rgba(179, 169, 156, 0.2); color: var(--text-secondary); }
.hl-badge[data-tier="game-day"] { background: rgba(156, 67, 16, 0.24); color: var(--orange-deep); }

/* ─── Filter tabs (division filters, etc.) ──────────────────────── */
.hl-filter-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.hl-filter-tab {
  padding: 6px 14px;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  font-size: 12px;
  font-weight: 600;
  text-decoration: none;
  color: var(--text-secondary);
  transition: border-color 0.15s ease, color 0.15s ease;
}

.hl-filter-tab:hover {
  border-color: var(--primary);
  color: var(--text);
}

.hl-filter-tab[aria-current="page"] {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
}

.hl-filter-tab[aria-current="page"]:hover {
  color: var(--on-primary);
}

/* Optional per-tab count (Standings' division tabs, Sponsors' tier
   groups use the same "N items in this group" number in their own
   headings) - currentColor at reduced opacity so it reads correctly
   against both the default pill (muted text) and the solid
   aria-current="page" pill (on-primary text) without a second rule. */
.hl-filter-tab-count {
  opacity: 0.65;
  font-family: var(--font-mono);
}

/* ─── Filter bar (search input + dropdown filters, e.g. the players
   directory) - a plain GET form, no htmx/JS required; the position
   `<select>` auto-submits on change via a template-level `onchange` as
   progressive enhancement, the "Filter" button is the no-JS fallback ─── */
.hl-filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.hl-filter-bar-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-muted);
}

.hl-filter-bar-clear {
  align-self: center;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
  text-decoration: underline;
}

.hl-filter-bar-clear:hover {
  color: var(--primary);
}

.hl-input,
.hl-select {
  min-width: 200px;
  padding: 9px 12px;
  border-radius: 8px;
  border: 1px solid var(--border-strong);
  background: var(--bg-card);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.2;
}

.hl-input::placeholder {
  color: var(--text-muted);
}

.hl-input:focus,
.hl-select:focus {
  outline: 2px solid var(--primary);
  outline-offset: 1px;
  border-color: var(--primary);
}

/* Narrower variant for a control sitting inline inside a table row (e.g.
   the per-player award-assign form) instead of a standalone filter bar. */
.hl-input--inline,
.hl-select--inline {
  min-width: 0;
  width: auto;
  padding: 6px 8px;
  font-size: 13px;
}

.hl-inline-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

/* ─── Stat tiles (career/season averages) ───────────────────────── */
.hl-stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
  gap: var(--space-3);
}

.hl-stat-tile {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: var(--space-3);
  text-align: center;
}

.hl-stat-tile .value {
  display: block;
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 700;
  color: var(--text);
}

.hl-stat-tile .label {
  display: block;
  margin-top: 2px;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-muted);
}

/* ─── Announcement list ──────────────────────────────────────────── */
.hl-announcement-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.hl-announcement-list li {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* ─── Error pages (404/403/500) ─────────────────────────────────── */
.hl-error-page {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  max-width: 480px;
  min-height: 60vh;
  overflow: hidden;
}

/* Large background numeral watermark - templates/core/_ball_motif.html,
   reused here purely as decoration (context-free, so it's also safe on
   500.html which renders without a request). Sits behind the copy, never
   intercepts clicks/taps. */
.hl-error-page .hl-ball-motif {
  position: absolute;
  top: 50%;
  left: -60px;
  transform: translateY(-50%);
  color: var(--primary);
  opacity: 0.07;
  z-index: 0;
}

.hl-error-page > * {
  position: relative;
  z-index: 1;
}

.hl-error-page .hl-eyebrow {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 3px;
}

.hl-error-page h1 {
  margin: var(--space-2) 0 var(--space-3);
}

.hl-error-page p {
  margin-bottom: var(--space-5);
}

@media (max-width: 560px) {
  .hl-error-page .hl-ball-motif {
    left: -90px;
  }
}

/* ─── Site header (brand lockup) ─────────────────────────────────── */
.site-header {
  display: flex;
  align-items: center;
  padding: 20px clamp(20px, 5vw, 64px);
  border-bottom: 1px solid var(--border);
}

.site-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
}

/* templates/core/_marketing_page_header.html - the pre-league content pages
   (pricing/terms/privacy) that override .site-header's default brand-only
   layout to also carry a "Log in" entry point. Kept as a modifier on
   .site-header rather than changed there directly - the shared default
   (error pages, accounts/login.html) stays brand-only/centered. */
.site-header--marketing {
  justify-content: space-between;
}

.site-header-login-link {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  text-decoration: none;
  transition: color 0.2s ease;
}
.site-header-login-link:hover {
  color: var(--primary);
}

.site-brand-name {
  font-family: "Saira Condensed", sans-serif;
  font-weight: 700;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  font-size: 17px;
  color: var(--text);
}

.site-brand-name .accent {
  color: var(--primary);
}

/* ─── Beta label ──────────────────────────────────────────────────
   Appended next to every "Hooperslist" wordmark instance (site header,
   landing header/mobile-nav/footer, league sidebar nav) while the product
   is in beta - one shared class so removing beta-status later is a single
   edit, not a hunt through each template. Fixed px size (not em-scaled to
   the wordmark it sits beside) so it reads the same small "meta" chip at
   every size the wordmark appears at, from the 12px league sidebar brand
   up to the 22px landing header. Neutral/muted, not accent-colored - it's
   a status label, not a call to action competing with --primary. */
.hl-beta-tag {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  /* Nudged down from dead-center in the brand row - reads more like a
     badge tucked under the wordmark's baseline than a piece of the
     lockup competing with the icon for vertical center. */
  transform: translateY(3px);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--text-muted);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  padding: 2px 5px;
  line-height: 1;
  white-space: nowrap;
}

/* ─── Site footer ─────────────────────────────────────────────────
   Sitewide default (base.html's site_footer block) - shared by every
   page that doesn't override it: pricing/terms/privacy and every
   @public_league_view page (templates/base/league_public_base.html,
   which overrides site_header but deliberately leaves this one alone).
   Mirrors templates/core/landing.html's own bespoke footer's brand/
   bottom-bar structure (built from static/css/landing.css's
   .landing-footer* rules) minus its link columns (user request - Pricing/
   Log in duplicated nav already on every one of these pages, unlike the
   marketing homepage where the footer is the only nav a visitor scrolled
   past to reach) so a visitor still sees one consistent footer design
   site-wide instead of the marketing page's rich version next to a bare
   copyright line everywhere else. Kept as its own .site-footer-* rules
   rather than literally reusing .landing-footer* classes, since those
   only exist in landing.css (loaded on "/" alone) and this footer needs
   to render correctly on every page through base.css, which every page
   loads. */
.site-footer {
  border-top: 1px solid var(--border);
  padding: 48px clamp(20px, 5vw, 64px) 28px;
  background: linear-gradient(180deg, transparent, rgba(242, 106, 16, 0.05));
}

.site-footer-inner {
  max-width: 960px;
  margin: 0 auto;
}

.site-footer-brand-col { display: flex; flex-direction: column; gap: 10px; padding-bottom: 32px; }
.site-footer-brand-col .site-brand { pointer-events: none; }
.site-footer-tagline { color: var(--text-secondary); font-size: 14px; margin: 0; max-width: 32ch; }
.site-footer-domain { color: var(--text-muted); font-size: 12px; letter-spacing: 0.3px; font-family: var(--font-mono); }

.site-footer-bottom {
  border-top: 1px solid var(--border);
  padding-top: 20px;
  font-size: 13px;
  color: var(--text-muted);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 20px;
}

.site-footer-legal { display: flex; gap: 20px; }
.site-footer-legal a {
  color: var(--text-muted);
  text-decoration: none;
  transition: color 0.2s ease;
}
.site-footer-legal a:hover {
  color: var(--text);
}

/* Clears static/css/back-to-top.css's fixed button (right: 20px, 46px
   wide) from this bar's right-aligned legal links - same overlap
   landing.css's .landing-footer-bottom already guards against (see its
   own comment), generalized with :has() so it also covers every public
   league page, which gets its back-to-top button from the same
   core/_back_to_top.html include (templates/base/league_public_base.html)
   but didn't share this footer's markup until now. */
@media (max-width: 1180px) {
  body:has(.back-to-top) .site-footer-bottom {
    padding-right: 88px;
  }
}

/* ─── Basketball-seam watermark (templates/core/_ball_motif.html) ────
   Shared default for the reusable motif partial - callers layer their
   own positioning class (e.g. .lh-hero-badge, .hl-league-nav-identity
   -motif) on top; this just sets the baseline color/opacity/pointer-
   events so a bare {% include %} with no extra class still looks right. */
.hl-ball-motif {
  color: var(--text);
  opacity: 0.08;
  pointer-events: none;
}

/* ─── League nav (bottom tab bar on mobile, sidebar on desktop) ─────
   Reusable component - any league-scoped public page can
   {% include "leagues/_public_nav.html" with active="..." %} to get it,
   not just league_home. Mirrors reference/preview/dashboard.html's
   bottom-nav -> sidebar breakpoint (min-width: 900px), rebuilt with this
   project's own .hl-* naming/tokens instead of the mockup's classes. */
.hl-league-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  display: flex;
  align-items: stretch;
  gap: 2px;
  padding: 0 6px;
  background: var(--bg-raised);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom, 0);
  box-shadow: 0 -8px 24px -16px rgba(0, 0, 0, 0.6);
}

.hl-league-nav-brand {
  display: none;
}

/* Sidebar-only account row (logo/initials + name/role) - hidden below
   900px, where the bottom tab bar's own account entry point (the
   avatar/user-circle "More" toggle further down) already covers this
   and there's no room for a second one. Selector is the compound
   `.hl-league-nav-identity.hl-league-nav-profile-row` (matching
   leagues/_public_nav.html's masthead anchor, which carries both
   classes), not just `.hl-league-nav-identity` alone - specificity
   (0,2,0) has to beat `.hl-league-nav-profile-row`'s own unconditional
   `display: flex` (below) on every viewport width, not just win by
   coming later in the file, since the min-width: 900px override further
   down needs the same compound selector to flip it back on and that
   only works if both sides of the fight are equally specific. */
.hl-league-nav-identity.hl-league-nav-profile-row {
  display: none;
}

/* Sidebar identity row is a <button> now (2026-09-18 user request - it
   opens .hl-account-menu below instead of navigating straight to
   accounts:account-roles), not an <a> - needs the same plain-button
   reset every other button-shaped nav row in this file already carries
   (.hl-league-nav-more-toggle/.hl-league-nav-logout-btn) on top of
   .hl-league-nav-profile-row's own (tag-agnostic) visual styling. */
.hl-league-nav-identity-btn {
  width: 100%;
  background: none;
  border: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.hl-league-nav-item {
  position: relative;
  box-sizing: border-box;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 56px;
  padding: 8px 4px 10px;
  border-radius: 10px;
  color: var(--text-muted);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.15s ease, background-color 0.15s ease, transform 0.15s ease;
}

.hl-league-nav-item:active {
  color: var(--text);
  transform: scale(0.96);
}

.hl-league-nav-item .icon {
  stroke: currentColor;
}

/* Wraps each nav item's icon (apps.core.templatetags.icons `{% icon %}`)
   so the sidebar variant (media query below) can give it its own tinted
   background chip instead of a flat muted glyph - mirrors
   league_home.css's .lh-glance-icon tiles. Bare/unstyled at mobile
   widths (the bottom tab bar keeps today's plain currentColor icon) so
   this is purely additive there. */
.hl-league-nav-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: transform 0.15s ease;
}

.hl-league-nav-item .label {
  font-size: 11px;
  font-weight: 500;
  line-height: 1;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hl-league-nav-item.active {
  color: var(--primary);
}

.hl-league-nav-item.active .label {
  font-weight: 600;
}

/* Small dot under the active tab's icon (mobile bar only - the desktop
   sidebar gets its own left-edge accent bar instead, see the
   min-width: 900px block). Scales in from nothing so it reads as a
   marker landing on the current tab rather than a static bullet. */
.hl-league-nav-item.active::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 4px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--primary);
  transform: translateX(-50%) scale(0);
}

@media (prefers-reduced-motion: no-preference) {
  .hl-league-nav-item.active .hl-league-nav-icon {
    animation: hl-nav-icon-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
  }

  .hl-league-nav-item.active::after {
    animation: hl-nav-dot-pop 0.28s 0.05s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  }
}

/* Account/"You" toggle opts out of the shared active-tab icon pop above
   (user request - the scale-bounce read oddly applied to a person's
   avatar/initials rather than a plain glyph). Every other tab
   (League/Leaders/Players/Sponsors) keeps it; the dot marker below the
   tab is untouched, so becoming active still reads as a state change,
   just without the avatar itself bouncing. Same specificity as the rule
   above (two classes + descendant), so this only wins by coming later in
   the file - keep it directly after that block if this ever moves. */
.hl-league-nav-more-toggle.active .hl-league-nav-icon {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .hl-league-nav-item.active::after {
    transform: translateX(-50%) scale(1);
  }
}

@keyframes hl-nav-icon-pop {
  from { transform: scale(0.7); }
  to { transform: scale(1); }
}

@keyframes hl-nav-dot-pop {
  from { transform: translateX(-50%) scale(0); }
  to { transform: translateX(-50%) scale(1); }
}

/* "More" toggle + flyout - collapses Schedule/Standings out of the mobile
   tab row so it caps at 5 visible slots (League, Leaders, Players,
   Sponsors, More). Base rules here are the mobile shape; the popover
   positioning lives in the max-width: 899px block below, and the
   min-width: 900px block turns this back into an ordinary pair of sidebar
   rows for desktop, where there's no need to hide anything behind a
   toggle. */
.hl-league-nav-more {
  flex: 1;
  position: relative;
  display: flex;
}

.hl-league-nav-more-toggle {
  flex: 1;
  width: 100%;
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Log out row (2026-09-18 user request: a way to log out from the public
   league pages, not just the admin_kit console) - a <button> inside its
   own <form method="post"> (leagues/_public_nav.html), styled exactly
   like an .hl-league-nav-item <a> but needing the same plain-button reset
   .hl-league-nav-more-toggle above carries. Its own class (not a reuse of
   that one) since it isn't the row that opens the flyout, just an
   ordinary row inside it. */
.hl-league-nav-logout-btn {
  width: 100%;
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.hl-league-nav-more-toggle .hl-league-nav-icon {
  transition: transform 0.2s ease, color 0.15s ease;
}

.hl-league-nav-more-toggle.is-open {
  color: var(--text);
}

/* Scales the icon up rather than rotating it (user request - reads as
   the icon "expanding" to reveal the panel, tried a translateY lift
   first before landing here) when the flyout is open. Works for both
   toggle states: the anonymous user-circle glyph and the signed-in
   avatar/initials chip. */
.hl-league-nav-more-toggle.is-open .hl-league-nav-icon {
  transform: scale(1.15);
}

.hl-league-nav-more-panel {
  display: flex;
  flex-direction: column;
}

/* The "back to the platform" row breaks out of the league entirely, so
   it reads as a separate exit rather than one more league page in the
   same list - a hairline divider + extra top space, in both the mobile
   flyout and (once .hl-league-nav-more collapses to display: contents,
   see the min-width: 900px block) the desktop sidebar's plain list.
   Selector is scoped one level down from .hl-league-nav-item's own
   padding rules so it wins on specificity without !important. */
.hl-league-nav-more-panel .hl-league-nav-item.hl-league-nav-more-panel-exit {
  margin-top: 4px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

/* More-tab-as-account-entry (leagues/_public_nav.html, signed-in
   visitors only) - sizing only. --lg is the profile row's own larger
   size; the bare class is the More toggle's small in-tab size (holds
   the signed-in visitor's initials). Color is below, deliberately NOT
   the shared .hl-avatar-placeholder[data-accent] rotation - same "this
   one card gets the league's own colors instead of the directory-style
   rotation" call .hl-league-nav-identity above already makes for the
   league/team logo fallback, and for the same reason: "your account,
   in THIS league" should read as belonging to the league, not get a
   random teal/violet/lime/gold/coral tint. */
.hl-league-nav-avatar {
  width: 20px;
  height: 20px;
  font-size: 9px;
  border-radius: 6px;
}

.hl-league-nav-avatar--lg {
  width: 34px;
  height: 34px;
  font-size: 13px;
  border-radius: 8px;
}

/* Mobile bottom-tab "You"/account toggle only (templates/leagues/
   _public_nav.html's .hl-league-nav-more-toggle) - both its anonymous
   state (user-circle icon, size=22) and signed-in state (initials) share
   this size so the toggle doesn't visibly shrink the moment a visitor
   logs in (user report - it used to fall back to the bare 20px
   .hl-league-nav-avatar size once initials replaced the icon). Sized to
   hold the icon at size=22, the same glyph size every sibling tab's own
   `.hl-league-nav-icon` already renders at, so the account icon/avatar
   reads as the same visual weight as League/Leaders/Players/Sponsors
   instead of looking undersized next to them. 28px gives the 22px icon
   a few px of padding so it doesn't touch the chip's rounded edge. */
.hl-league-nav-avatar--icon {
  width: 28px;
  height: 28px;
  border-radius: 8px;
}

/* Real User.avatar_url image variant of the same chip - the placeholder
   rules above only cover the initials <span> (inline-flex centering comes
   from .hl-avatar-placeholder), so an <img> needs its own object-fit/
   flex-shrink to sit the same size in these flex rows without stretching. */
img.hl-league-nav-avatar {
  display: block;
  object-fit: cover;
  flex-shrink: 0;
}

/* Wins over .hl-avatar-placeholder[data-accent="..."] on specificity
   (two class/attribute selectors either way) via source order - this
   rule comes later. --primary-rgb/--primary/--on-primary come from
   apps.core.context_processors.public_league_theme (falls back to the
   sitewide court_orange palette when a league hasn't picked its own -
   see that processor's own docstring), so this tints itself to whatever
   league's page it's actually rendering on. */
.hl-avatar-placeholder.hl-league-nav-avatar {
  background: rgba(var(--primary-rgb), 0.16);
  color: var(--primary);
  border-color: rgba(var(--primary-rgb), 0.35);
}

/* Profile row leading the flyout/sidebar list for a signed-in visitor -
   own divider below it (mirrors .hl-league-nav-more-panel-exit's own
   divider above, just at the opposite end of the list: "this is a
   different kind of row" reads the same way whether it's the first row
   or the last). */
.hl-league-nav-profile-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  margin-bottom: 4px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border);
  border-radius: 8px;
  text-decoration: none;
  color: inherit;
  transition: background-color 0.15s ease;
}

.hl-league-nav-profile-row:hover {
  background: rgba(179, 169, 156, 0.08);
}

.hl-league-nav-profile-meta {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1;
}

.hl-league-nav-profile-meta .name {
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hl-league-nav-profile-meta .role {
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hl-league-nav-profile-chevron {
  flex-shrink: 0;
  display: inline-flex;
  color: var(--text-muted);
  /* chevron-left rotated to point right - chevron-right isn't in this
     project's vetted icon set, see leagues/_public_nav.html's own
     comment where this icon is used. */
  transform: rotate(180deg);
}

@media (prefers-reduced-motion: no-preference) {
  .hl-league-nav-profile-chevron {
    transition: transform 0.15s ease;
  }
}

/* Rotated further (pointing down) while the sidebar identity button's
   own .hl-account-menu dropdown is open - a plain affordance that this
   row now expands something, since it stopped being a plain link
   (2026-09-18 user request). */
.hl-league-nav-profile-chevron.is-open {
  transform: rotate(90deg);
}

/* Sidebar identity button's dropdown (2026-09-18 user request: click
   your name -> Edit my profile / My roles / Switch league / Log out,
   leagues/_public_nav_account_menu.html) - hidden below 900px by
   default (this trigger is itself hidden there too, see
   .hl-league-nav-identity.hl-league-nav-profile-row above; the "More"
   toggle covers the identical entry point at that width instead), shown
   as an ordinary vertical list at 900px+ inline in the sidebar flow
   (Alpine's x-show removes/restores this rule rather than setting its
   own inline `display: flex` - see that directive's own note in
   leagues/_public_nav.html). */
.hl-account-menu {
  display: none;
}

@media (min-width: 900px) {
  .hl-account-menu {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding-bottom: 8px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--border);
  }
}

/* Mobile "More" flyout's own copy of the same account items (leagues/
   _public_nav.html's `.hl-account-menu-mobile` wrapper) - `display:
   contents` so it doesn't add an extra flex box of its own inside the
   flyout's column layout, `display: none` at 900px+ so this copy
   disappears once the sidebar's own separate `.hl-account-menu` above
   is showing the identical content instead (`.hl-league-nav-more`
   collapsing to display:contents at that width would otherwise flatten
   THIS copy into the sidebar too, duplicating every row). The divider
   below it (separating the account group from Schedule/Standings/
   Awards/Showcase) is hidden at the same breakpoint for the same
   reason - nothing to divide once the group above it is gone. */
.hl-account-menu-mobile {
  display: contents;
}

.hl-league-nav-divider {
  height: 1px;
  flex-shrink: 0;
  background: var(--border);
  margin: 4px 10px 6px;
}

@media (min-width: 900px) {
  .hl-account-menu-mobile,
  .hl-league-nav-more-panel .hl-league-nav-divider {
    display: none;
  }
}

/* Alpine x-transition hooks for the flyout (see
   leagues/_public_nav.html) - fade + rise on open, reverse on close.
   Durations only apply under prefers-reduced-motion: no-preference so a
   reduced-motion visitor just gets an instant show/hide. */
.hl-nav-more-enter-start,
.hl-nav-more-leave-end {
  opacity: 0;
  transform: translateY(6px) scale(0.97);
}

.hl-nav-more-enter-end,
.hl-nav-more-leave-start {
  opacity: 1;
  transform: translateY(0) scale(1);
}

@media (prefers-reduced-motion: no-preference) {
  .hl-nav-more-enter {
    transition: opacity 0.16s ease, transform 0.16s ease;
  }

  .hl-nav-more-leave {
    transition: opacity 0.12s ease, transform 0.12s ease;
  }
}

/* Page content needs bottom clearance so the fixed tab bar never covers
   the last section - see .hl-league-content below. */
.hl-league-content {
  padding-bottom: 76px;
}

/* templates/base/base.html's global .site-footer is a sibling of <main>
   (.hl-league-shell renders INSIDE <main> - see base.html: <main>{% block
   content %}{% endblock %}</main><footer class="site-footer">...), so the
   footer directly follows <main>, not .hl-league-shell itself - hence
   :has() below rather than a plain adjacent-sibling selector.
   league_public_base.html only overrides site_header, not site_footer, to
   keep the copyright/tagline reachable - .hl-league-nav-brand above is
   display:none below 900px, so without this the footer is the only
   Hooperslist branding left on a mobile league page. Below 900px
   .hl-league-nav is position:fixed same as above, so it sits on top of
   whatever is at the true bottom of the page - .hl-league-content already
   reserves clearance for the page content itself, but the footer that
   follows it needs its own, identical reservation or the fixed tab bar
   covers it too. Scoped so only a public league page's footer is
   affected; the landing/marketing footer (<main> has no .hl-league-shell)
   is untouched. */
@media (max-width: 899px) {
  main:has(.hl-league-shell) + .site-footer {
    padding-bottom: 76px;
  }

  /* Popover anchored above the tab bar, right-aligned under the More
     button (the last slot in the row). x-cloak/x-show on the element
     itself (see leagues/_public_nav.html) handle show/hide; this is just
     position + card styling for once it's visible. */
  .hl-league-nav-more-panel {
    position: absolute;
    right: 8px;
    bottom: calc(100% + 10px);
    min-width: 190px;
    gap: 2px;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 6px;
    box-shadow: 0 12px 32px -12px rgba(0, 0, 0, 0.65);
    /* This list has grown (profile row + account group + Schedule/
       Standings/Teams/Awards/Showcase/Hooperslist) - uncapped, it ran off
       the top of the viewport on a short phone screen (user report). Cap
       + scroll internally instead of shrinking/removing any row. */
    max-height: min(70vh, 420px);
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  /* Rows inside the flyout read as a dropdown menu (icon beside label),
     not the tab bar's icon-over-label layout - same row shape the
     sidebar uses one breakpoint early, since this is already a vertical
     list here. */
  .hl-league-nav-more-panel .hl-league-nav-item {
    flex: none;
    flex-direction: row;
    justify-content: flex-start;
    min-height: 0;
    width: 100%;
    padding: 10px;
    border-radius: 8px;
    gap: 10px;
  }

  /* One plain neutral chip for every flyout icon - no per-item accent
     hue. Color is left to inherit (no `color` set here) so it still
     tracks the row's own state (muted at rest, primary when active). */
  .hl-league-nav-more-panel .hl-league-nav-icon {
    width: 30px;
    height: 30px;
    border-radius: 8px;
    background: rgba(179, 169, 156, 0.12);
  }

  .hl-league-nav-more-panel .hl-league-nav-icon .icon {
    width: 16px;
    height: 16px;
  }

  .hl-league-nav-more-panel .hl-league-nav-item .label {
    font-size: 13px;
    font-weight: 500;
    white-space: normal;
  }

  /* Row layout has no room for the tab bar's under-icon dot - the
     background tint below is indicator enough here. */
  .hl-league-nav-more-panel .hl-league-nav-item.active::after {
    display: none;
  }

  .hl-league-nav-more-panel .hl-league-nav-item:active {
    background: rgba(245, 241, 236, 0.06);
  }

  .hl-league-nav-more-panel .hl-league-nav-item.active {
    background: rgba(var(--primary-rgb), 0.12);
    color: var(--primary);
  }
}

@media (min-width: 900px) {
  .hl-league-shell {
    display: grid;
    /* Was 264px while the sidebar led with the league's own "Roster
       Card" masthead (crest above name, 800-weight/15px name needing
       more room than a flat row) - narrowed back down now that the top
       of the sidebar is the visitor's own account row instead (user
       request, see leagues/_public_nav.html's .hl-league-nav-identity
       comment), which is no wider than any other sidebar row and a
       compactness pass on top of that. */
    grid-template-columns: 232px 1fr;
    /* Was --space-6 (32px) - bumped for more breathing room between the
       sidebar and the page content on desktop (user-requested spacing
       pass), same reasoning as the .hl-page mobile padding bump below. */
    gap: var(--space-8);
    align-items: start;
    max-width: 1200px;
    margin: 0 auto;
    padding: var(--space-7) clamp(20px, 4vw, 64px);
  }

  .hl-league-nav {
    position: sticky;
    top: var(--space-5);
    left: auto;
    right: auto;
    bottom: auto;
    z-index: auto;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    background: var(--bg-raised);
    border: 1px solid var(--border);
    border-radius: 12px;
    /* Vertical padding trimmed from --space-4 (16px) - compactness pass
       (user-requested); horizontal padding is left at --space-3 so
       .hl-league-nav-item.active::before's `left: -13px` (below) still
       lands on the container's own edge without recalculating it. */
    padding: var(--space-3) var(--space-3);
  }

  /* Hidden on the sidebar for now (user request) - the identity card
     right below already gives this rail a clear "whose sidebar is this"
     anchor, and the "Hooperslist" exit link at the bottom of the item
     list (leagues/_public_nav.html's flyout-exit row, which collapses
     into ordinary flow here per .hl-league-nav-more below) still gets a
     visitor back to the platform landing page, so removing this top row
     doesn't remove that path - only this second, redundant one. Mobile's
     own `.hl-league-nav-brand { display: none; }` (component rules
     above) is untouched; this only overrides the desktop sidebar. Rest
     of the rule is left as-is so reverting is just this one property. */
  .hl-league-nav-brand {
    display: none;
    align-items: center;
    gap: 8px;
    padding: 0 8px;
    margin-bottom: var(--space-3);
    text-decoration: none;
    opacity: 0.7;
  }

  .hl-league-nav-brand-name {
    font-family: var(--font-heading);
    font-weight: 700;
    letter-spacing: 1.2px;
    text-transform: uppercase;
    font-size: 12px;
    color: var(--text);
  }

  .hl-league-nav-brand-name .accent {
    color: var(--primary);
  }

  /* Sidebar-only account entry (replaces the old "Roster Card" league
     masthead - user request: the sidebar should lead with the visitor's
     own account, not the league's logo/name/season status; the League
     nav item below and every page's own header still carry which
     league this is, so that context isn't actually lost by dropping it
     from here too). All the visual styling - avatar, name/role, hover
     state, bottom divider - comes from the shared `.hl-league-nav-
     profile-row` class also applied to this element in
     leagues/_public_nav.html (same markup as the flyout's own copy of
     this row, just promoted to the top of the sidebar); this override
     only needs to flip the component rules' mobile-hidden default back
     to visible at this breakpoint - same compound selector as that rule
     (see its own comment for why the plain class alone isn't enough). */
  .hl-league-nav-identity.hl-league-nav-profile-row {
    display: flex;
  }

  /* Row padding/gap trimmed from 10px 12px / 12px - compactness pass
     (user-requested); the active-bar's `left: -13px` below is relative
     to this row's own box, not its padding, so it still lands on the
     sidebar container's edge unchanged. */
  .hl-league-nav-item {
    flex-direction: row;
    justify-content: flex-start;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: 8px;
  }

  .hl-league-nav-item .label {
    font-size: 13px;
    font-weight: 500;
  }

  /* One plain neutral chip for every sidebar icon (sidebar only) - no
     per-destination accent hue. `color` is left unset so the glyph
     inherits the row's own state color (muted at rest, primary when
     active, text on hover) instead of a fixed tint. */
  .hl-league-nav-icon {
    width: 28px;
    height: 28px;
    border-radius: 8px;
    background: rgba(179, 169, 156, 0.12);
    transition: background-color 0.15s ease;
  }

  .hl-league-nav-icon .icon {
    width: 16px;
    height: 16px;
  }

  .hl-league-nav-item:hover {
    background: rgba(245, 241, 236, 0.04);
    color: var(--text);
    transform: translateX(2px);
  }

  /* Chip background itself also lifts on hover/active - still no
     per-destination hue (see comment above), just the same state color
     the row already uses, so the chip doesn't stay visually inert while
     everything else about the row responds. */
  .hl-league-nav-item:hover .hl-league-nav-icon {
    background: rgba(245, 241, 236, 0.1);
  }

  /* Active row carries the emphasis alone now (Roster Card review pass) -
     a filled icon chip + glow instead of a tinted one, so the row reads
     as "on" rather than just "slightly warmer than the others". Label
     goes to --text/700 rather than --primary: the icon chip is now the
     one primary-colored element in the row, and doubling the accent onto
     the text as well read as redundant next to it. */
  .hl-league-nav-item.active {
    background: rgba(var(--primary-rgb), 0.16);
    color: var(--text);
    font-weight: 700;
  }

  .hl-league-nav-item.active .hl-league-nav-icon {
    background: var(--primary);
    box-shadow: 0 4px 14px -4px rgba(var(--primary-rgb), 0.8);
  }

  .hl-league-nav-item.active .hl-league-nav-icon .icon {
    color: var(--on-primary);
  }

  /* Mobile's active-tab dot (base rules above) gives way to a left-edge
     accent bar here - more legible against a full-width row than a small
     dot would be. */
  .hl-league-nav-item.active::after {
    display: none;
  }

  .hl-league-nav-item.active::before {
    content: "";
    position: absolute;
    left: -13px;
    top: 50%;
    width: 3px;
    height: 60%;
    border-radius: 0 3px 3px 0;
    background: var(--primary);
    transform: translateY(-50%) scaleY(0);
    transform-origin: center;
  }

  @media (prefers-reduced-motion: no-preference) {
    .hl-league-nav-item.active::before {
      animation: hl-nav-bar-grow 0.22s ease forwards;
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .hl-league-nav-item.active::before {
      transform: translateY(-50%) scaleY(1);
    }
  }

  @keyframes hl-nav-bar-grow {
    from { transform: translateY(-50%) scaleY(0); }
    to { transform: translateY(-50%) scaleY(1); }
  }

  /* The mobile More toggle/flyout collapses back into two ordinary
     sidebar rows here - there's no space constraint on desktop, so
     nothing should be hidden behind a click. display: contents drops
     .hl-league-nav-more itself from the box tree so its panel's children
     (Schedule/Players) sit directly in .hl-league-nav's own column flow,
     spaced by that column's existing gap instead of a nested box. */
  .hl-league-nav-more {
    display: contents;
  }

  .hl-league-nav-more-toggle {
    display: none;
  }

  .hl-league-nav-more-panel {
    /* leagues/_public_nav.html's x-show sets an inline display: none for
       the closed (mobile-default) state - !important is required to win
       over that inline style so the panel always renders at this
       breakpoint. */
    display: flex !important;
    flex-direction: column;
    gap: 4px;
  }

  /* This flyout row is now a duplicate at this breakpoint - the sidebar's
     own `.hl-league-nav-identity` masthead (above) already shows this
     same avatar+name+role (it shares this exact class), promoted to the
     top of the sidebar instead of buried below Sponsors. Mobile's flyout
     copy is untouched; this only hides the one inside the now always-
     visible .hl-league-nav-more-panel. */
  .hl-league-nav-more-panel .hl-league-nav-profile-row {
    display: none;
  }

  .hl-league-content {
    padding-bottom: 0;
  }
}

/* Tablet/large-phone pass (481-899px, below the sidebar breakpoint but
   above the tightened <=480px pass below) - a touch more vertical
   breathing room than the plain .hl-page default, per the user-requested
   mobile spacing pass. */
@media (max-width: 899px) {
  .hl-page {
    padding-top: var(--space-7);
    padding-bottom: var(--space-7);
  }
}

/* ─── Mobile pass (schedule/standings/box scores are the pages most
   likely opened one-handed from a gym sideline - docs/
   ui_content_recommendations.md §11) ─────────────────────────────── */
@media (max-width: 480px) {
  .hl-page {
    padding-top: var(--space-6);
    padding-bottom: var(--space-6);
  }

  .hl-header-band {
    flex-direction: column;
    align-items: flex-start;
  }

  .hl-section-head {
    flex-direction: column;
    align-items: stretch;
  }

  .hl-section-head .hl-btn,
  .hl-section-head .hl-share-group {
    align-self: flex-start;
  }

  .hl-card-grid {
    grid-template-columns: 1fr;
  }

  .hl-table th,
  .hl-table td {
    padding: 10px;
  }

  .hl-filter-bar {
    flex-direction: column;
    align-items: stretch;
  }

  .hl-input,
  .hl-select {
    min-width: 0;
    width: 100%;
  }
}

/* ─── Stackable table (card layout on narrow screens) ───────────────
   Opt-in via `.hl-table--stack` on `.hl-table` - the schedule page uses
   this since its rows carry 5 columns (date, matchup, venue, status,
   score) that get unreadably cramped in a horizontally-scrolled table
   on a phone. Each `<td>` needs a `data-label` attribute; `<thead>` is
   hidden (not removed, so screen readers relying on table semantics as
   plain text still get the labels via the visible `data-label`
   equivalents rendered per cell). ─────────────────────────────────── */
@media (max-width: 640px) {
  .hl-table--stack thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
    padding: 0;
    margin: -1px;
  }

  .hl-table--stack,
  .hl-table--stack tbody,
  .hl-table--stack tr,
  .hl-table--stack td {
    display: block;
    width: 100%;
    box-sizing: border-box;
  }

  .hl-table--stack tbody tr {
    padding: var(--space-2) var(--space-4);
    border-bottom: 1px solid var(--border);
  }

  .hl-table--stack tbody tr:last-child {
    border-bottom: none;
  }

  .hl-table--stack .hl-table-group-row {
    padding: 0;
  }

  .hl-table--stack .hl-table-group-row td {
    padding: 6px var(--space-4);
  }

  .hl-table--stack .hl-table-group-row td::before {
    content: none;
  }

  /* Label + value stay in normal inline flow (not flex) so a long value
     (full date/time, venue name) wraps onto a second line instead of
     overflowing the card and scrolling the rest of the row out of view. */
  .hl-table--stack td {
    padding: 3px 0;
    border-bottom: none;
    text-align: left;
  }

  .hl-table--stack td::before {
    content: attr(data-label) ": ";
    font-family: var(--font-body);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    color: var(--text-muted);
  }

  /* A cell with no meaningful column header (e.g. a trailing "view more"
     link column) opts out of the label prefix entirely via data-label=""
     rather than showing a stray ": ". */
  .hl-table--stack td[data-label=""]::before {
    content: none;
  }

  .hl-table--stack .hl-schedule-matchup {
    padding-top: 4px;
    padding-bottom: 4px;
  }

  .hl-table--stack .hl-schedule-matchup::before {
    content: none;
  }

  .hl-table--stack .hl-schedule-team {
    font-family: var(--font-heading);
    font-size: 15px;
  }

  /* ─── Schedule-specific card polish - .hl-schedule-table is only ever
     used on games/season_calendar.html (see its own comment above), so
     these rules layer on top of the generic .hl-table--stack treatment
     above without touching any other page that opts into stacking
     (players directory, head-to-head, season detail, bracket tables).
     Turns each row from a plain label/value list bounded by a hairline
     into an actual bordered card, matching how the calendar-grid view
     of this same page already renders a game as a discrete chip. */
  .hl-schedule-table.hl-table--stack tbody tr:not(.hl-table-group-row) {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 12px;
    margin-bottom: var(--space-2);
  }

  .hl-schedule-table.hl-table--stack tbody tr:not(.hl-table-group-row):last-child {
    margin-bottom: 0;
  }

  /* Status + Score read as one scoreboard line instead of two stacked
     "label: value" rows - both are short enough to share a line even on
     a narrow phone, and putting the score next to the status it belongs
     to (won/lost against what) reads faster than two separate lines.
     `width: auto` matters as much as `display` here - the shared
     `.hl-table--stack td` rule above sets `width: 100%` on every cell,
     and an inline-block box at 100% width still claims the whole row
     (same bug as the icon cell above, hit twice in the same file): Score
     was dropping to its own line anyway and, on a highlighted next-game
     row, rendering as a second, misaligned tinted box overlapping the
     card's rounded bottom edge instead of sitting inline with Status. */
  .hl-schedule-table.hl-table--stack td[data-label="Status"],
  .hl-schedule-table.hl-table--stack td[data-label="Score"] {
    display: inline-block;
    width: auto;
  }

  .hl-schedule-table.hl-table--stack td[data-label="Score"] {
    margin-left: var(--space-3);
  }

  /* Add-to-calendar pins to the card's top-right corner instead of
     sitting as its own trailing line at the card's bottom - the row is
     already `position: relative` (.hl-table--row-link, which this table
     carries), and the icon link itself already carries
     .hl-table-cell-interactive for its own stacking context above the
     row's stretched-link overlay (base.css's .hl-table--row-link rules
     above), so this is a pure position change, no z-index of its own
     needed. Empty on a completed/forfeited/cancelled row (the template
     only renders the icon for scheduled/in_progress games) - an empty
     absolutely-positioned cell takes up no visual space. */
  .hl-schedule-table.hl-table--stack td[data-label=""] {
    position: absolute;
    top: var(--space-2);
    right: var(--space-3);
    /* The shared `.hl-table--stack td` rule above sets `width: 100%` for
       every cell and isn't overridden just by this rule declaring
       `position` - without resetting it here too, this cell stays full
       row width and its icon (shrink-to-fit inline content) renders at
       that box's left edge instead of the top-right corner the
       position:absolute + right offset was meant to place it at. */
    width: auto;
    padding: 0;
  }

  /* Next-game highlight, mobile card version - tints the card (the row
     itself) rather than each `<td>` (see the desktop rule's own comment
     for why per-cell backgrounds don't work once cells stop spanning the
     card's full width). Every cell's own background is cleared so the
     card's single fill shows through uniformly, including the icon
     cell - it's `position: absolute` with no explicit background of its
     own, so without this it was still picking up the desktop rule's
     `.hl-schedule-row--next td` color and rendering as a small separately
     -tinted square floating behind the calendar icon instead of blending
     into the card. No left accent bar here - see the desktop block's
     comment on why that's desktop-only. */
  .hl-schedule-table.hl-table--stack tbody tr.hl-schedule-row--next {
    background: rgba(var(--primary-rgb), 0.09);
  }

  .hl-schedule-table.hl-table--stack tbody tr.hl-schedule-row--next td {
    background: none;
  }
}
