/* scores/polish.css — entrance + interaction animations for /scores
 * Presentation-only. Scoped to html[data-route="/scores"].
 * /rankings redirects permanently to /scores (nginx.conf).
 * Reduced-motion fallbacks included.
 * G8b fix / fix/iter2-closers 2026-06-10
 * Player-rankings panel + its pr-* entrance/skeleton rules removed 2026-07-16
 * (Pat: "looks like a player's ranking thing, which we don't need" — panel
 * deleted from scores/index.html, this file's dead pr-* selectors with it).
 */

/* ── Button hover / active / focus-visible ────────────────── */
html[data-route="/scores"] .btn,
html[data-route="/scores"] .press-btn {
  transition: transform .14s ease, box-shadow .14s ease;
}
html[data-route="/scores"] .btn:hover,
html[data-route="/scores"] .press-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 5px 16px rgba(255, 26, 26, .22);
}
html[data-route="/scores"] .btn:active,
html[data-route="/scores"] .press-btn:active {
  transform: translateY(0);
}
html[data-route="/scores"] .btn:focus-visible,
html[data-route="/scores"] .press-btn:focus-visible {
  outline: 2px solid var(--gold-accent, var(--accent,#FF2D2D));
  outline-offset: 3px;
}

/* ── Reduced-motion overrides ─────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  html[data-route="/scores"] .btn:hover,
  html[data-route="/scores"] .press-btn:hover {
    transform: none;
  }
}

/* ── AP TOP 25 = mini scores-tiles (Pat 2026-07-01) ─────────────────────────
   Team + logo LEFT on the school-color L→R gradient (like the game tiles); rank ·
   movement · record on the RIGHT. Movement keeps green-up / red-down (sanctioned
   exception per Pat). Lives here (loaded last, stamped) to override the bundled
   core.css AP rules — direct core.css edits are CloudFlare-masked (unstamped). */
html[data-route="/scores"] .ap-row {
  display: flex !important;
  grid-template-columns: none !important;
  align-items: center; gap: 10px;
  padding: 9px 11px; margin: 4px 0; border-radius: 9px;
  border-top: 0 !important; position: relative; overflow: hidden;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--team, #3A3A3A) 78%, #000),
    color-mix(in srgb, var(--team, #3A3A3A) 26%, #0a0a0a)) !important;
}
html[data-route="/scores"] .ap-row::before { display: none !important; }
html[data-route="/scores"] .ap-team { order: 0; flex: 1 1 auto; display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
html[data-route="/scores"] .ap-logo { width: 22px !important; height: 22px !important; }
/* CLS fix (FISHING-LEDGER #4 / LANE 2): the AP Top 25 is ALWAYS exactly 25
   rows once loaded, but the list starts with 3 skeleton rows — reserve the
   full 25-row height up front (row box ~43px incl. margin, computed against
   the .ap-row rule above) so swapping skeleton->real content doesn't balloon
   the rail height after paint. A short degraded ("Rankings unavailable")
   state just leaves blank space below one row rather than shifting layout. */
html[data-route="/scores"] .ap-list { min-height: 1080px; }
html[data-route="/scores"] .ap-name {
  font-family: var(--f-display, "Graduate", sans-serif); font-weight: 700; font-size: 13px;
  color: #F5F5F5 !important; text-shadow: 0 1px 2px rgba(0,0,0,.55);
}
html[data-route="/scores"] .ap-rank { order: 1; color: #F5F5F5 !important; text-shadow: 0 1px 2px rgba(0,0,0,.55); min-width: 20px; text-align: center; }
html[data-route="/scores"] .ap-move { order: 2; text-shadow: 0 1px 2px rgba(0,0,0,.55); }
html[data-route="/scores"] .ap-rec { order: 3; color: rgba(255,255,255,.92) !important; text-shadow: 0 1px 2px rgba(0,0,0,.55); white-space: nowrap; }
html[data-route="/scores"] .ap-pts { display: none !important; }

/* ── My Teams empty-state CTA (DEAD-14) ───────────────────────────────────
   core.css's .sb-empty is plain text-only, dashed-border, no way forward
   when the user has zero favorited teams. Lives here (loaded last, stamped)
   so it actually reaches the CDN — direct core.css edits are CloudFlare-
   masked (unstamped), per the AUD-24 note above. */
html[data-route="/scores"] .sb-empty-msg {
  color: var(--white-bright, #F5F5F5);
  font: 700 14px/1.4 var(--f-body, sans-serif);
}
html[data-route="/scores"] .sb-empty-sub {
  margin: 6px 0 14px;
}
html[data-route="/scores"] .sb-empty-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

/* ── Empty section collapses onto its heading line (Brock 2026-07-27) ──────
   Off-season / pre-kickoff there are no live games and no finals, and
   core.css's .sb-empty painted two full dashed cards — 22px padding, its own
   heading, plus the 18px .sb-main gap, twice — roughly 300px of dead space
   above the first real tile. script.js now marks an empty slot's section
   .is-empty and the section collapses to a single row: the message sits
   inline after the title, no card, no dashed border. The text stays; only the
   box goes. "My Teams" is deliberately excluded — renderFavoritesSlot() never
   sets .is-empty, so its empty state keeps the room its Browse Teams CTA needs.
   :not([hidden]) so core.css's .sb-section[hidden]{display:none} still wins
   when a filter chip hides the section — that rule sits inside @layer
   components (core.css:1125), so an unlayered display:flex here would
   otherwise beat it on cascade order and un-hide a filtered-out section. */
html[data-route="/scores"] .sb-section.is-empty:not([hidden]) {
  display: flex;
  /* center, not baseline: sharing a baseline between 12px body text and the
     ~23px Graduate title dropped the message 4.8px below the title's optical
     centre and read as misaligned. Centring the boxes zeroes that, and keeps
     working when the message wraps to two lines on a narrow viewport. */
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
html[data-route="/scores"] .sb-section.is-empty .sb-section-head {
  margin-bottom: 0;
}
html[data-route="/scores"] .sb-section.is-empty .sb-games {
  display: block;          /* drop the tile grid — there are no tiles to lay out */
  flex: 1 1 12rem;
  min-width: 0;
}
html[data-route="/scores"] .sb-section.is-empty .sb-empty {
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  /* Centred in the space the heading leaves, not left-hugging it. Only the
     message moves: the headings stay left where they are, and text-align
     changes no box geometry, so every heading keeps its exact position and
     the row heights are unchanged. Centring across the FULL section width
     instead would need the message to overlap the heading's cell, which
     collides at narrow widths — this stays safe when the message wraps. */
  text-align: center;
  font: 500 12px/1.4 var(--f-body, sans-serif);
  color: var(--gray-light, #C2C2C2);
}

/* ── Card status chip is redundant inside its own section (Brock 2026-07-27) ─
   Every tile under "Upcoming" wore an UPCOMING chip, every tile under "Live" a
   LIVE chip — the section heading directly above already said it. Hide the
   chip in the three state-named sections; on tiles whose meta line is long
   (the Aer Lingus Classic) that also buys back the line it forced to wrap.
   Clipped rather than display:none so the state still reaches screen readers —
   the tile's own accessible name is "<away> at <home> — open details" and
   carries no game state. "My Teams" interleaves live and upcoming games in one
   list, so its tiles keep the visible chip; there it is the only state cue.
   Scoped under .sb-section, so the game-detail <dialog>'s own LIVE/FINAL/
   UPCOMING pills (script.js ~779/877/957) are untouched. */
html[data-route="/scores"] .sb-section[data-section="live"] .gt-pill,
html[data-route="/scores"] .sb-section[data-section="final"] .gt-pill,
html[data-route="/scores"] .sb-section[data-section="upcoming"] .gt-pill {
  position: absolute;
  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;
  box-shadow: none;
  animation: none;         /* no live-pulse on a 1px invisible node */
}

/* ── Mobile SCORES | AP TOP 25 toggle (Pat 2026-07-16) ───────────────────────
   The AP rail used to stack below the whole scoreboard on mobile and get
   buried. The existing top-of-page tab bar is now a MOBILE-ONLY toggle
   between the scoreboard and the AP rail so the rail is one tap away.
   Desktop keeps the current two-column layout with NO tab bar at all — the
   break here matches core.css's own .sb-layout stack point (max-width:960px,
   core.css:1595), not the generic sitewide 880px chrome breakpoint, so there
   is no dead zone between the two where the rail is stacked-below but the
   tab bar to reach it is hidden.
   One .ap-list DOM node — this is a pure CSS show/hide keyed off
   [data-mobile-view] on #tab-scores (see scores/script.js); no duplicate
   list, no second fetch. Lives here (loaded last, stamped) so it beats
   core.css's @layer components rules on cascade order alone. */
@media (min-width: 961px) {
  html[data-route="/scores"] .sc-page-tabs { display: none; }
}
@media (max-width: 960px) {
  /* default view (no attribute, or explicit "scores") = scoreboard */
  html[data-route="/scores"] #tab-scores[data-mobile-view="ap"] .sb-filterbar,
  html[data-route="/scores"] #tab-scores[data-mobile-view="ap"] .sb-main {
    display: none;
  }
  html[data-route="/scores"] #tab-scores:not([data-mobile-view="ap"]) .sb-rail {
    display: none;
  }
}

/* ── Desktop AP rail: independent scroll window (Pat 2026-07-16) ────────────
   core.css makes .sb-rail position:sticky; top:64px above 960px but sets no
   height cap, so the list runs past the viewport and the user has to scroll
   the whole page to reach the bottom of the poll. Cap the rail to the
   viewport and give it its own scroll container so a mouse-wheel over the
   rail moves the list, not the page, while every entry stays reachable. */
@media (min-width: 961px) {
  html[data-route="/scores"] .sb-rail {
    max-height: calc(100vh - 64px - 24px);
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--gray-border, #2A2A2A) transparent;
  }
  html[data-route="/scores"] .sb-rail::-webkit-scrollbar { width: 8px; }
  html[data-route="/scores"] .sb-rail::-webkit-scrollbar-track { background: transparent; }
  html[data-route="/scores"] .sb-rail::-webkit-scrollbar-thumb {
    background: var(--gray-border, #2A2A2A);
    border-radius: 4px;
  }
  html[data-route="/scores"] .sb-rail::-webkit-scrollbar-thumb:hover {
    background: var(--gray-light, #C2C2C2);
  }
}

/* ── Mobile CONF selector: own row (SITE-AUDIT-2026-07-02, AUD-24) ──────────
   At <=620px the Conf <select> shared the sticky filter row with the
   scrollable filter-chip strip + week-nav and got squeezed to ~20px wide,
   clipped by the row's overflow-hidden guard (core.css:1552-1564 — rect
   check showed .sb-conf width:20 vs the bar's clip edge). Give it its own
   full-width row below the chip strip so it never competes for space.
   Lives here (loaded last, stamped) — direct core.css edits are
   CloudFlare-masked (unstamped); core.css's .sb-filterbar rules are inside
   @layer components (core.css:1125), so this unlayered override wins on
   cascade order alone (core.css:941), no !important needed for that part. */
@media (max-width: 620px) {
  html[data-route="/scores"] .sb-filterbar {
    flex-wrap: wrap;
    overflow-x: visible;
  }
  html[data-route="/scores"] .sb-filters { order: 1; }
  html[data-route="/scores"] .sb-week { order: 2; }
  html[data-route="/scores"] .sb-conf-wrap {
    order: 3;
    flex: 1 1 100%;
    max-width: none;
    margin-left: 0;
    margin-top: 2px;
  }
  html[data-route="/scores"] .sb-conf {
    flex: 1 1 auto;
    min-width: 0;
    max-width: none;
    width: 100%;
    min-height: var(--sns-touch-target, 44px);
  }
}

/* ── Q-EXP-5 (D-EXPCARD-0716 §2b): #game-modal → shared quick-look visual
   contract. Restyle-in-place: native <dialog> + live-poll behavior untouched
   (CSS only). Base gm-* rules live in core.css (CF-masked for /scores), so
   contract overrides belong here in the stamped bundle. Tokens mirror
   /assets/exp-card.css: scrim rgba(0,0,0,.6)+blur(4px), ✕ ≥44px pinned,
   border/radius/sizing. */
.game-modal {
  width: min(760px, 92vw);
  max-width: none;
  max-height: 86vh;
  padding: 0;
  background: var(--bg-surface, #0A0A0A);
  border: 1px solid var(--gray-border, #2A2A2A);
  border-radius: 12px;
  box-shadow: 0 16px 56px rgba(0,0,0,.75);
  overflow: hidden;               /* .gm-body is the scroll region */
}
.game-modal::backdrop {
  background: rgba(0,0,0,.6);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
.gm-head { flex: 0 0 auto; }      /* ✕ row pinned; never scrolls */
.gm-close {
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  flex: 0 0 auto;
  background: rgba(255,0,0,.15);
  border: 1px solid rgba(255,0,0,.5);
  border-radius: 8px;
  color: var(--white-bright, #F5F5F5);
  font-size: 18px;
  line-height: 1;
}
.gm-close:hover { background: rgba(255,0,0,.35); color: var(--white-bright, #F5F5F5); }
.gm-body { flex: 1 1 auto; min-height: 0; overscroll-behavior: contain; }
@media (max-width: 880px) {
  .game-modal {
    width: min(92vw, 420px);
    max-height: 78dvh;
    border-radius: 16px;
  }
}

/* ---- Modal CTA: "View full game" -> /game/<id> ------------------------- *
   Rendered only for games that actually have a detail page (see the
   resolvability index in scores/script.js), so this is absent rather than
   dead for the FCS / non-FBS events ESPN lists that we don't carry. */
.gm-foot { flex: 0 0 auto; }
.gm-foot:empty { display: none; }
.gm-foot .gm-cta-wrap { padding: 14px 18px; border-top: 1px solid var(--gray-border, #2A2A2A); }
.gm-cta-wrap:empty { display: none; }
.gm-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 12px 18px;
  border: 1px solid var(--gray-border, #2A2A2A);
  border-radius: 10px;
  background: var(--black-soft, #111);
  color: var(--white-bright, #F5F5F5);
  font: 700 13px/1 var(--f-body, sans-serif);
  letter-spacing: .08em;
  text-transform: uppercase;
  text-decoration: none;
  transition: border-color .15s, background .15s, transform .1s;
}
.gm-cta:hover,
.gm-cta:focus-visible {
  border-color: var(--red-primary, #FF1A1A);
  background: color-mix(in srgb, var(--red-primary, #FF1A1A) 10%, var(--black-soft, #111));
}
.gm-cta:active { transform: scale(.98); }
.gm-cta:focus-visible { outline: 2px solid var(--red-primary, #FF1A1A); outline-offset: 2px; }
.gm-cta-arrow { font-size: 15px; line-height: 1; transition: transform .15s; }
.gm-cta:hover .gm-cta-arrow { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .gm-cta, .gm-cta-arrow { transition: none; }
  .gm-cta:hover .gm-cta-arrow { transform: none; }
}
