@import url('https://fonts.googleapis.com/css2?family=Audiowide&display=swap');

:root {
  --ink: #141414;
  --red: #d61f2c;
  --red-dark: #a3161f;
  --blue: #2563eb;
  --blue-dark: #1741a6;
  --bg: #f5f5f4;
  --card: #ffffff;
  --text: #1a1a1a;
  --muted: #6b6b6b;
  --border: #e3e2e0;
  --radius: 10px;
}

* { box-sizing: border-box; }

/* Scales every rem-sized font on the site (the vast majority of them) up by
   1.2x in one place, rather than hand-editing every individual font-size
   declaration — 1rem is normally the browser default (16px); this makes it
   19.2px, and everything sized in rem follows proportionally. */
html {
  font-size: 120%;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
}

/* Locked to the top of the viewport at all times (scrolling up or down
   never carries it off-screen) — logo/search/Teams-Events nav stay
   reachable no matter how far down the page a visitor has scrolled.
   .page-filters below stacks directly beneath it (see its own comment)
   rather than at the very top of the viewport, using --sticky-header-height
   (set from this element's actual rendered height — see the resize/load
   listener in app.js) so the two never overlap. */
header.site-header {
  background: var(--ink);
  color: #fff;
  position: sticky;
  top: 0;
  z-index: 25;
}

/* The bar itself stays full-width (dark background edge to edge), but its
   contents are constrained to the same max-width as <main>/<footer> below
   (see main{} / footer{}) and centered the same way, so the logo/search/nav
   line up with the page content instead of hugging the browser's edges. */
.site-header-inner {
  max-width: 1000px;
  margin: 0 auto;
  padding: 14px 24px;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 18px;
}

.site-header-inner .brand {
  justify-self: start;
}

.site-header-inner .header-search {
  justify-self: center;
}

.site-header-inner nav.tabs {
  justify-self: end;
}

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

header.site-header .brand img {
  width: 38px;
  height: 38px;
  display: block;
}

header.site-header .brand-word {
  font-family: 'Audiowide', sans-serif;
  font-size: 1.05rem;
  color: #fff;
  letter-spacing: 0.02em;
}

header.site-header .brand-word .accent {
  color: var(--red);
}

/* Season/Country/State — moved out of the dark header bar and onto the
   page itself (a plain card, like everything else in <main>), so the .dd*
   styles below are tuned for a light background now, not the header's. */
/* Sticks directly below the sticky header.site-header while the page
   scrolls, so the Season/Country/State (and the Include Scrimmage/…
   toggle folded into this same card — see #scrimmage-filter-row) stay
   reachable without scrolling back up. `top: var(--sticky-header-height)`
   rather than a plain `0` so it locks in right underneath the header bar
   instead of sliding up underneath/behind it — that variable is set from
   the header's actual rendered height (see app.js) rather than a
   hardcoded number, since the header's height isn't fixed (e.g. it can
   wrap on narrow screens). A shadow is added only once it's actually
   pinned (see .page-filters.is-pinned in app.js) so it doesn't look like a
   stray box when the page is at the very top. */
.page-filters {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  align-items: center;
  position: sticky;
  top: var(--sticky-header-height, 0px);
  z-index: 20;
  transition: box-shadow 0.15s ease;
}

.page-filters.is-pinned {
  box-shadow: 0 4px 10px -4px rgba(0, 0, 0, 0.25);
}

.page-filters-scrimmage-toggle {
  font-size: 0.81rem; /* was 0.9rem, cut 10% per request */
}

/* Team profile's own "Include Scrimmages" checkbox (In-season Progress
   row) — same 10% reduction, applied everywhere this control shows up. */
.include-scrimmages-label {
  font-size: 0.9em;
}

.dd {
  display: flex;
  align-items: center;
  gap: 8px;
  position: relative;
}

.dd-caption {
  color: var(--muted);
  font-size: 0.8rem;
  font-weight: 600;
}

.dd-trigger {
  display: flex;
  align-items: center;
  gap: 6px;
  background: #fff;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 10px;
  font-size: 0.8rem;
  cursor: pointer;
}

.dd-trigger:hover {
  border-color: var(--red);
}

/* State dropdown, when no Country is selected yet — a bare state/province
   code is ambiguous without a country to disambiguate it, so picking one
   isn't allowed until a specific Country is chosen (see
   updateStateTriggerEnabled in app.js). */
.dd-trigger-disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.dd-trigger-disabled:hover {
  border-color: var(--border);
}

.dd-caret {
  font-size: 0.65rem;
  opacity: 0.8;
}

.dd-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  background: #ffffff;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  min-width: 220px;
  max-height: 320px;
  overflow-y: auto;
  z-index: 50;
  box-shadow: 0 8px 20px rgba(0,0,0,0.25);
}

.dd-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  cursor: pointer;
  font-size: 0.85rem;
}

.dd-item.dd-item-hidden {
  display: none;
}

.dd-search {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 8px 12px;
  border: none;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
  position: sticky;
  top: 0;
  background: #fff;
}

.dd-search:focus {
  outline: none;
  background: #fbe9e9;
}

.dd-item:hover {
  background: #fbe9e9;
}

.dd-item-count {
  margin-left: auto;
  font-size: 0.78rem;
}

.dd-thumb {
  width: 30px;
  height: 30px;
  object-fit: contain;
  border-radius: 3px;
  background: #fff;
  flex-shrink: 0;
}

.dd-item .dd-thumb {
  width: 34px;
  height: 34px;
}

.state-flag {
  width: 28px;
  height: 21px;
  object-fit: cover;
  border-radius: 2px;
  border: 1px solid var(--border);
  flex-shrink: 0;
  vertical-align: middle;
}

.dd-item .state-flag {
  width: 32px;
  height: 24px;
}

/* Country flags (flag-icons library) sized to match the state flags above. */
.fi.country-flag {
  display: inline-block;
  width: 28px;
  height: 21px;
  border-radius: 2px;
  border: 1px solid var(--border);
  background-size: cover;
  background-position: center;
  flex-shrink: 0;
  vertical-align: middle;
}

.dd-item .fi.country-flag {
  width: 32px;
  height: 24px;
}

.header-search {
  position: relative;
  width: 320px;
  max-width: 100%;
}

.header-search-icon {
  position: absolute;
  top: 50%;
  left: 10px;
  transform: translateY(-50%);
  display: flex;
  color: rgba(255,255,255,0.55);
  pointer-events: none;
}

.header-search-input {
  width: 100%;
  padding: 7px 10px 7px 32px;
  border: 1px solid rgba(255,255,255,0.22);
  border-radius: 8px;
  background: rgba(255,255,255,0.08);
  color: #fff;
  font-size: 0.85rem;
}

.header-search-input::placeholder {
  color: rgba(255,255,255,0.55);
}

.header-search-input:focus {
  outline: none;
  background: rgba(255,255,255,0.16);
}

.header-search-suggestions {
  width: 100%;
  min-width: 260px;
}

.header-search-suggestion-type {
  flex-shrink: 0;
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 2px 6px;
  border-radius: 4px;
  color: #fff;
  min-width: 40px;
  text-align: center;
}

.header-search-suggestion-type-team {
  background: var(--red);
}

.header-search-suggestion-type-event {
  background: var(--ink);
}

.header-search-suggestion-num {
  font-family: monospace;
  font-weight: 600;
  flex-shrink: 0;
}

.header-search-suggestion-name {
  color: var(--muted);
  font-size: 0.82rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.header-search-empty {
  padding: 10px 12px;
  font-size: 0.85rem;
  color: var(--muted);
}

.header-search-recent-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px 4px;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.header-search-recent-clear {
  background: none;
  border: none;
  padding: 0;
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: normal;
  color: var(--red);
  cursor: pointer;
}

.header-search-recent-clear:hover {
  text-decoration: underline;
}

nav.tabs {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

nav.tabs button {
  background: transparent;
  border: 1px solid rgba(255,255,255,0.25);
  color: #fff;
  padding: 8px 14px;
  border-radius: 999px;
  cursor: pointer;
  font-size: 0.9rem;
}

nav.tabs button.active {
  background: var(--red);
  border-color: var(--red);
  font-weight: 600;
}

main {
  max-width: 1000px;
  margin: 0 auto;
  padding: 24px;
}

.season-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
  color: var(--muted);
  font-size: 0.9rem;
}

.panel {
  display: none;
}
.panel.active { display: block; }

/* Events LIST page (the Browse/Timeline table, before drilling into a
   specific event) — 10% smaller overall, per request. `zoom` (not
   `font-size: 90%`) is deliberate: this page's CSS mixes rem, em, and
   plain px sizes throughout, and a parent font-size only rescales
   descendants sized in em — rem is root-relative and px is absolute, so
   most of the page wouldn't have shrunk at all. `zoom` scales the whole
   rendered subtree (text, icons, gaps, borders) uniformly and keeps every
   existing percentage-based layout (column widths, etc.) internally
   consistent, without having to touch every font-size rule individually.
   Supported in all current major browsers (Firefox added it in 2024).
   (The event DETAIL page's own two stacked 10% cuts from an earlier
   request were reverted — this only affects the list.) */
#events-list-view {
  zoom: 0.9;
}

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px 20px;
  margin-bottom: 16px;
}

.card h2 {
  margin-top: 0;
  font-size: 1.1rem;
}

/* Separates two logically-distinct pieces of content sharing one card
   (e.g. the Teams tab's summary stats sitting above the full team table) —
   a plain rule rather than a second card's worth of border/shadow/padding. */
.card-divider {
  border: none;
  border-top: 1px solid var(--border);
  margin: 18px 0;
}

#news-box p {
  margin: 0;
}

#news-box a {
  color: var(--red-dark);
  text-decoration: none;
  font-weight: 600;
}

#news-box a:hover {
  text-decoration: underline;
}

.video-list {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
}

.video-item {
  display: block;
  width: 200px;
  color: var(--text);
  text-decoration: none;
}

.video-thumb {
  width: 200px;
  height: 112px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--border);
  display: block;
}

.video-title {
  margin-top: 6px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--red-dark);
}

.video-item:hover .video-title {
  text-decoration: underline;
}

.dashboard {
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
}

.dash-stat {
  flex: 1;
  min-width: 160px;
}

.dash-value {
  font-family: 'Audiowide', sans-serif;
  font-size: 1.2rem; /* 20% down from 2rem, per request */
  color: var(--red);
  line-height: 1.1;
}

.dash-label {
  color: var(--muted);
  font-size: 0.68rem; /* 20% down from 0.85rem, per request */
  margin-top: 4px;
}

.search-row {
  display: flex;
  gap: 8px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}

.search-row input[type="text"], .search-row input[type="number"] {
  flex: 1;
  min-width: 180px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 1rem;
}

.search-row button {
  background: var(--red);
  color: #fff;
  border: none;
  padding: 10px 18px;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 600;
}

.search-row button:hover { background: var(--red-dark); }

.search-row button.sort-toggle {
  background: #fff;
  color: var(--red-dark);
  border: 1px solid var(--border);
}

.search-row button.sort-toggle:hover { background: #fbe9e9; }

.search-row button.sort-toggle[data-sort="opr"] {
  background: var(--red);
  color: #fff;
  border-color: var(--red);
}

.sortable-th {
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}

.sortable-th:hover {
  color: var(--red-dark);
}

.sortable-th-active {
  color: var(--red-dark);
}

.team-number-badge {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.team-number-trophy {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.36em; /* 15% down from 1.6em, per request */
  opacity: 0.28;
  pointer-events: none;
}

/* Team profile page's trophy only — 15% smaller than the shared
   .team-number-trophy size above (which the Teams list also uses), per
   request. */
.team-number-trophy-profile {
  font-size: 1.156em;
}

.opr-events table, .opr-leaderboard table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
  margin-top: 8px;
}

/* Event page's Leaderboard tab — matches the Top Score Matches table's size
   (see .top-score-matches-table below) rather than its own separate
   pre-scale value, so every table on the event detail page reads at the
   same size. */
.opr-leaderboard table {
  font-size: 0.8em;
}

.opr-leaderboard th {
  text-align: center;
}

/* Desktop-only per request — Team is 21.952% (was bumped to 30.7328% by a
   4th ×1.4 request, then that specific increase was reversed — the "cut
   it off" ask right after was about confirming the existing ellipsis
   behavior, not asking for the column to grow again), with the other 10
   columns at their matching pre-4th-bump widths, keeping relative
   proportions to each other intact ("all numeric fields" — every column
   except Team). table-layout:fixed is needed for these percentages to
   actually hold — without it the browser's auto layout sizes columns by
   content instead. Wrapped in min-width so it only ever applies alongside
   the SEPARATE mobile-only fixed-width rules already in the file
   (different values, mobile hides 4 of these columns entirely) rather
   than fighting them. Team cuts off with an ellipsis rather than wrapping
   — same flex/nowrap/ellipsis pattern already used for the Scouting tab's
   own team-name truncation (see .registration-table .scouting-team-td
   .leaderboard-team-fullname elsewhere in this file). */
@media (min-width: 701px) {
  .opr-leaderboard table {
    table-layout: fixed;
  }
  .opr-leaderboard table th:nth-child(1),
  .opr-leaderboard table td:nth-child(1) { width: 3.39366%; }
  .opr-leaderboard table th:nth-child(2),
  .opr-leaderboard table td:nth-child(2) { width: 21.952%; }
  .opr-leaderboard table th:nth-child(3),
  .opr-leaderboard table td:nth-child(3) { width: 7.63414%; }
  .opr-leaderboard table th:nth-child(4),
  .opr-leaderboard table td:nth-child(4) { width: 6.78685%; }
  .opr-leaderboard table th:nth-child(5),
  .opr-leaderboard table td:nth-child(5) { width: 7.63414%; }
  .opr-leaderboard table th:nth-child(6),
  .opr-leaderboard table td:nth-child(6) { width: 7.63414%; }
  .opr-leaderboard table th:nth-child(7),
  .opr-leaderboard table td:nth-child(7) { width: 9.33119%; }
  .opr-leaderboard table th:nth-child(8),
  .opr-leaderboard table td:nth-child(8) { width: 9.33119%; }
  .opr-leaderboard table th:nth-child(9),
  .opr-leaderboard table td:nth-child(9) { width: 9.33119%; }
  .opr-leaderboard table th:nth-child(10),
  .opr-leaderboard table td:nth-child(10) { width: 9.33119%; }
  .opr-leaderboard table th:nth-child(11),
  .opr-leaderboard table td:nth-child(11) { width: 7.63414%; }
  /* display:flex (not the base rule's inline-flex) + max-width:100% is
     what actually bounds this to the td's fixed width — inline-flex sizes
     itself to fit its content (image + full name) regardless of the
     column's width, which is what let long names visually spill into the
     next cell instead of respecting the ellipsis truncation below (seen
     directly: a name overlapping into the Avg RP/#/Match cells). Same
     fix already used for the Scouting tab's own team-name truncation —
     see .registration-table .scouting-team-td .leaderboard-team. */
  .opr-leaderboard .leaderboard-team {
    display: flex;
    min-width: 0;
    max-width: 100%;
  }
  .opr-leaderboard .leaderboard-team-fullname {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
}

/* "#/Match" (column 4 — see leaderboardColumns in app.js) must render
   exactly as authored ("#" over "Match", not "MATCH") on both mobile and
   desktop, per an explicit case-sensitive request — unlike the other
   text-transform:none overrides for this table, which are mobile-only
   (see the media-query block below), this one is NOT scoped to a
   viewport width so it applies everywhere. Overrides the site-wide
   th{text-transform:uppercase} rule (see th{} elsewhere in this file). */
.opr-leaderboard table th:nth-child(4) {
  text-transform: none;
}

/* Click-to-sort column headers (see makeSortableTable in app.js) — shared
   by the Leaderboard and Advancement tabs. A sortable header gets a faint
   up/down affordance that solidifies + tints red once it's the active sort
   column, so it's clear both that headers are clickable and which one is
   currently driving the order. */
/* Events tab's "Top Score Matches" table — 20% smaller than the page's
   normal table text, so 8 columns (2 team cells + 4 score figures + date +
   event) fit comfortably without wrapping. */
.top-score-matches-table {
  font-size: 0.8em;
  table-layout: fixed;
}

/* Date and Event narrowed (allowed to wrap onto two lines) so the freed
   width goes to the four score columns instead — makes their scale bars
   (see numberBarCell in app.js) more informative at a glance. */
.top-score-matches-table th:nth-child(1),
.top-score-matches-table td:nth-child(1) {
  width: 3%;
}

.top-score-matches-table th:nth-child(2),
.top-score-matches-table td:nth-child(2) {
  width: 23.956%; /* was 16%, given the space freed from Event (-15%) and the four score columns (-10% each) */
}

.top-score-matches-table th:nth-child(3),
.top-score-matches-table td:nth-child(3),
.top-score-matches-table th:nth-child(4),
.top-score-matches-table td:nth-child(4),
.top-score-matches-table th:nth-child(5),
.top-score-matches-table td:nth-child(5),
.top-score-matches-table th:nth-child(6),
.top-score-matches-table td:nth-child(6) {
  width: 10.476%; /* was 11.64%, reduced 10% (relative) per request; freed space given to Alliance */
}

.top-score-matches-table th:nth-child(7),
.top-score-matches-table td:nth-child(7) {
  width: 7.44%;
}

.top-score-matches-table th:nth-child(8),
.top-score-matches-table td:nth-child(8) {
  width: 18.7%; /* was 22%, reduced 15% (relative) per request; freed space given to Alliance */
}

.top-score-matches-table th:nth-child(7),
.top-score-matches-table td:nth-child(7),
.top-score-matches-table th:nth-child(8),
.top-score-matches-table td:nth-child(8) {
  white-space: normal;
  word-break: break-word;
}

/* Date/Event text is capped at 2 lines (the usual height of the Alliance
   column's 2 stacked teams) — anything past that truncates with an
   ellipsis instead of growing the row taller than Alliance can justify.
   This clamp lives on an inner <span> (see topScoreDateEventCell-style
   markup in renderTopScoreMatchesPage in app.js), not the <td> itself —
   same reasoning as .top-score-alliance-cell living on an inner <div>:
   overriding a table-cell's own display breaks table-layout:fixed's
   column-width math. */
.top-score-matches-table .clamp-2-lines {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.top-score-matches-table th {
  text-align: center;
  /* Overrides the site-wide th{text-transform:uppercase} (see th{}
     further down) — scoped to just this table per request, so "Total
     Pts" etc. render as written in renderTopScoreMatchesPage in app.js
     instead of shouting "TOTAL PTS", without changing every other
     table's header styling site-wide. */
  text-transform: none;
}

/* "Country/region Match Analysis" tab (see loadMatchAnalysis in app.js) —
   shares .top-score-matches-table's general look (font-size, table-layout:
   fixed, header treatment, the .clamp-2-lines rule above) via carrying
   both classes, but needs its own column widths since it has one extra
   leading column (Country/Region) that the plain Top Matches table
   doesn't: # / Country-Region / Alliance / npPts / TotalPts / AutoPts /
   TelePts / Date / Event = 9 columns instead of 8. Same specificity as the
   .top-score-matches-table nth-child rules above, so source order (this
   block comes later) is what makes these win instead of those. */
.match-analysis-table th:nth-child(1),
.match-analysis-table td:nth-child(1) {
  width: 3%;
}

.match-analysis-table th:nth-child(2),
.match-analysis-table td:nth-child(2) {
  /* 5% was too narrow at this table's actual rendered width (the page
     content is capped at max-width:1000px — see main/.card above — so 5%
     of that is nowhere near enough room for a 28px flag + gap + 2-3
     letters), causing the state abbreviation to visually overflow into
     the Alliance column next to it. 9% is the minimum that comfortably
     fits flag+text without overlap. */
  width: 9%;
}

.match-analysis-table th:nth-child(3),
.match-analysis-table td:nth-child(3) {
  width: 20%;
}

.match-analysis-table th:nth-child(4),
.match-analysis-table td:nth-child(4),
.match-analysis-table th:nth-child(5),
.match-analysis-table td:nth-child(5),
.match-analysis-table th:nth-child(6),
.match-analysis-table td:nth-child(6),
.match-analysis-table th:nth-child(7),
.match-analysis-table td:nth-child(7) {
  width: 10%;
}

.match-analysis-table th:nth-child(8),
.match-analysis-table td:nth-child(8) {
  width: 7%;
  white-space: normal;
  word-break: break-word;
}

.match-analysis-table th:nth-child(9),
.match-analysis-table td:nth-child(9) {
  width: 14%; /* was 18%, gave 4% back to ST/RG to fix the overlap */
  white-space: normal;
  word-break: break-word;
}

.top-score-alliance-cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* A long team name truncates with an ellipsis instead of wrapping the
   alliance cell onto a second line — max-width caps it, the rest is the
   usual nowrap+ellipsis trio. Doesn't touch the logo <img>, only the name
   text next to it (see topScoreTeamCell in app.js). */
.top-score-alliance-cell .leaderboard-team {
  max-width: 220px;
}

.top-score-alliance-cell .leaderboard-team span {
  display: inline-block;
  max-width: 190px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}

.sortable-th {
  cursor: pointer;
  user-select: none;
  position: relative;
  padding-right: 16px;
}

/* Two-line column header (see makeSortableTable's `label: ['Avg', 'RP']`
   array form) — stacks each line in its own <span>, tightened line-height
   so it doesn't blow out the header row's height. */
.th-two-line {
  line-height: 1.15;
}

.th-two-line span {
  display: block;
}

.sortable-th::after {
  content: '⇅';
  position: absolute;
  right: 2px;
  opacity: 0.35;
  font-size: 0.85em;
}

.opr-leaderboard th.sorted-asc::after, .advancement-table th.sorted-asc::after {
  content: '↑';
  opacity: 1;
  color: var(--red-dark);
}

.opr-leaderboard th.sorted-desc::after, .advancement-table th.sorted-desc::after {
  content: '↓';
  opacity: 1;
  color: var(--red-dark);
}

/* A team profile's per-event card — event name links through to that
   event's own page (see jumpToEvent in app.js). */
.event-link {
  color: var(--red-dark);
  cursor: pointer;
}

.event-link:hover {
  text-decoration: underline;
}

/* One event's card on a team profile (see buildEventCard in app.js) — row 1
   is the event name + date/location in a bigger font than the rest of the
   page, row 2 is a quick-glance stats strip, and below that a small
   Qualification/Playoff tab switch showing the actual match tables. */
.team-event-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 14px;
  background: var(--bg);
  transition: box-shadow 0.3s ease, border-color 0.3s ease;
}

/* Brief highlight for the card jumped to from a match-table team badge
   click (see openTeamProfile's focusEventCode / showTeamDetail's
   scroll-into-view logic in app.js) — a couple seconds of a colored ring
   so it's obvious which card the page just scrolled to, then fades back
   to normal via the transition above (the class itself is removed on a
   timer, not this rule). */
.team-event-card-highlight {
  border-color: var(--red);
  box-shadow: 0 0 0 2px rgba(214, 31, 44, 0.25);
}

.team-event-card-row1 {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-bottom: 8px;
}

.team-event-card-name {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 1.05rem;
  font-weight: 700;
}

/* Small flag/calendar/pin icon in front of the event name/date/location
   (see eventCardIcon in app.js) — currentColor, so it always matches
   whatever text color it sits next to without a separate variant. */
.team-event-card-icon {
  display: inline-flex;
  flex-shrink: 0;
  opacity: 0.6;
}

.team-event-card-datetime {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.team-event-card-date,
.team-event-card-location {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 1rem;
  color: var(--muted);
  white-space: nowrap;
}

.team-event-card-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  margin-bottom: 10px;
}

.event-stat {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  font-size: 1rem;
}

.event-stat-label {
  color: var(--muted);
  font-weight: 600;
}

.event-stat-value {
  font-weight: 700;
}

.team-event-card-matches {
  margin-top: 4px;
}

/* Award badge(s) this team won at an event (see renderTeamEventMatches in
   app.js) — sits right under the event name/date row, above the OPR stats
   row, so it reads as a headline fact about the event rather than buried
   stat. Gold-tinted to stand out from the plain stat/location text around
   it; hidden entirely (see the inline display:none default) for the
   overwhelming majority of event cards that have no award to show. */
.team-event-card-awards {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.team-award-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: #fff7e0;
  border: 1px solid #f0c96b;
  color: #8a6300;
  border-radius: 999px;
  padding: 3px 10px 3px 8px;
  font-size: 0.85rem;
  font-weight: 700;
}

/* Compact Qualification/Playoff tab switch inside one event card — a
   smaller variant of .event-tab-bar/.event-tab-btn (that one's sized for a
   whole event page's own tabs, this needs to fit inside a single card).
   This same .team-event-tab-bar class is also reused for the team profile
   page's own Qualification/Playoff switch (nested inside an event card,
   see renderTeamEventMatches in app.js) — the sticky/solid-background
   treatment below only makes sense for the two TOP-LEVEL bars sitting
   directly under the page's Season/Country/State filters, so it's scoped
   to those two by id rather than the shared class; the team-profile one
   stays plain/transparent like every other in-card tab switch. */
.team-event-tab-bar {
  display: flex;
  gap: 16px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 10px;
}

/* Locked directly beneath the (also sticky) header + filters bar, same
   "stays put while content scrolls underneath it" treatment as those two
   — see --sticky-filters-height in app.js. Solid background needed since
   #teams-tab-bar sits directly in the page (above #teams-dashboard-card),
   not inside a .card of its own — without it, scrolled-past content would
   show through behind the bar instead of being hidden by it. */
#teams-tab-bar,
#timeline-tab-bar {
  position: sticky;
  top: var(--sticky-filters-height, 0px);
  z-index: 18;
  background: var(--card);
}

/* On the Teams tab specifically, the tab bar is a separate sibling sitting
   right above #teams-dashboard-card (not nested inside a card the way
   #timeline-tab-bar is on Events — that one's already seamless with its
   card since it lives inside it), which used to leave a visible gap
   between the two. Merging them into what reads as one continuous panel:
   the tab bar picks up the card's own border/rounded-top-corners/
   background and its bottom margin goes to 0, while the card below drops
   its top border and top corners so the two sit flush with nothing
   between them. */
#teams-tab-bar {
  margin-bottom: 0;
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: var(--radius) var(--radius) 0 0;
  padding: 10px 20px 0;
  justify-content: center;
}

#teams-dashboard-card {
  margin-top: 0;
  border-top: none;
  border-radius: 0 0 var(--radius) var(--radius);
}

.team-event-tab-btn {
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  padding: 6px 2px;
  font-size: 1rem;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}

/* Events tab's Live/Upcoming/Championship buttons (see #timeline-tab-bar)
   carry both a full and a short label — desktop shows the full one
   ("Live Events"); mobile swaps to the short one ("Live") to save space,
   see the mobile media query. */
.timeline-tab-label-short {
  display: none;
}

/* Events list header/date cells (see .timeline-col-label-full/-short in
   loadTimelineEvents) — same full/short pattern: desktop shows the full
   text ("#Teams", the full date range, " Location"), mobile swaps to the
   short one (just "#", the start date only, or nothing beyond the pin
   icon) — see the mobile media query. */
.timeline-col-label-short {
  display: none;
}

/* Events list Location cell (see .timeline-location-cell in
   loadTimelineEvents) — same compact format on both mobile and desktop
   now: country flag, then state/region code, no city. One row, no
   wrapping. */
.timeline-location-cell {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: nowrap;
}

.team-event-tab-btn:hover {
  color: var(--text);
}

.team-event-tab-btn.active {
  color: var(--red);
  border-bottom-color: var(--red);
}

/* "Season Progress" toggle button + its chart box (see showTeamDetail in
   app.js) — same pill-button language as .video-toggle-btn. */
.season-progress-btn {
  background: #fff;
  color: var(--red-dark);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 14px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}

.season-progress-btn:hover {
  background: var(--bg);
}

.season-progress-box {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
  background: var(--card);
}

.season-progress-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin-bottom: 8px;
}

.season-progress-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 0.85rem;
  color: var(--muted);
  font-weight: 600;
}

.season-progress-swatch {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 3px;
}

/* In-season Progress chart + "Compare with:" sidebar, side by side (see
   showTeamDetail in app.js) — the chart column flexes to fill the
   available width, the compare sidebar stays a fixed-ish narrow column on
   the right so adding/removing compare teams doesn't reflow the chart's
   own position. Stacks back to one column on narrow/mobile widths. */
.season-progress-layout {
  display: flex;
  align-items: flex-start;
  gap: 20px;
}

.season-progress-chart-col {
  flex: 1 1 auto;
  min-width: 0;
}

@media (max-width: 640px) {
  .season-progress-layout {
    flex-direction: column;
  }
}

/* "Compare with:" sidebar (see .season-progress-layout above) — lets the
   viewer add up to 3 other teams' NP OPR onto the chart. Vertical stack
   (label, chips, input row) rather than the single horizontal line this
   used when it sat above the chart, since it's now a narrow side column. */
.compare-teams-box {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 0 0 200px;
  width: 200px;
}

.compare-teams-label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
}

.compare-teams-chips {
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  gap: 6px;
}

.compare-teams-input-row {
  display: flex;
  gap: 6px;
}

.compare-teams-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1.5px solid var(--border);
  border-radius: 999px;
  padding: 3px 6px 3px 10px;
  font-size: 0.8rem;
  font-weight: 600;
  background: #fff;
}

.compare-teams-chip-remove {
  cursor: pointer;
  font-weight: 700;
  padding: 0 4px;
  border-radius: 50%;
  line-height: 1;
}

.compare-teams-chip-remove:hover {
  background: rgba(0, 0, 0, 0.08);
}

.compare-teams-input {
  width: 84px;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.85rem;
}

.compare-teams-add-btn {
  background: #fff;
  color: var(--red-dark);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 12px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}

.compare-teams-add-btn:hover {
  background: var(--bg);
}

.compare-teams-error {
  font-size: 0.8rem;
  color: var(--red-dark);
}

.leaderboard-team {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* Event page's "Award" tab (see showEventDetail in app.js) — one card
   (.award-group) per DISTINCT award name, since an event routinely hands
   out the same award to several winners (e.g. 3 Judges' Awards); each
   winner is its own row underneath the award-name heading, team badge
   (or person's name) leading the row rather than trailing it. */
/* Matches the Top Score Matches table's size (see .top-score-matches-table
   below) rather than the ambient body size, so every section on the event
   detail page reads at the same size. */
.event-awards {
  padding: 4px 0;
  font-size: 0.8em;
}

/* Event page's "Advancement" tab (see showEventDetail in app.js) — a plain
   ranked table, one row per team, since (unlike Award) every row has the
   exact same shape (7 numeric columns) rather than needing card grouping. */
.event-advancement {
  padding: 4px 0;
}

/* Matches the Top Score Matches table's size (see .top-score-matches-table
   below) rather than its own separate pre-scale value, so every table on
   the event detail page reads at the same size. */
.advancement-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.8em;
  margin-top: 8px;
}

.advancement-table th, .advancement-table td {
  padding: 6px 8px;
  text-align: left;
  border-bottom: 1px solid var(--border);
}

/* Desktop-only per request — Team is 27.44% (was bumped to 38.416% by a
   4th ×1.4 request, then that specific increase was reversed — the "cut
   it off" ask right after was about confirming the existing ellipsis
   behavior, not asking for the column to grow again), with the other 7
   columns ("all numeric fields" — everything except Team) at their
   matching pre-4th-bump widths, keeping relative proportions to each
   other intact — this table has no OPR columns (unlike the Ranking tab's
   identical-in-spirit request just above). :not(.registration-table)
   excludes the Scouting tabs, which share this class — same exclusion
   pattern used everywhere else in this file for Advancement-only changes
   (see the git history around "header change I asked you to do in
   Advancement tab also got applied to all tabs under scouting"). Team
   cuts off with an ellipsis rather than wrapping. */
@media (min-width: 701px) {
  .advancement-table:not(.registration-table) {
    table-layout: fixed;
  }
  .advancement-table:not(.registration-table) th:nth-child(1),
  .advancement-table:not(.registration-table) td:nth-child(1) { width: 4.03258%; }
  .advancement-table:not(.registration-table) th:nth-child(2),
  .advancement-table:not(.registration-table) td:nth-child(2) { width: 27.44%; }
  .advancement-table:not(.registration-table) th:nth-child(3),
  .advancement-table:not(.registration-table) td:nth-child(3) { width: 8.06274%; }
  .advancement-table:not(.registration-table) th:nth-child(4),
  .advancement-table:not(.registration-table) td:nth-child(4) { width: 12.09334%; }
  .advancement-table:not(.registration-table) th:nth-child(5),
  .advancement-table:not(.registration-table) td:nth-child(5) { width: 12.09334%; }
  .advancement-table:not(.registration-table) th:nth-child(6),
  .advancement-table:not(.registration-table) td:nth-child(6) { width: 12.09334%; }
  .advancement-table:not(.registration-table) th:nth-child(7),
  .advancement-table:not(.registration-table) td:nth-child(7) { width: 12.09334%; }
  .advancement-table:not(.registration-table) th:nth-child(8),
  .advancement-table:not(.registration-table) td:nth-child(8) { width: 12.09334%; }
  /* display:flex (not the base rule's inline-flex) + max-width:100% is
     what actually bounds this to the td's fixed width — see the matching
     comment on .opr-leaderboard .leaderboard-team just above for the full
     explanation (same bug, same fix, same table structure). */
  .advancement-table:not(.registration-table) .leaderboard-team {
    display: flex;
    min-width: 0;
    max-width: 100%;
  }
  .advancement-table:not(.registration-table) .leaderboard-team-fullname {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
}

.advancement-table th {
  color: var(--muted);
  font-weight: 700;
  text-align: center;
}

/* Advancement tab's own DESKTOP headers ("qualRank", "qualPts", "alliPts",
   "playoffPts", "awardPts") are deliberately camelCase and need to render
   exactly as written — the site-wide th{text-transform:uppercase} would
   otherwise force them to "QUALRANK" etc. :not(.registration-table)
   excludes the Scouting tabs' tables, which share the .advancement-table
   class but must NOT pick this up (same leak this exact exclusion pattern
   fixed once already — see the registration-table th rule above, and the
   git history around "header change I asked you to do in Advancement tab
   also got applied to all tabs under scouting"). Scoped to desktop only,
   per request — mobile substitutes its own short ::before labels
   ("Qual"/"Alli"/"PlayO"/"Awd"/"Tot", see the mobile media query below)
   and was intentionally left alone here so those keep rendering exactly
   as they did before (uppercased by the site-wide rule, same as always). */
@media (min-width: 701px) {
  .advancement-table:not(.registration-table) th {
    text-transform: none;
  }
}

.advancement-total {
  font-weight: 700;
}

/* Scouting tab's own table (registration-table, in addition to the shared
   advancement-table styling above) — column order is # / Team / Location
   / npOPR / autoOPR / teleOPR / pRank / Luckiness / aRank (see
   showEventDetail's renderRegistrationRows). table-layout:fixed + explicit
   widths on every column is needed to make the three OPR columns exactly
   equal width (auto layout sizes each to its own content, which will
   never land on identical widths by itself). Current widths, after
   several rounds of requests: Team cut by 1/3 (30% → 20%), Location cut
   in half (15% → 7.5%), and most recently the three OPR columns cut by
   10% (14.83% → 13.35% each), with that freed space split 70/30 into
   pRank (5% → 8.11%) and Luckiness (10% → 11.33%) — the OPR columns are
   still kept exactly equal to one another, just no longer the single
   widest columns in the table. Applies on both desktop and mobile; the
   mobile media query separately hides the last three columns entirely
   rather than resizing them. */
.registration-table {
  table-layout: fixed;
}
/* Overrides the site-wide th{text-transform:uppercase} (see th{} above) —
   this table's headers ("npOPR", "autoOPR", "teleOPR") are meaningful
   mixed-case abbreviations, and forcing them to all-caps ("NPOPR") loses
   that distinction entirely, per request. */
.registration-table th {
  text-transform: none;
}
/* Schedule Luck's sort arrow sits centered BELOW the die icon instead of
   to the right of it (the generic .sortable-th::after default) — the icon
   is much larger than this table's other header text, so a right-aligned
   arrow next to it reads as stray/misplaced rather than attached to the
   header. Applies to both Pre-Qualification and Qualification (same
   column 8 either way — Playoff has its own different column set, same
   :not(.playoff-columns) exclusion used elsewhere in this file for this
   column). Works on both desktop and mobile since it's not inside a media
   query — the icon is the same size either way. */
/* Centered on desktop per request (the midpoint-between-left-and-center
   treatment stayed on mobile only — see the media-query override below,
   which still uses that fixed-px approach since it looked right there). */
.registration-table:not(.playoff-columns) th:nth-child(8) {
  text-align: center;
  padding-right: 0;
  padding-bottom: 14px;
}
.registration-table:not(.playoff-columns) th:nth-child(8)::after {
  right: auto;
  top: auto;
  left: 50%;
  bottom: 2px;
  transform: translateX(-50%);
}

/* Playoff Multiplier's × icon (see .scouting-playoff-mult-th in app.js —
   set on the real <th> directly rather than matched by nth-child, since
   this column is built two different ways on desktop vs. mobile — see
   playoffColumns/buildPlayoffMobileThead — so a positional selector
   wouldn't reliably hit the same header both ways). Same centered-icon,
   arrow-below treatment as Schedule Luck's die. */
.scouting-playoff-mult-th {
  text-align: center;
  padding-right: 0;
  padding-bottom: 14px;
}
.scouting-playoff-mult-th::after {
  right: auto;
  top: auto;
  left: 50%;
  bottom: 2px;
  transform: translateX(-50%);
}
.registration-table th:nth-child(1), .registration-table td:nth-child(1) { width: 5%; }
.registration-table th:nth-child(2), .registration-table td:nth-child(2) { width: 20%; }
.registration-table th:nth-child(3), .registration-table td:nth-child(3) { width: 7.5%; }
.registration-table th:nth-child(4), .registration-table td:nth-child(4),
.registration-table th:nth-child(5), .registration-table td:nth-child(5),
.registration-table th:nth-child(6), .registration-table td:nth-child(6) { width: 13.35%; } /* was 14.83%, cut 10% (1.483 pts/col, 4.449 pts total) per request */
.registration-table th:nth-child(7), .registration-table td:nth-child(7) { width: 8.11%; } /* was 5%, +70% of the 4.449 pts freed from the OPR columns (3.11 pts) */
.registration-table th:nth-child(8), .registration-table td:nth-child(8) { width: 11.33%; } /* was 10%, +30% of the 4.449 pts freed from the OPR columns (1.33 pts) */
.registration-table th:nth-child(9), .registration-table td:nth-child(9) { width: 8%; }

/* Pre-Qualification is 8 columns, not 9 — it drops aRank (see
   ensureTableForMode/renderRegistrationRows in app.js: aRank is the real
   Leaderboard rank, which doesn't exist yet in a "before anything
   happened" view). Luckiness (now the LAST column, still index 8) absorbs
   the 8 points aRank would've taken so the row still totals 100% instead
   of leaving a gap at the right edge. Higher specificity than the plain
   nth-child(8) rule above guarantees this wins regardless of source
   order, same pattern as .playoff-columns. */
.registration-table.pre-columns th:nth-child(8),
.registration-table.pre-columns td:nth-child(8) { width: 19.33%; }

/* Team column — cut to 20% width (see above) means a long team name would
   normally wrap onto a second line and grow every row's height. Kept to a
   single line and hard-clipped at the column edge instead (no ellipsis —
   matches the same "just cut it off, no …" preference used for the team
   profile page's own name heading) via nowrap + hidden overflow on the
   name span, with min-width:0 so it can actually shrink below its natural
   content width inside the flex row (.leaderboard-team's default
   inline-flex sizing otherwise refuses to shrink a flex item at all). */
.registration-table .scouting-team-td {
  overflow: hidden;
}
.registration-table .scouting-team-td .leaderboard-team {
  display: flex;
  min-width: 0;
  max-width: 100%;
}
.registration-table .scouting-team-td .leaderboard-team-fullname {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: clip;
}

/* Playoff view — a completely different column set (see playoffColumns/
   renderPlayoffRows in app.js): Alliance, Team, individual + alliance-
   combined qual OPR, individual + alliance-combined playoff-only OPR, and
   the playoff multiplier. Team's own width/behavior is scoped to
   .scouting-team-td/-th (class-based, not position-based) above/below.
   Widths themselves are set via an explicit <colgroup> (see
   mountPlayoffTable/buildPlayoffMobileThead in app.js) rather than
   th/td:nth-child rules — the mobile header is a genuine two-row grouped
   header (colspan/rowspan), where a header row's nth-child position no
   longer lines up 1:1 with actual table columns, but a <col>'s width
   applies to its whole column regardless of how any given row's cells are
   spanned, so this is the only approach that works correctly for both the
   desktop (single-row) and mobile (two-row) header shapes.
   Alliance (column 1) is half the width of the other data columns per
   request (6.5% vs. 13%) — same value repeated across every teammate's
   row (see the rowSpan collapsing in renderPlayoffRows), so it doesn't
   need much room. The 6.5 points freed by halving it go to Team, which
   benefits most from the extra space (logo + name). */
.registration-table.playoff-columns colgroup col:nth-child(1) { width: 6.5%; }
.registration-table.playoff-columns colgroup col:nth-child(2) { width: 28.5%; }
.registration-table.playoff-columns colgroup col:nth-child(3),
.registration-table.playoff-columns colgroup col:nth-child(4),
.registration-table.playoff-columns colgroup col:nth-child(5),
.registration-table.playoff-columns colgroup col:nth-child(6),
.registration-table.playoff-columns colgroup col:nth-child(7) { width: 13%; }

/* Location column (see locationCell in showEventDetail) reuses stateCell's
   own combined row — built country-flag-first + plain state/region code,
   the same markup Top Teams already shows on mobile (see
   .state-cell-row-combined elsewhere) — instead of the old separate
   State+Country columns. !important is required on both since these
   elements carry an inline `style="display:..."` from app.js, which
   otherwise beats a plain external rule regardless of specificity. */
.registration-table .state-cell-row {
  display: none !important;
}
.registration-table .state-cell-row-combined {
  display: flex !important;
}

/* Two columns to save vertical space (see award-group's break-inside
   below) — a CSS multi-column layout rather than a grid, since award
   groups vary in height (some have one winner, some have six) and columns
   fill top-to-bottom before wrapping, same as a newspaper layout, instead
   of a grid's rigid same-height rows leaving gaps. Reverts to a single
   column on mobile (see the media query) — two columns would be too
   cramped at phone width. */
/* The Award tab is built from plain divs, not a <table> like the other
   tabs (Qualification/Leaderboard/Advancement/Scouting all use one) — so
   it never picked up the site-wide `table { font-size: 0.92rem; }` rule
   and rendered noticeably larger by default. Matching that size here
   makes it read consistently with the rest of the event page. */
.award-list {
  column-count: 2;
  column-gap: 12px;
  font-size: 0.92rem;
}

.award-group {
  break-inside: avoid;
  margin-bottom: 12px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
}

.award-group-name {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  margin-bottom: 8px;
}

.award-trophy {
  font-size: 1.1rem;
}

/* Left-aligned, no indent — every row starts flush with the award name
   above it (previously indented 26px to line up under the trophy icon's
   text, per request removed). */
.award-winner-row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  text-align: left;
  gap: 8px;
  padding: 4px 0;
}

/* Place ("1st Place") or alliance-role ("Captain"/"1st Pick") qualifier
   split off the base award name (see parseAwardName in app.js) — shown as
   a small pill in front of the team/person it belongs to, since grouping
   by base name would otherwise lose which place/role each winner had. Now
   a single circled-digit/anchor character rather than text (see
   qualifierEmoji in app.js) — light red, per request; text-presentation
   characters/selector on those glyphs is what makes this color actually
   apply instead of a full-color emoji glyph ignoring it. */
.award-qualifier {
  flex-shrink: 0;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 8px;
  font-size: 0.75rem;
  font-weight: 700;
  color: #e57373;
}


.award-winner {
  color: var(--muted);
  font-weight: 600;
}

/* Season tabs on a team profile (see showTeamDetail in app.js) — just the
   year + that season's logo, no game name, so several seasons' worth of
   tabs still fit on one line. Full "YYYY — GAME NAME" is a title tooltip.
   Sits at the very top of its section (seasonsBlock) and uses bold, solid
   pill buttons rather than the plain underline style shared by the event
   page's Qualification/Playoff/Leaderboard tabs — it's the primary way of
   navigating this whole card, so it needs to stand out, not blend in. */
.season-tab-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 16px;
}

.season-tab-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--bg);
  border: 2px solid var(--border);
  border-radius: 999px;
  padding: 8px 18px;
  font-size: 0.84rem;
  font-weight: 700;
  color: var(--text);
  cursor: pointer;
}

.season-tab-btn:hover {
  border-color: var(--red);
}

.season-tab-btn.active {
  background: var(--red);
  border-color: var(--red);
  color: #fff;
}

.season-tab-logo {
  width: 26px;
  height: 26px;
  object-fit: contain;
  border-radius: 4px;
  flex-shrink: 0;
}

.leaderboard-team-logo {
  width: 22px;
  height: 22px;
  border-radius: 5px;
  object-fit: cover;
  flex-shrink: 0;
  display: block;
}

/* Qualification / Playoff / Leaderboard tabs on an event's detail page
   (see showEventDetail in app.js) — an underline-style tab bar, distinct
   from the pill-style nav.tabs in the dark header, since this sits on a
   plain white card. */
.event-tab-bar {
  display: flex;
  gap: 22px;
  border-bottom: 1px solid var(--border);
  margin: 18px 0 16px;
}

/* Qualification/Playoff match tables, scoped to the event detail page only
   (.match-schedule is shared with a team profile's per-event cards too,
   which should keep their own larger size — see .match-schedule table's
   own comment) — matches the Top Score Matches table's size (see
   .top-score-matches-table below) instead of .match-schedule's bigger
   sitewide default, so every table on the event detail page reads at the
   same size. */
.event-tab-panel .match-schedule table {
  font-size: 0.8em;
}


.event-tab-btn {
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  padding: 10px 2px;
  font-size: 1rem;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}

.event-tab-btn:hover {
  color: var(--text);
}

/* Qualification/Playoff tab icons (see tabEmoji in app.js) — always red,
   regardless of active/inactive tab state, per request (unlike the tab's
   own label text, which switches between --muted/--text and --red via
   .event-tab-btn/.event-tab-btn.active above). */
.event-tab-emoji {
  color: var(--red);
  margin-right: 2px;
}

.event-tab-btn.active {
  color: var(--red);
  border-bottom-color: var(--red);
}

/* Detailed/Compact affects both match tabs, so it's a shared row above
   them (hidden on the Leaderboard tab) rather than duplicated per tab. */
.match-display-toggle-row {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 12px;
}

/* Scouting tab's row above the table — team count (left) + the
   Pre-Qualification/Qualification/Playoff toggle, TRUE-centered under the
   tab (see the scouting-mode-row three-column grid comment in app.js —
   1fr/auto/1fr with an empty balancing third cell, so the middle toggle
   column stays centered regardless of how wide the team-count text is). */
.scouting-mode-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 12px;
}

/* Detailed/Compact segmented toggle (see showEventDetail in app.js) —
   same visual language as the Teams tab's sort-toggle button. */
.match-display-toggle {
  display: flex;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}

.match-display-btn {
  background: #fff;
  color: var(--text);
  border: none;
  padding: 7px 14px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  /* Prevents a long label (e.g. "Pre-Qualification") from wrapping onto
     two lines if its column/row ever gets squeezed narrower than the
     text's natural width — see the .scouting-mode-row mobile fix above
     for the concrete case this was guarding against. */
  white-space: nowrap;
}

.match-display-btn + .match-display-btn {
  border-left: 1px solid var(--border);
}

.video-toggle-btn {
  background: #fff;
  color: var(--red-dark);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 14px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}

.video-toggle-btn:hover {
  background: var(--bg);
}

.match-display-btn.active {
  background: var(--red);
  color: #fff;
}

/* Bigger than the site's default table text (~0.92rem) — a match table is
   mostly short tokens (match #, team codes/numbers, scores), so there's a
   lot of open space at the default size; larger text fills it in without
   the table actually needing to be any wider. */
.match-schedule table {
  margin-top: 4px;
  font-size: 0.9rem;
  table-layout: fixed;
  width: 100%;
}

.match-schedule th, .match-schedule td {
  padding: 5px 14px;
}

.match-schedule th {
  text-align: center;
}

.match-schedule-red-heading {
  color: var(--red);
}

.match-schedule-blue-heading {
  color: var(--blue);
}

/* Fixed column widths so the Red and Blue Alliance cells are always the
   same size — without this, the browser sizes each column from its own
   content and the two alliances can end up different widths, which is
   what made one alliance's badges wrap while the other's didn't.
   Column order is Match, Red score, Red Alliance, Blue score, Blue
   Alliance — each score sits immediately to the left of its own alliance
   so the two are visually paired. */
.match-schedule th:first-child, .match-schedule td:first-child {
  width: 64px;
}

.match-schedule th:nth-child(2), .match-schedule td:nth-child(2),
.match-schedule th:nth-child(4), .match-schedule td:nth-child(4) {
  width: 60px;
}

.match-schedule th:nth-child(3), .match-schedule td:nth-child(3),
.match-schedule th:nth-child(5), .match-schedule td:nth-child(5) {
  width: 34%;
}

/* Event page's Qual and Playoff tabs (and the team-profile page's own
   match tables), DESKTOP only, per request — same "drop the Red/Blue
   score columns, show scores as two lines under the match number"
   treatment already applied everywhere on mobile via
   .mobile-compact-match-schedule (see that class's own comments), just
   also here on desktop (originally Playoff-only, extended to Qual too
   per a follow-up request — see match-desktop-compact in app.js for why
   this needed its own class rather than reusing an existing one).
   .match-number-mobile-score is hidden by default everywhere (see that
   base rule elsewhere in this file) since desktop normally has its own
   dedicated score columns — re-shown here the same way the mobile media
   query already does for every OTHER table on the site.

   DESKTOP-ONLY column split, per a follow-up request: column 2 (the
   Red-score slot, previously hidden the same as column 4 below) is now
   the "results only" column (.match-result-td, see app.js) instead of
   staying hidden, and the Alliance columns (3/5) shrink 20%
   (45% -> 36%) to make room. Column 1 keeps its match-number label
   ("P1(✓)", already black — see .match-number-link) but its own
   duplicate score rows are hidden on desktop since matching duplicates
   now show in the new column 2 instead (mobile is untouched — this whole
   block only applies inside the min-width:701px query).

   Win/loss moved onto its own row under the match number, desktop only,
   per a follow-up request — .match-number-label switches from a single
   row to a column (num on row 1, "(✓)"/"(✗)" on row 2, video link last).
   Mobile keeps the original single-line "P1(✓)" layout untouched (see
   the unscoped .match-number-label rule above). */
@media (min-width: 701px) {
  .match-desktop-compact table th:nth-child(4),
  .match-desktop-compact table td:nth-child(4) {
    display: none;
  }
  /* Match column cut in half (90px -> 45px) per request, with the freed
     space handed to the Red/Blue Alliance columns (36% -> 41.9% each) —
     since this column is px-based while the Alliance columns are
     %-based (mixed units, same approximation noted elsewhere in this
     file), the freed 45px is converted using the same ~380px assumed
     table width as those earlier conversions: 22.5px/380px ≈ 5.9% per
     side. The Result column (next rule) is untouched. */
  .match-desktop-compact table th:first-child,
  .match-desktop-compact table td:first-child {
    width: 45px;
  }
  .match-desktop-compact table th:nth-child(2),
  .match-desktop-compact table td:nth-child(2) {
    width: 18%;
  }
  .match-desktop-compact table th:nth-child(3),
  .match-desktop-compact table td:nth-child(3),
  .match-desktop-compact table th:nth-child(5),
  .match-desktop-compact table td:nth-child(5) {
    width: 41.9%;
  }
  .match-desktop-compact td.match-number-link .match-number-mobile-score {
    display: none;
  }
  .match-desktop-compact .match-number-label {
    flex-direction: column;
    align-items: center;
    gap: 1px;
  }
  /* .match-result-td's two score rows, enlarged and stretched to fill the
     column's full width/height (per request — they previously only filled
     about half of it). The flex column lives on an inner wrapper div
     (.match-result-td-inner), NOT the <td> itself — overriding a table
     cell's own display to flex takes it out of table layout entirely
     (breaks table-layout:fixed, collapses the column), which is exactly
     what happened the first time this was tried. The wrapper's children
     (already display:flex internally) stretch to the cell's full width by
     default (align-items:stretch), the bar is taller (16px -> 24px), and
     its text/RP scale up to match. */
  .match-desktop-compact .match-result-td {
    height: 100%;
  }
  .match-desktop-compact .match-result-td-inner {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 8px;
    height: 100%;
  }
  .match-desktop-compact .match-result-td .match-number-mobile-score {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
  }
  .match-desktop-compact .match-result-td .match-number-mobile-score-bar {
    height: 24px;
    border-radius: 6px;
  }
  .match-desktop-compact .match-result-td .match-number-mobile-score-bar-text {
    font-size: calc(0.95rem / 1.2);
  }
  .match-desktop-compact .match-result-td .match-number-mobile-score-rp {
    font-size: calc(0.95rem / 1.2);
  }
}

/* Alliance color coding — light tint always on, full-strength color (with
   white text) only on the alliance that actually won the match. Applied to
   both the score cell and the alliance cell so the color reads as one
   paired unit per the reordered columns above. */
.match-schedule td.station-red {
  background: rgba(214, 31, 44, 0.08);
}

.match-schedule td.station-blue {
  background: rgba(37, 99, 235, 0.08);
}

.match-schedule td.station-red.match-winner {
  background: var(--red);
}

.match-schedule td.station-blue.match-winner {
  background: var(--blue);
}

.match-schedule td.match-winner {
  font-weight: 700;
  color: #fff;
}

/* Badge chips get a translucent white pill (instead of their default faint
   dark tint) so team codes/names stay readable against the solid winner
   background. */
.match-winner .team-badge-detailed,
.match-winner .team-code-badge {
  background: rgba(255, 255, 255, 0.18);
}

.match-winner .team-badge-detailed:hover,
.match-winner .team-code-badge:hover {
  background: rgba(255, 255, 255, 0.3);
}

.match-winner .team-badge-detailed-num,
.match-winner .team-code-badge-code {
  color: #fff;
}

/* Motion/Goal/Pattern ranking-point dots, shown under an alliance's score.
   One dot per bonus threshold that match could earn — filled when earned,
   hollow when not. On a light (non-winning) score cell they use the
   alliance's own color; on a solid winner background they switch to white
   so they stay visible against the saturated fill. */
.rp-dots {
  display: flex;
  gap: 4px;
  margin-top: 2px;
}

.rp-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  opacity: 0.45;
}

.rp-dot-on {
  opacity: 1;
  background: currentColor;
}

.station-red .rp-dots { color: var(--red); }
.station-blue .rp-dots { color: var(--blue); }
.match-winner .rp-dots { color: #fff; }

/* Clickable Q#/P# match label (see renderMatchTable/showScoreBreakdownModal
   in app.js) — underline-on-hover like the site's other .event-link/
   .team-badge-link click affordances. Plain black/var(--text) per
   request (was red-dark) — the match number itself isn't alliance-
   colored the way each alliance's scores now are (see
   .match-number-mobile-score-bar-text's inline color in app.js). */
.match-number-link {
  cursor: pointer;
  color: var(--text);
  font-weight: 600;
}

.match-number-link:hover {
  text-decoration: underline;
}

.match-number-label {
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 4px;
}

/* "P1(✓)" centered in the redesigned compact Match column (mobile
   everywhere, desktop for Qual and Playoff — see
   .mobile-compact-match-schedule/.match-desktop-compact for where this
   applies; both containers carry .mobile-compact-match-schedule, so one
   rule covers both). Not applied to the normal 5-column desktop layout,
   where the label just sits in its usual narrow first column. */
.mobile-compact-match-schedule .match-number-label {
  justify-content: center;
}

/* Desktop-only "results only" column (see matchResultCell in app.js) —
   clickable like .match-number-link, opens the same score-breakdown
   modal. */
.match-result-td {
  cursor: pointer;
}

/* Redesigned compact Match column's red/blue score rows (see
   mobileScoreRow in app.js) — a scale bar for the total score (same
   visual language as numberBarCell elsewhere, just inline rather than a
   whole <td>) with that alliance's Ranking Points to the right. Only
   ever visible when its parent .match-number-mobile-score is switched to
   display:flex (mobile always, desktop Playoff-only — see those two
   rules), so these are unscoped by viewport width themselves. */
.match-number-mobile-score-bar {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: 16px;
  border-radius: 4px;
  background: var(--bg);
  overflow: hidden;
}

.match-number-mobile-score-bar-fill {
  position: absolute;
  inset: 0;
  opacity: 0.16;
}

.match-number-mobile-score-bar-text {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  font-size: calc(0.72rem / 1.2);
  font-weight: 600;
  white-space: nowrap;
}

.match-number-mobile-score-rp {
  flex: 0 0 auto;
  font-size: calc(0.72rem / 1.2);
  font-weight: 600;
  min-width: 1em;
  text-align: right;
}

/* Per-match video link (▶, see renderMatchTable's videoLink in app.js) —
   only rendered when FIRST's own hybrid schedule data includes a
   videoURL for that match, so this is invisible on any match that
   doesn't have one on file. Plain Unicode ▶ (not a color emoji), always
   red regardless of the surrounding .match-number-link's own hover
   state, and never underlines on hover (only the match label itself
   does — see .match-number-link:hover above). */
.match-video-link {
  color: var(--red);
  text-decoration: none;
  font-size: 0.75em;
}

.match-video-link:hover {
  color: var(--red-dark);
}

/* Mobile-only duplicate score rows under the match label (team profile's
   match tables only — see .mobile-compact-match-schedule) — desktop already
   shows these in their own Red/Blue score columns, so these stay hidden
   there. */
.match-number-mobile-score {
  display: none;
}

/* Score-breakdown modal — a simple centered card over a dimmed backdrop,
   closed by the × button or clicking outside the card (see
   showScoreBreakdownModal in app.js). No animation library involved, just
   a fixed-position overlay appended to <body> on open and removed on
   close. */
.score-breakdown-overlay {
  position: fixed;
  inset: 0;
  background: rgba(20, 20, 20, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 16px;
}

.score-breakdown-card {
  background: var(--card);
  border-radius: var(--radius);
  max-width: 520px;
  width: 100%;
  max-height: 85vh;
  overflow-y: auto;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
}

.score-breakdown-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}

.score-breakdown-header h3 {
  margin: 0;
  font-size: 1.05rem;
}

.score-breakdown-close {
  background: transparent;
  border: none;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  color: var(--muted);
  padding: 0 4px;
}

.score-breakdown-close:hover {
  color: var(--text);
}

.score-breakdown-body {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  padding: 16px 18px;
}

@media (max-width: 480px) {
  .score-breakdown-body {
    grid-template-columns: 1fr;
  }
}

/* Plain padded paragraph body for showInfoModal (currently just the
   Schedule Luck header's die-icon explanation) — reuses the score-
   breakdown modal's overlay/card/header shell but doesn't want that
   grid-of-two-tables layout, just a normal block of text. */
.info-modal-body {
  padding: 16px 18px;
  line-height: 1.5;
  color: var(--text);
  /* Schedule Luck's explanation now has multiple paragraphs (see the
     \n\n-separated bodyText passed to showInfoModal) — pre-line preserves
     those blank-line breaks while still wrapping normally, instead of
     collapsing everything onto one run-on line like plain text does. */
  white-space: pre-line;
}

.score-breakdown-table {
  width: 100%;
  border-collapse: collapse;
}

.score-breakdown-table th {
  text-align: left;
  padding: 6px 4px;
  border-bottom: 2px solid currentColor;
  font-size: 0.95rem;
}

.score-breakdown-table.station-red th { color: var(--red-dark); }
.score-breakdown-table.station-blue th { color: var(--blue-dark); }

.score-breakdown-table td {
  padding: 5px 4px;
  border-bottom: 1px solid var(--border);
}

.score-breakdown-table td:last-child {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.score-breakdown-table tr:last-child td {
  border-bottom: none;
  font-weight: 700;
}

/* No wrap — with equal-width alliance columns (above), badges instead
   shrink/truncate to fit (see min-width:0 + text-overflow on the badges
   themselves) rather than reflowing onto a second line. That's what keeps
   every alliance's badges aligned on one row, in both Detailed and
   Compact mode. */
.match-alliance {
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
  align-items: flex-start;
}

.team-code-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  padding: 3px 8px 3px 3px;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.03);
  flex: 1 1 0;
  min-width: 0;
}

.team-code-badge-code {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.team-code-badge:hover {
  background: rgba(214, 31, 44, 0.08);
}

.team-code-badge-img {
  width: 22px;
  height: 22px;
  border-radius: 5px;
  object-fit: cover;
  flex-shrink: 0;
  display: block;
}

.team-code-badge-code {
  font-family: monospace;
  font-weight: 700;
  letter-spacing: 0.5px;
  font-size: 1.05rem;
}

/* Organization name, shown inline (icon, then name) at the left edge of
   igCodeRow — the same row as the Instagram/team-code inputs — on the
   team profile page (desktop only — hidden on mobile, see the mobile
   media query). No organization-logo data exists anywhere in FIRST's
   API, so this uses the same outlined single-color SVG icon style as
   the location pin (see EVENT_CARD_ICON_PATHS.building in app.js)
   instead of a multi-color emoji, for visual consistency. */
.team-profile-org-info {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  color: var(--muted);
}

.team-profile-org-name {
  font-size: 0.85rem;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 260px;
}

/* "Detailed" mode's badge — logo + team number on top, team name below.
   flex:1 1 0 + min-width:0 (rather than a fixed width) so every badge in
   an alliance cell shares the available space equally and shrinks/truncates
   instead of wrapping onto a second line — see .match-alliance. */
.team-badge-detailed {
  display: flex;
  flex-direction: column;
  gap: 1px;
  cursor: pointer;
  padding: 2px 8px 3px;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.03);
  flex: 1 1 0;
  min-width: 0;
  max-width: none;
}

.team-badge-detailed:hover {
  background: rgba(214, 31, 44, 0.08);
}

.team-badge-detailed-top {
  display: flex;
  align-items: center;
  gap: 8px;
}

.team-badge-detailed-img {
  width: 26px;
  height: 26px;
  border-radius: 6px;
  object-fit: cover;
  flex-shrink: 0;
  display: block;
}

/* Match details' team number/name are kept at their PRE-scale size even
   though html's root font-size is now 120% (see the `html { font-size:
   120%; }` rule near the top) — calc() divides back out that 1.2x so these
   two render at exactly the same absolute size they did before, while
   everything else on the page still scaled up normally. */
.team-badge-detailed-num {
  font-family: monospace;
  font-weight: 700;
  font-size: calc(1.1rem / 1.2);
}

.team-badge-detailed-name {
  font-size: calc(0.88rem / 1.2);
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Keep the winning alliance's highlight consistent across the team
   number/code AND the name below it — the name needs this explicit
   override since it sets its own muted color otherwise, and against a
   solid red/blue winner background it needs to be white, not muted grey. */
.match-winner .team-badge-detailed-name {
  color: rgba(255, 255, 255, 0.9);
}

/* A 3- or 4-team alliance (e.g. a drafted backup robot in playoffs) has to
   fit one or two more badges into the same cell width as a normal 2-team
   alliance — shrink the team number/name (Detailed mode) and team code
   (Compact mode) text by 20% in either case so it still fits comfortably. */
.team-badge-detailed.alliance-compact .team-badge-detailed-num {
  font-size: calc(1.1rem / 1.2 * 0.8);
}

.team-badge-detailed.alliance-compact .team-badge-detailed-name {
  font-size: calc(0.88rem / 1.2 * 0.8);
}

.team-code-badge.alliance-compact .team-code-badge-code {
  font-size: calc(1.05rem * 0.8);
}


table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92rem;
}

th, td {
  text-align: left;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
}

/* Teams list — tighter row spacing than the site's default table padding
   above, since this table is mostly short tokens (rank, number, code,
   OPR figures) with a lot of open vertical space at the default size. */
/* Was 4px 10px (tighter than every other table) — matches the sitewide
   default th,td padding (8px 10px) now, same row spacing as Top Matches
   and every other tab's table instead of looking noticeably denser. */
#teams-result th, #teams-result td {
  padding: 8px 10px;
}

/* Same 0.8em as .top-score-matches-table (Top Matches tab) — both tables
   sit at the same DOM depth with no font-size override in between, so an
   identical relative unit renders identically, keeping the two tabs'
   typography consistent per request (was a smaller, differently-derived
   calc() value before, so the two tabs read at slightly different sizes).
   th font-size is intentionally left alone here — same as Top Matches,
   its header falls back to the sitewide th{font-size:0.82rem} rule rather
   than getting its own override. */
#teams-result table {
  font-size: 0.8em;
  table-layout: fixed;
}

#teams-result td {
  font-weight: 400;
}

/* No cell's text wraps past one line — truncates with an ellipsis instead
   (see .teams-result-name-text below for the Team column specifically,
   which needs the ellipsis on an inner <span> rather than the <td> itself
   since its content is a flex row, not plain text). */
#teams-result th,
#teams-result td {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.teams-result-name-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0; /* lets it actually shrink below its content width inside the flex row, or ellipsis never kicks in */
}

/* Column widths — OPR Event (the freeform event-name column, historically
   the widest since nothing constrained it) reduced to half a "fair share"
   per request, with the difference given to the other columns, most of it
   to Team (the other freeform column) and WD Rank (its "1234 (Top 1%)"
   text needs the most room of the short columns). */
#teams-result th:nth-child(1),
#teams-result td:nth-child(1) {
  width: 4.89%; /* was 4.526%, given the space freed from Team (-2%) */
}

#teams-result th:nth-child(2),
#teams-result td:nth-child(2) {
  width: 6.486%; /* was 6%, given the space freed from OPR Event (-2%) */
}

#teams-result th:nth-child(3),
#teams-result td:nth-child(3) {
  width: 17.836%; /* was 18.2%, cut 2% per request; freed space given to the first (rank) column */
}

#teams-result th:nth-child(4),
#teams-result td:nth-child(4),
#teams-result th:nth-child(5),
#teams-result td:nth-child(5),
#teams-result th:nth-child(6),
#teams-result td:nth-child(6) {
  width: 9%;
}

#teams-result th:nth-child(7),
#teams-result td:nth-child(7) {
  width: 9.45%; /* was 10.5%, cut 10% per request; freed space given to the first (rank) column */
}

#teams-result th:nth-child(8),
#teams-result td:nth-child(8) {
  width: 23.338%; /* was 23.814%, cut 2% per request; freed space given to the first (rank) column */
}

th {
  color: var(--muted);
  font-weight: 600;
  font-size: 0.82rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

tr.championship-row {
  background: rgba(214, 31, 44, 0.05);
  box-shadow: inset 4px 0 0 0 var(--red);
}

tr.championship-row td {
  font-weight: 600;
}

tr.championship-row:hover {
  background: rgba(214, 31, 44, 0.1);
}

/* Teams in the top 1% of world rank by OPR (see searchTeams' isTopOpr) —
   a very light tint of the site's red accent, with a red bar on the left
   edge (same var(--red) as the site's red accent). Half the row's height,
   vertically centered — a layered background-image (not the box-shadow
   trick used before) since that's the only way to size/center a partial-
   height bar on a <tr>, which can't hold a real positioned child element
   the way the "Elite Teams" legend swatch in index.html can. Matches that
   swatch's same half-height/centered treatment. */
tr.top-opr-row {
  background: linear-gradient(var(--red), var(--red)) no-repeat left center / 4px 50%, #fff;
}

tr.top-opr-row td {
  font-weight: 600;
}

tr.top-opr-row:hover {
  background: linear-gradient(var(--red), var(--red)) no-repeat left center / 4px 50%, rgba(214, 31, 44, 0.1);
}

/* Country/Region Analysis table (one row per country/state) — table-layout
   fixed with explicit PIXEL widths on every column (including the name
   column, not just the four data columns) so they render the same width
   as each other regardless of how wide their own content happens to be —
   otherwise a longer number like "5,963" would make its column wider than
   a short one like "64", throwing off the bar-width comparisons those
   columns are built around (see numberBarCell in app.js).
   `table-layout: fixed` on a <table> with no explicit width of its own
   stretches to fill its container (a table-specific quirk — plain
   `width: auto` tables normally shrink-to-fit, but fixed-layout ones
   don't), which is what kept leaving/widening the empty gap between the
   name column and the data columns even after removing `width: 100%` —
   the leftover space just landed in the name column instead, since it was
   the only one left as `width: auto`. Giving the table an explicit total
   width (the exact sum of all five columns below) fixes that: nothing is
   left auto, so there's no leftover space for the browser to dump
   anywhere. Name column trimmed down to 150px (most rows only need a flag
   + a 2-3 letter code — see abbreviateCountry) since that's what was
   actually behind the remaining visible gap: a fixed-width column doesn't
   stretch its own content, so a short "US" left blank space out to the
   column's edge before the next column began — the header row (which
   spans the full column width regardless of any one row's content) just
   made that same gap look intentional. Data columns widened to 156px
   (130px + another 20% per request; 108px originally) so the two-line
   "% Elite Teams"/"% Worldwide Teams" headers have room to fit without
   crowding into each other. */
.elite-summary-table {
  table-layout: fixed;
  width: 870px; /* 90 (name column, narrowed to fit flag+abbreviation) + 156 * 5 (data columns, now including World Teams) */
}

.elite-summary-table th,
.elite-summary-table td {
  white-space: nowrap;
  text-align: center;
  width: 156px;
}

/* Overrides the site-wide th{text-transform:uppercase} (see th{} above) —
   scoped to just this table per request, rather than changing every other
   table's header styling site-wide. Headers here read as normal title
   case ("Elite Teams"), matching how they're actually written in
   buildEliteSummaryTable in app.js. */
.elite-summary-table th {
  text-transform: none;
}

.elite-summary-table th:first-child,
.elite-summary-table td:first-child {
  width: 90px;
  padding-right: 16px;
}

.elite-summary-table td:first-child {
  font-weight: 600;
  color: var(--muted);
  text-align: left;
}

.pill {
  display: inline-block;
  background: #fbe9e9;
  color: var(--red-dark);
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
}

.muted { color: var(--muted); }

.state {
  padding: 30px 0;
  text-align: center;
  color: var(--muted);
}

.error {
  background: #fdecea;
  border: 1px solid #f5c2c0;
  color: #8a1f1a;
  padding: 12px 14px;
  border-radius: 8px;
  margin-bottom: 16px;
}

footer {
  max-width: 1000px;
  margin: 0 auto;
  padding: 20px 24px 40px;
  color: var(--muted);
  font-size: 0.85rem;
}

footer a { color: var(--red-dark); }

.team-detail .meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 10px;
  margin-bottom: 14px;
}

.team-detail .meta div {
  background: var(--bg);
  border-radius: 8px;
  padding: 10px 12px;
}

.team-detail .meta .label {
  font-size: 0.75rem;
  color: var(--muted);
  text-transform: uppercase;
}

.team-detail .meta a {
  color: var(--red-dark);
  text-decoration: none;
  font-weight: 600;
}

.team-detail .meta a:hover {
  text-decoration: underline;
}

.back-link {
  background: none;
  border: none;
  color: var(--red-dark);
  cursor: pointer;
  padding: 0;
  font-size: 0.9rem;
  margin-bottom: 12px;
}

.pager {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
}

/* "Page X of X" label (see renderPager in app.js, used by every paginated
   list on the site) — 15% smaller than its ambient text size. */
.pager-label {
  font-size: 0.85em;
}

.pager button {
  background: #fbe9e9;
  border: 1px solid var(--border);
  color: var(--red-dark);
  padding: 5px 10px; /* 15% smaller than the previous 6px/12px */
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.72rem; /* 15% smaller than the previous 0.85rem */
}

.pager button:disabled {
  opacity: 0.4;
  cursor: default;
}

/* ============================================================
   MOBILE — everything below only applies at phone widths. Placed at the
   very end of the file (not scattered next to each rule it touches) so
   it always wins the cascade against equal-specificity desktop rules
   defined earlier, without needing !important anywhere. Two things this
   deliberately does NOT attempt: rewriting any table into a stacked-card
   layout (every table below scrolls horizontally instead — far less
   code, and keeps every column's data visible without a rewrite of each
   render function), and a from-scratch mobile redesign. This is a
   targeted pass at the specific things that were actually broken:
   the header's 3-column grid, the sticky stack eating the screen, tab
   bars wrapping their long labels onto multiple lines, and tables
   squeezing their columns into unreadable widths (or overlapping,
   exactly the ST/RG bug fixed earlier this session) instead of just
   being wider than the screen.
   ============================================================ */
/* Blinks the event title when the live-sync poller (lib/liveSync.js) has a
   pull due within 2 minutes — see refreshLiveStatus() in app.js, which
   toggles this class based on the /live-status endpoint's nextPullAt. Only
   ever applied on desktop (the status row it's driven by is desktop-only,
   see .event-live-status below), so no mobile-specific handling needed. */
@keyframes event-title-blink {
  0%, 100% { opacity: 1; color: inherit; }
  50% { opacity: 0.35; color: var(--red); }
}
.event-title-blinking {
  animation: event-title-blink 1s ease-in-out infinite;
}

@media (max-width: 700px) {
  /* Event detail page, mobile only — every table (Qualification/Playoff/
     Leaderboard/Advancement/Scouting) is excluded from this and stays at
     its normal size (see the counter-zoom below); everything else on the
     page (labels, the address/website rows, tab bar, buttons, etc.) gets
     10% smaller. `zoom` (not font-size%) is used because this page mixes
     rem/em/px sizing throughout — a single zoom on the container scales
     all of it uniformly in one place, same technique already used for
     the Events list page's own font-size reduction (see #events-list-view
     elsewhere in this file). */
  #event-detail-view {
    zoom: 0.9;
  }
  /* Cancels the 0.9 zoom above specifically for tables — 1 ÷ 0.9 — so
     they render at their true original size instead of also shrinking
     10%, since `zoom` on a container multiplies through everything
     nested inside it by default. */
  #event-detail-view table {
    zoom: 1.1111;
  }
  /* The event's own name heading gets a DEEPER cut (15%, not 10%) per
     request. It's already inside the 0.9-zoomed container above, so its
     own zoom only needs to make up the remaining difference to reach a
     true 15% off the original: 0.85 ÷ 0.9 ≈ 0.9444 (0.9 × 0.9444 = 0.85).
  */
  .event-detail-title {
    zoom: 0.9444;
  }

  /* Last-pulled/next-pull row (see app.js's refreshLiveStatus) is desktop
     only per request — hidden here rather than just left small, since it's
     status chrome, not content someone reads on a phone mid-match. */
  .event-live-status {
    display: none !important;
  }

  /* Event Leaderboard tab, mobile only — drop every column literally
     labeled "OPR" (Total/OPR, np/OPR, Auto/OPR, Tele/OPR; see
     leaderboardColumns in app.js). Shifted from columns 6-9 to 7-10 after
     "#/Match" was inserted right after Avg RP (was column 4, now 5 — every
     nth-child in this whole mobile block shifted +1 from that point on).
     OPR/Conf (the former column 11) was later removed from the column set
     entirely (desktop and mobile both), so it no longer needs a hide rule
     here at all. Kept on mobile: #, Team, Avg RP, #/Match, W-L-T, Avg Pts,
     Max/npPts (column 11 — formerly Playoff X). Explicit
     width:0/padding:0/border:none alongside display:none — display:none
     alone doesn't reliably collapse a column's reserved width. */
  .opr-leaderboard table th:nth-child(7),
  .opr-leaderboard table td:nth-child(7),
  .opr-leaderboard table th:nth-child(8),
  .opr-leaderboard table td:nth-child(8),
  .opr-leaderboard table th:nth-child(9),
  .opr-leaderboard table td:nth-child(9),
  .opr-leaderboard table th:nth-child(10),
  .opr-leaderboard table td:nth-child(10) {
    display: none;
    width: 0;
    padding: 0;
    border: none;
  }

  /* Team logo/code stacked (code under logo) — same pattern used
     elsewhere on this page (Scouting/Advancement tables). table-
     layout:fixed + explicit widths on every VISIBLE column (auto layout
     can't guarantee an exact cut). Column 1 (#) was later cut by a
     further 80% RELATIVE to its own width here (18.3333% × 0.2 =
     3.6667%, per a follow-up request), with the freed 14.6666 points
     split evenly across the other 5 visible columns (+2.93332 each,
     since that request likewise didn't name a destination) — Team:
     8.3333% → 11.26662%, Avg RP/W-L-T/Avg Pts/Max-npPts: 18.3333% →
     21.26662% each. */
  .opr-leaderboard table {
    table-layout: fixed;
  }
  .opr-leaderboard .leaderboard-team {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
  }
  /* Column 1 (#) increased 50% RELATIVE to its own prior width (3.6667%
     × 1.5 = 5.50005%), with the added 1.83335 points taken evenly from
     the other 5 visible columns (-0.36667 each) → Team 10.89995%, Avg
     RP/W-L-T/Avg Pts/Max-npPts 20.89995% each. Then, per a later request,
     the three plain-number columns (Avg RP, Avg Pts, Max npPts — NOT
     W-L-T, a compound record, and NOT #, just a display position) were
     each cut a further 10% RELATIVE (20.89995% × 0.9 = 18.809955%), with
     all 3×2.089995 = 6.269985 points freed going to Team (10.89995% →
     17.169935%). W-L-T stays at 20.89995%. */
  /* 7 visible columns now (was 6, before "#/Match" was inserted) — every
     existing column's width scaled down by 6/7 of its old value, with the
     freed 1/7 of the row going to the new column (100/7 ≈ 14.285714%),
     keeping the same relative proportions between the pre-existing
     columns intact. */
  .opr-leaderboard table th:nth-child(1),
  .opr-leaderboard table td:nth-child(1) { width: 4.71432%; }
  .opr-leaderboard table th:nth-child(2),
  .opr-leaderboard table td:nth-child(2) { width: 14.71708%; }
  .opr-leaderboard table th:nth-child(3),
  .opr-leaderboard table td:nth-child(3) { width: 16.12282%; }
  .opr-leaderboard table th:nth-child(4),
  .opr-leaderboard table td:nth-child(4) { width: 14.28571%; }
  .opr-leaderboard table th:nth-child(5),
  .opr-leaderboard table td:nth-child(5) { width: 17.91424%; }
  .opr-leaderboard table th:nth-child(6),
  .opr-leaderboard table td:nth-child(6) { width: 16.12282%; }
  .opr-leaderboard table th:nth-child(11),
  .opr-leaderboard table td:nth-child(11) { width: 16.12282%; }

  /* "Avg/RP", "#/Match", "Avg/Pts", "Max/npPts" headers should render in
     their original mixed case, but the site-wide `th { text-transform:
     uppercase; }` rule (see th{} elsewhere in this file) was still
     shouting them in all caps even after the earlier lowercase override
     was removed — a plain element selector still beats "nothing".
     Explicit text-transform:none here overrides it, same pattern already
     used for a few other tables' headers (see .top-score-matches-table th
     and similar "Overrides the site-wide th{text-transform:uppercase}"
     comments elsewhere in this file). */
  .opr-leaderboard table th:nth-child(3),
  .opr-leaderboard table th:nth-child(6),
  .opr-leaderboard table th:nth-child(11) {
    text-transform: none;
  }

  /* Award tab keeps its two-column layout on mobile too (see .award-list,
     non-mobile) — long award names and team/person names wrap onto extra
     lines instead of overflowing a narrower column, rather than dropping
     to one column. min-width:0 is needed alongside the wrapping itself —
     a flex item's default min-width:auto otherwise refuses to shrink
     below its text's natural unbroken width no matter what white-space/
     overflow-wrap say. */
  .award-group-name span:last-child,
  .award-winner-row .award-winner,
  .award-winner-row .leaderboard-team-fullname {
    white-space: normal;
    overflow-wrap: break-word;
    min-width: 0;
  }
  .award-group-name span:last-child,
  .award-winner-row > * {
    min-width: 0;
  }

  /* Scales every rem-sized font on the site down 15% on mobile — same
     mechanism as the root html{font-size:120%} rule near the top of this
     file (that comment explains why rem-based scaling was chosen: one
     change here affects every rem-sized font at once instead of hand-
     editing each declaration). 120% * 0.85 = 102%. Text sized in em
     (the various tables at 0.8em/0.9em/etc.) rides along automatically
     too, since em is relative to this same inherited value. */
  html {
    font-size: 102%;
  }

  /* Nothing stays locked/pinned on mobile — the header, the Season/
     Country/State filters bar, and the Teams/Events tab bars all scroll
     away normally instead of sticking to the top of the viewport. On a
     phone the combined sticky stack ate a large share of the (already
     short) screen before any content showed; scrolling them away trades
     "always reachable" for "more room to actually see the page." */
  header.site-header,
  .page-filters,
  #teams-tab-bar,
  #timeline-tab-bar {
    position: static;
  }

  /* Header: the desktop 3-column grid (brand | search | nav) is wider
     than a phone screen on its own just from the search box's 320px
     width plus the nav pills — restacked into two rows (brand+nav on
     top, search filling its own row below) via flex-wrap instead. */
  .site-header-inner {
    display: flex;
    flex-wrap: wrap;
    padding: 10px 14px;
    gap: 8px 12px;
  }

  .site-header-inner .brand {
    order: 1;
  }

  .site-header-inner nav.tabs {
    order: 2;
    margin-left: auto;
  }

  .site-header-inner .header-search {
    order: 3;
    flex: 1 1 100%;
    width: 100%;
  }

  header.site-header .brand-word {
    font-size: 0.95rem;
  }

  header.site-header .brand img {
    width: 30px;
    height: 30px;
  }

  /* Season/Country/State filters — each dropdown shrinks to whatever fits
     2-3 to a row instead of the desktop's wide fixed-content sizing, and
     the sticky stack's padding tightens up so it doesn't eat half the
     screen before any actual content shows. */
  main {
    padding: 12px;
  }

  .card {
    padding: 12px 14px;
  }

  .page-filters {
    gap: 10px 14px;
  }

  .dd {
    flex: 1 1 130px;
  }

  .dd-trigger {
    width: 100%;
    justify-content: space-between;
  }

  /* Keeps the dropdown's own popup from running off the right edge of a
     narrow screen when its trigger sits near the edge (the popup is
     absolutely positioned at left:0 of the trigger, so it doesn't
     automatically know how close to the viewport edge it is). */
  .dd-menu {
    max-width: calc(100vw - 32px);
  }

  /* Tab bars (Top Teams/Top Matches/etc., Live/Upcoming/etc., a team
     profile's Qualification/Playoff/Award) — labels are long enough
     ("Country/region Match Analysis") that wrapping them onto multiple
     lines looks broken, so this scrolls horizontally (swipeable) instead,
     same pattern as the tables below. */
  .team-event-tab-bar {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .team-event-tab-btn {
    flex-shrink: 0;
  }

  /* All four tabs squeeze into one row without horizontal scrolling —
     overrides the shared .team-event-tab-bar nowrap+overflow-x:auto/
     flex-shrink:0 rules above (ID beats class, so this wins) with a
     shrink-to-fit layout instead: short labels (see
     .timeline-tab-label-short usage in index.html — TopTeam/TopMatch/
     RegRankTeam/RegRank Match) and a smaller font. Buttons are sized to
     their own content (not flex:1/equal-width) — equal-width buttons left
     a lot of dead centered space around the two short labels (TopTeam/
     TopMatch), which read as an oversized gap between them; content-sized
     buttons with a small fixed gap put the spacing where it's actually
     controlled. */
  #teams-tab-bar {
    padding: 8px 10px 0;
    /* flex-start (not space-between) — space-between stretches the gaps to
       fill the whole row's leftover width, which is exactly what made the
       gap after the two short labels look oversized. flex-start packs the
       four buttons together with only the fixed `gap` below between them. */
    justify-content: flex-start;
    overflow: visible;
    gap: 4px;
  }

  #teams-tab-bar .team-event-tab-btn {
    flex: 0 1 auto;
    min-width: 0;
    padding: 4px 1px;
    font-size: 0.726rem; /* 0.66rem +10% */
    text-align: center;
    white-space: nowrap;
  }

  /* Reg.Rank by Team's table (.elite-summary-table) had no font-size
     override of its own, so it rendered at the page's normal table text
     size — noticeably bigger than Top Teams (#teams-result) and Top
     Matches/Reg.Rank by Match (.top-score-matches-table/
     .match-analysis-table), which both already use 0.8em. Matches that
     same 0.8em here so all four Teams sub-tabs read at a consistent size. */
  .elite-summary-table {
    font-size: 0.8em;
    /* 60 (row label, cut by 1/3 below) + 78*5 (numeric columns, halved
       below) = 450px. */
    width: 450px;
  }

  /* Every numeric column's width cut in half (156px -> 78px) — text-align
     is already centered, so the halved column just reads as a tighter,
     denser table rather than clipping anything. */
  .elite-summary-table th:not(:first-child),
  .elite-summary-table td:not(:first-child) {
    width: 78px;
  }

  /* Row-label (Country/State) column cut by 1/3 (90px -> 60px) — pairs
     with stacking the flag above the name below (.region-flag-label),
     which needs much less horizontal room than the old side-by-side
     layout. Padding trimmed too so the stacked content isn't squeezed. */
  .elite-summary-table th:first-child,
  .elite-summary-table td:first-child {
    width: 60px;
    padding-right: 6px;
  }

  /* Flag stacks above the name/code instead of sitting beside it — same
     layout this table used to have before it became a wide multi-column
     table (see the comment on rowLabelContent in updateEliteSummary) —
     brought back on mobile now that both columns are much narrower.
     !important needed since the flex-row is set inline in app.js. */
  .region-flag-label {
    flex-direction: column !important;
    align-items: center !important;
    gap: 2px !important;
    text-align: center;
  }

  /* "Total Teams" -> "Total" / "Teams" two-row headers (and the same for
     the other four numeric columns) — real text hidden via font-size:0 and
     replaced with generated content instead of changing it in app.js, same
     trick as the match table's RED/BLUE headers and the event page's
     Qualify tab. `\A` + white-space:pre in the content string is what
     forces the second line. */
  .elite-summary-table th:not(:first-child) {
    font-size: 0;
  }

  .elite-summary-table th:not(:first-child)::after {
    display: inline-block;
    white-space: pre;
    /* Absolute unit, not em — the th itself is font-size:0 (that's what
       hides its real text), and an em value here would compute against
       that inherited 0, collapsing this pseudo-element's text to 0 too.
       This was the actual bug behind "I cannot see the header" — the
       two-line label was rendering at literally zero size. */
    font-size: 0.7rem;
    line-height: 1.15;
    /* These columns also carry the sortable-th class (see makeSortableTable),
       whose own ::after rule (the ⇅ sort-hint icon) sets position:absolute
       + opacity:0.35 + right:2px. CSS cascades per PROPERTY, not per rule —
       since this rule never declared those three properties, that other
       rule's values were still winning and applying to THIS content too,
       which is what caused the header to render faded and floating out of
       the row's normal flow (overlapping the row below) instead of sizing
       its own header row. Explicitly resetting all three here is what
       actually fixes the misplaced/washed-out header from the screenshot. */
    position: static;
    right: auto;
    opacity: 1;
    color: var(--muted);
  }

  .elite-summary-table th:nth-child(2)::after { content: "Total\ATeams"; }
  .elite-summary-table th:nth-child(3)::after { content: "World\ATeams"; }
  .elite-summary-table th:nth-child(4)::after { content: "Elite\ATeams"; }
  .elite-summary-table th:nth-child(5)::after { content: "% Elite\ATeams"; }
  .elite-summary-table th:nth-child(6)::after { content: "%WD\ATeam"; }

  /* Tables built with table-layout:fixed + desktop-width column
     percentages (Top Teams, Top Matches, Match Analysis) squeeze their
     columns into unreadable widths — or outright overlap, as happened
     with ST/RG earlier — at phone widths rather than naturally
     overflowing, since percentages just get smaller instead of
     triggering a scrollbar. Giving each table a fixed min-width (the
     approximate desktop width its columns were actually designed for)
     and making its container scroll horizontally fixes that: the columns
     keep their designed proportions and stay readable, and the whole
     table becomes swipeable instead of crushed. Elite/Country-Region
     Analysis isn't listed here since it already uses this same pattern
     unconditionally (see the overflow-x:auto wrapper div built in
     updateEliteSummary in app.js). */
  #teams-result {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  #teams-result table {
    min-width: 640px;
  }

  /* Mobile-only column re-split: rank (1st) and # (2nd) columns get 10%
     more room each (relative increase over their desktop widths); Team
     (3rd) shrinks way down since it now shows logo + short code instead
     of the full name (see .teams-result-name-text/-code above) — no
     longer needs anywhere near its desktop width. Every column after
     Team keeps the same relative proportions to each other as desktop
     but expands to fill the width Team gave up, which reads as those
     columns "sliding left" into the freed space. Wins over the desktop
     nth-child rules above via source order (same specificity, later in
     the cascade) — same pattern used elsewhere in this file. */
  #teams-result th:nth-child(1),
  #teams-result td:nth-child(1) {
    width: 5.4%; /* 4.89% desktop, +10% */
  }

  #teams-result th:nth-child(2),
  #teams-result td:nth-child(2) {
    width: 9.7%; /* 8.17% (the prior mobile width), +1.53% taken from OPR per later request */
  }

  #teams-result th:nth-child(3),
  #teams-result td:nth-child(3) {
    width: 6.3%; /* was 9% here, cut 30% per later request (originally 17.836% desktop) */
  }

  /* ST/RG (4th) shows the combined flag+region-letters content (see the
     combined-column rules further below) — its content (one small flag +
     2-3 letters) doesn't need anywhere near 16.52%, which is what was
     actually causing the visible gap before OPR, not the hidden Country
     column (that one's already zeroed out below). Shrunk down to fit. */
  #teams-result th:nth-child(4),
  #teams-result td:nth-child(4) {
    width: 9%;
  }

  /* width:0 (not just display:none) — table-layout:fixed can still
     reserve a hidden cell's specified column width in some browsers. */
  #teams-result th:nth-child(5),
  #teams-result td:nth-child(5) {
    display: none;
    width: 0;
    padding: 0;
    border: none;
  }

  /* OPR/WD Rank/OPR Event scaled up further to consume the space Location
     gave up above — closes the gap between Location and OPR. OPR then cut
     10% again per a later request, with that space going to the # (team
     number) column above instead. */
  #teams-result th:nth-child(6),
  #teams-result td:nth-child(6) {
    width: 13.76%; /* 15.29% * 0.9 */
  }

  #teams-result th:nth-child(7),
  #teams-result td:nth-child(7) {
    width: 16.07%;
  }

  #teams-result th:nth-child(8),
  #teams-result td:nth-child(8) {
    width: 39.78%;
  }

  #top-score-matches-list,
  #match-analysis-list {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .top-score-matches-table,
  .match-analysis-table {
    min-width: 640px;
  }

  .match-schedule {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .match-schedule table {
    min-width: 480px;
  }

  /* Team profile and event page Qualification/Playoff tables: with both
     score columns hidden (see the mobile-compact-match-schedule rules above),
     only Match/Red Alliance/Blue Alliance remain — that's narrow enough
     to fit a phone screen without horizontal scrolling, so this overrides
     the generic .match-schedule table min-width (480px) that would
     otherwise force a scrollbar it no longer needs. Match-number column
     and cell padding are also trimmed to give the two alliance columns a
     bit more breathing room. */
  .mobile-compact-match-schedule {
    overflow-x: visible;
  }

  .mobile-compact-match-schedule table {
    min-width: 0;
    width: 100%;
  }

  .mobile-compact-match-schedule th,
  .mobile-compact-match-schedule td {
    padding: 4px 3px;
  }

  .mobile-compact-match-schedule table th:first-child,
  .mobile-compact-match-schedule table td:first-child {
    width: 40px;
    font-size: calc(0.8rem / 1.2);
  }

  /* Pager/tab buttons sized for mouse precision on desktop fall under
     Apple's ~44px minimum touch-target guidance on a phone — bumped up
     just enough to comfortably tap without also looking oversized. */
  .pager button {
    padding: 8px 12px;
  }

  .team-event-tab-btn {
    padding: 10px 6px;
  }

  /* Team profile page — the Instagram/team-code/Save/Show-Videos row isn't
     useful on a phone (nobody's editing team metadata from their pocket)
     and was also the source of extra clutter at the bottom of the header
     card, so it's hidden entirely per request. !important is needed here
     since these elements are built with inline `style` in app.js (see
     showTeamDetail/renderTeamEventMatches) — inline styles otherwise beat
     any external stylesheet rule regardless of selector specificity. */
  .team-ig-code-row {
    display: none !important;
  }

  /* Organization name/logo is a desktop-only addition (per request) —
     hidden on mobile for the same reason as the ig-code row above: no
     room, not something worth editing/viewing from a phone, and it would
     otherwise break the photo/OPR-box same-row grid guarantee described
     below since .team-profile-photo-col would grow taller. */
  .team-profile-org-info {
    display: none !important;
  }

  /* The header row (photo, name+location, rank-navigation arrows, OPR/
     rank stat box) is built with all four side by side in one flex row
     (see headerTop in showTeamDetail) — fine on desktop, but on a phone
     headerContent (the name+location column) got squeezed down to a
     sliver, forcing the team name to wrap 3-4 lines deep while the OPR
     box — vertically centered against that now-very-tall column — ended
     up floating overlapped on top of the location text below it.
     A first attempt fixed this with flex-wrap + `order` + a
     flex-basis:100% trick to force headerContent onto its own line —
     but flexbox's own line-breaking heuristic turned out to ALSO bump
     the OPR box onto its own separate line whenever its content (long
     World/Country/Region rank text) was wide, even with min-width:0 set,
     which is exactly the bug reported: photo/code and the OPR box
     splitting across two rows instead of guaranteed sharing one.
     CSS Grid with named areas replaces that — "photo+OPR box share row
     1, headerContent gets all of row 2" is now the actual grid structure
     (rank arrows are hidden entirely on mobile, see
     .team-profile-rank-arrows below, so no area is needed for them), not
     something the browser decides based on content width. The OPR box's
     grid cell can still shrink (minmax(0, 1fr) on that column, plus
     min-width:0 on the box itself below) but it can never escape onto a
     different row the way a wrapping flex item could. */
  .team-profile-header-top {
    display: grid !important;
    grid-template-columns: auto minmax(0, 1fr) !important;
    grid-template-areas: "photo opr" "content content" !important;
    align-items: center !important;
    gap: 10px !important;
  }

  .team-profile-photo-col {
    grid-area: photo;
  }

  .team-profile-opr-box-container {
    grid-area: opr;
    min-width: 0;
  }

  .team-profile-header-content {
    grid-area: content;
  }

  /* Number/rank-line fonts kept at full desktop size (see below) — the
     grid structure above already guarantees the box can never wrap to
     its own row, so there's no need to shrink most of its contents to
     fit. min-width:0 stays so the grid column math still works. The
     gap between the OPR number and the World/Country/State rankings IS
     tightened here though (14px -> 6px), per a follow-up request — the
     inline style in renderHeaderStatBox (app.js) sets it for desktop,
     hence !important to override it on mobile specifically. */
  .team-profile-opr-box {
    min-width: 0 !important;
    width: 100%;
    gap: 6px !important;
  }

  .team-profile-rank-lines {
    padding-left: 6px !important;
  }

  /* OPR box text, 10% smaller on mobile (desktop sizes: 1.5rem number,
     0.8rem rank lines, 0.7rem "npOPR" label — all set inline in
     renderHeaderStatBox in app.js, hence !important). */
  .team-profile-opr-number {
    font-size: 1.35rem !important;
  }

  .team-profile-rank-line {
    font-size: 0.72rem !important;
  }

  .team-profile-opr-label {
    font-size: 0.63rem !important;
  }

  .team-profile-name {
    font-size: 1.3rem !important;
  }

  /* The 5-stat dashboard summary bar (active teams/World Teams/Elite
     Teams/etc. — see #teams-dashboard, toggled via inline style in
     updatePageFiltersVisibility in app.js) is dropped on mobile per
     request — five stat blocks side by side just wrap into a cramped,
     hard-to-read stack on a phone screen. !important needed since it's
     shown/hidden via inline `style.display`, which otherwise beats any
     external rule regardless of specificity. */
  #teams-dashboard {
    display: none !important;
  }

  /* Season tabs (see .season-tab-bar/.season-tab-btn) show just the logo
     on mobile — the year label is dropped to keep each tab compact enough
     for several seasons' worth of tabs to fit on a phone width. Full
     "YYYY — GAME NAME" is still available as the button's title tooltip. */
  .season-tab-year {
    display: none;
  }

  /* One row, no wrapping — sized so ~5 logo-only tabs fit across a phone
     screen at once; anything beyond that scrolls horizontally instead of
     wrapping to a second row. */
  .season-tab-bar {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    gap: 8px;
    margin-bottom: 10px;
  }

  .season-tab-btn {
    padding: 6px 10px;
    flex-shrink: 0;
  }

  .season-tab-logo {
    width: 22px;
    height: 22px;
  }

  /* "In-season Progress" and "YoY Progress" chart toggles (see
     .season-progress-btn variants and .season-progress-box in
     showTeamDetail) are dropped on mobile per request — both are wide
     line charts with a "Compare with" sidebar that don't fit usefully on
     a phone screen. !important needed since seasonProgressBox/
     yoyProgressBox toggle between inline display:none and display:''
     when opened/closed. */
  .yoy-progress-btn,
  .in-season-progress-btn {
    display: none !important;
  }

  .season-progress-box {
    display: none !important;
  }

  /* Team profile AND event page Qualification/Playoff match tables (see
     .mobile-compact-match-schedule in showTeamDetail/renderEventForTeam and
     showEventDetail) — drop both score columns (Match | Red score | Red
     Alliance | Blue score | Blue Alliance → keep 1/3/5) and shrink each
     team badge down to just logo + team code, stacked vertically. The one
     difference between the two pages is the match label itself: the team
     profile passes forTeamNumber to renderMatchTable, so its label gets a
     "(W/L/T)" suffix; the event page doesn't, so its label is just
     "Q1"/"P1". */
  .mobile-compact-match-schedule table th:nth-child(2),
  .mobile-compact-match-schedule table td:nth-child(2),
  .mobile-compact-match-schedule table th:nth-child(4),
  .mobile-compact-match-schedule table td:nth-child(4) {
    display: none;
  }

  /* Match column widened slightly to fit the label + two score rows below
     (see the Match column content changes next), taken proportionally
     from the two Alliance columns since the table is fixed-layout. */
  .mobile-compact-match-schedule table th:nth-child(1),
  .mobile-compact-match-schedule table td:nth-child(1) {
    width: 60px;
  }

  /* .match-schedule-two-team (see twoTeamOnly in app.js) — when every
     match in this table has a normal 2-team alliance on both sides (no
     drafted-backup-robot playoff match anywhere), the Alliance columns
     don't need room for a 3rd badge, so shrink them 10% (34% -> 30.6%)
     and give the Match column (which on mobile also shows the match
     results — see .match-number-mobile-score below) the freed space. The
     Match column's own width is px-based (not %, see the base rule right
     above) while the Alliance columns are %-based, so its growth here is
     an approximation of that freed 2x3.4% rather than an exact
     percentage-preserving conversion between the two units. */
  .mobile-compact-match-schedule table.match-schedule-two-team th:nth-child(3),
  .mobile-compact-match-schedule table.match-schedule-two-team td:nth-child(3),
  .mobile-compact-match-schedule table.match-schedule-two-team th:nth-child(5),
  .mobile-compact-match-schedule table.match-schedule-two-team td:nth-child(5) {
    width: 30.6%;
  }

  .mobile-compact-match-schedule table.match-schedule-two-team th:first-child,
  .mobile-compact-match-schedule table.match-schedule-two-team td:first-child {
    width: 86px;
  }

  /* Red/Blue Alliance headers shortened to just "RED"/"BLUE" — the real
     text ("Red Alliance"/"Blue Alliance") is hidden via font-size:0 and
     replaced with generated content instead of changing it in app.js. */
  .mobile-compact-match-schedule table th:nth-child(3),
  .mobile-compact-match-schedule table th:nth-child(5) {
    font-size: 0;
  }

  .mobile-compact-match-schedule table th:nth-child(3)::after {
    content: "RED";
    font-size: calc(0.82rem / 1.2);
  }

  .mobile-compact-match-schedule table th:nth-child(5)::after {
    content: "BLUE";
    font-size: calc(0.82rem / 1.2);
  }

  /* Match column: label stays on its own line (already nowrap by
     default — see .match-number-label), Red/Blue total scores appear as
     two extra rows below it (a scale bar + Ranking Points, see
     .match-number-mobile-score-bar/-rp below), taking over for the
     now-hidden score columns. */
  .mobile-compact-match-schedule .match-number-mobile-score {
    display: flex;
    align-items: center;
    gap: 4px;
  }

  .mobile-compact-match-schedule .team-badge-detailed-top {
    flex-direction: column;
    align-items: center;
    gap: 2px;
  }

  .mobile-compact-match-schedule .team-badge-detailed-num {
    display: none;
  }

  .mobile-compact-match-schedule .team-badge-detailed-code {
    display: block !important;
    font-family: monospace;
    font-weight: 700;
    font-size: calc(0.85rem / 1.2);
  }

  .mobile-compact-match-schedule .team-badge-detailed-name {
    display: none;
  }

  /* Compact mode's badge is already just logo + code — only needs to
     switch from side-by-side to stacked. */
  .mobile-compact-match-schedule .team-code-badge {
    flex-direction: column;
    align-items: center;
    gap: 2px;
  }

  /* Site-wide, mobile-only: every OTHER place a team's full name is
     shown (Teams list, event page match schedule, Top Matches/Match
     Analysis alliance cells, leaderboard, awards, advancement,
     registration — see teamNameWithMobileCode() and the
     teams-result-name-* spans in app.js) swaps to just the team code.
     The team profile page itself is intentionally NOT touched here — its
     own heading still shows the full team name, and its match tables
     already get their own dedicated code-only treatment above. */
  /* !important needed on the hide side too — .top-score-alliance-cell
     .leaderboard-team span (Top Matches/Match Analysis alliance cells)
     sets display:inline-block on every span here with higher selector
     specificity than a plain single-class rule, which would otherwise
     win over this and keep the full name visible. */
  .leaderboard-team-fullname,
  .teams-result-name-text,
  .header-search-suggestion-name-team {
    display: none !important;
  }

  .leaderboard-team-code-mobile,
  .header-search-suggestion-code {
    display: inline !important;
    font-family: monospace;
    font-weight: 700;
  }

  /* "Detailed" mode's team badge (logo+number, name below — see
     teamBadgeDetailed): generic fallback hiding the number/name and
     showing the code inline instead. In practice every match table on the
     site is now scoped by .mobile-compact-match-schedule (team profile and
     event page both), whose own rules above win instead (column-stacked,
     larger code) — this stays as a fallback for any future badge usage
     outside that scope. */
  .team-badge-detailed-num {
    display: none;
  }

  .team-badge-detailed-code {
    display: inline !important;
    font-family: monospace;
    font-weight: 700;
    font-size: calc(0.95rem / 1.2);
  }

  .team-badge-detailed-name {
    display: none;
  }

  /* Top Teams' Team cell now shows just the logo on mobile — the team
     code moved to the # column instead (see .teams-result-number-cell/
     -code below), so this row no longer needs to stack anything; the
     name text itself is already hidden by the site-wide rule above. */
  .teams-result-name-row {
    justify-content: center;
  }

  /* # column: team number on top, team code below (moved here from the
     Team column per request). */
  .teams-result-number-cell {
    text-align: center;
  }

  .teams-result-number-code {
    display: block !important;
    font-family: monospace;
    font-weight: 700;
    font-size: 0.75em;
    /* Spreads the (usually 2-3 letter) code out so its rendered width
       roughly matches the team number above it, instead of sitting much
       narrower and looking off-center under it. */
    letter-spacing: 0.2em;
  }

  /* ST/RG and Country columns (Top Teams and Registration tables — see
     stateCell/countryCell in app.js): flag on top, code below, instead of
     side by side. Country additionally swaps its text from the
     abbreviated country name to the bare 2-letter code, same idea as the
     team-name swaps above. */
  .state-cell-row,
  .country-cell-row {
    flex-direction: column;
    align-items: center;
    gap: 2px;
  }

  .country-cell-full {
    display: none;
  }

  .country-cell-code {
    display: inline !important;
    font-size: 0.85em;
  }

  /* When a flag is actually shown (country-flag span, or the state-flag
     img for US/CA/GB), the code text next to/under it is redundant —
     hide it. Only falls back to showing the plain code text when no flag
     could be resolved (e.g. a country/region with no flag mapping). */
  .country-cell-row .country-flag ~ .country-cell-code {
    display: none !important;
  }

  .state-cell-row .state-flag ~ .state-cell-code {
    display: none !important;
  }

  /* Top Teams only: ST/RG column shows the combined country-flag +
     state/region-letters row instead of the plain state-only version
     (see .state-cell-row-combined in stateCell in app.js) — this is what
     "absorbs" the now-hidden Country column above. Scoped to #teams-result
     specifically so the Registration table (which also calls stateCell)
     keeps its separate State/Country columns exactly as before. */
  /* !important on both — .state-cell-row/-combined both carry an inline
     `style="display:flex"` (see stateCell in app.js), which otherwise
     always wins over a plain external rule regardless of specificity. */
  #teams-result .state-cell-row {
    display: none !important;
  }

  #teams-result .state-cell-row-combined {
    display: flex !important;
  }

  /* Top Matches / Match Analysis Alliance column: now that team names are
     replaced with short codes (see .leaderboard-team-fullname/-code-mobile
     above), the column no longer needs anywhere near its desktop width —
     shrink it down to just fit logo + code, and let every column after it
     (score columns, Date, Event) expand to fill the freed space, which
     reads as them sliding left. Wins over the desktop nth-child rules via
     source order (same specificity, later in the cascade). */
  .top-score-matches-table th:nth-child(2),
  .top-score-matches-table td:nth-child(2) {
    width: 12%;
  }

  .top-score-matches-table th:nth-child(3),
  .top-score-matches-table td:nth-child(3),
  .top-score-matches-table th:nth-child(4),
  .top-score-matches-table td:nth-child(4),
  .top-score-matches-table th:nth-child(5),
  .top-score-matches-table td:nth-child(5),
  .top-score-matches-table th:nth-child(6),
  .top-score-matches-table td:nth-child(6) {
    width: 12.32%;
  }

  .top-score-matches-table th:nth-child(7),
  .top-score-matches-table td:nth-child(7) {
    width: 8.75%;
  }

  .top-score-matches-table th:nth-child(8),
  .top-score-matches-table td:nth-child(8) {
    width: 21.99%;
  }

  /* Country/ST-RG column cut by 1/3 (9% desktop -> 6%) — pairs with
     stacking the flag above the name/code below (.region-flag-label),
     which needs much less horizontal room than the old side-by-side
     layout. */
  .match-analysis-table th:nth-child(2),
  .match-analysis-table td:nth-child(2) {
    width: 6%;
  }

  .match-analysis-table th:nth-child(3),
  .match-analysis-table td:nth-child(3) {
    width: 12%;
  }

  .match-analysis-table th:nth-child(4),
  .match-analysis-table td:nth-child(4),
  .match-analysis-table th:nth-child(5),
  .match-analysis-table td:nth-child(5),
  .match-analysis-table th:nth-child(6),
  .match-analysis-table td:nth-child(6),
  .match-analysis-table th:nth-child(7),
  .match-analysis-table td:nth-child(7) {
    width: 11.31%;
  }

  .match-analysis-table th:nth-child(8),
  .match-analysis-table td:nth-child(8) {
    width: 7.92%;
  }

  .match-analysis-table th:nth-child(9),
  .match-analysis-table td:nth-child(9) {
    width: 15.84%;
  }

  /* Alliance cell badges no longer need the wide max-width sized for full
     team names now that they only show a short code. */
  .top-score-alliance-cell .leaderboard-team {
    max-width: none;
  }

  /* Top Teams header row: the rank column (1st, already label-less on
     desktop but still showed its sort arrow) plus "#" / "Team" / "ST/RG" /
     "CTRY" labels are redundant once their columns already show a
     self-explanatory number, logo, or flag (see the code/flag-stacking
     changes above) — dropped entirely on mobile to save header height.
     font-size:0 (rather than removing the text in app.js) keeps the <th>
     itself, its click-to-sort behavior, and its column width intact; only
     the rendered label/arrow disappears. OPR/WD Rank/OPR Event headers
     keep their labels as before. */
  #teams-result th:nth-child(1),
  #teams-result th:nth-child(2),
  #teams-result th:nth-child(3),
  #teams-result th:nth-child(4),
  #teams-result th:nth-child(5) {
    font-size: 0;
  }

  /* Combined ST/RG+Country column (4th) has no header label at all per
     request — the flag+region-letters content in the column itself is
     self-explanatory. */

  /* "Team" header spans visually across the # (2nd, team number+code) and
     Team (3rd, logo) columns together, since neither needs its own label
     anymore but the pair as a whole still benefits from one. Positioned
     via generated content rather than a real <th colspan="2"> so the
     header row keeps exactly one <th> per body <td> — changing that would
     shift every nth-child index used throughout this file. overflow:visible
     is needed on the 2nd th specifically so this can actually extend into
     the 3rd th's space instead of being clipped by the #teams-result
     th{overflow:hidden} rule. Left-aligned (not centered) and using the
     sitewide th{} font (0.82rem uppercase, same as OPR/WD Rank/OPR Event)
     so it reads consistently with every other header instead of looking
     like a different, smaller label. The width (165%) is an approximation
     of (2nd column width + 3rd column width) / 2nd column width — exact
     pixel alignment isn't critical for a small header label; kept in sync
     with the # column's actual width whenever that changes. */
  #teams-result th:nth-child(2) {
    position: relative;
    overflow: visible;
  }

  #teams-result th:nth-child(2)::after {
    content: "Team";
    position: absolute;
    left: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 165%;
    text-align: left;
    font-size: 0.82rem;
    font-weight: 600;
    /* !important — matches the real "Wd Rank"/OPR Event headers' plain
       muted color exactly; without it, .sortable-th-active's red
       (whenever # happens to be the active sort column) or any other
       higher-specificity color rule could otherwise still win through. */
    color: var(--muted) !important;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    pointer-events: none;
  }

  /* Sorting disabled entirely on mobile — clicking a header no longer
     re-sorts (pointer-events:none blocks the click handler from ever
     firing), and both sort indicators (the active column's real ▾/▴ text
     — now its own span, see .teams-result-sort-arrow/sortableTh in
     app.js — and the generic inactive-column ⇅ icon from .sortable-th::
     after) are hidden so nothing on the header row hints at
     now-nonfunctional sorting. !important needed on the ::after override
     since it's overriding a real (non-mobile-scoped) rule of equal
     specificity that appears earlier in the file. */
  #teams-result th.sortable-th {
    pointer-events: none !important;
    cursor: default !important;
  }

  #teams-result .teams-result-sort-arrow {
    display: none;
  }

  /* :not(:nth-child(2)) excludes the one column that still has its own
     generated ::after content ("Team" — see above; Location's was removed
     per request, so 4th no longer needs excluding here) — without this,
     the !important here (needed to beat the generic .sortable-th::after
     '⇅' icon) would ALSO clobber it, since !important always wins over a
     non-!important rule regardless of specificity, even a more specific
     one written earlier in the file. */
  #teams-result th.sortable-th:not(:nth-child(2))::after {
    content: none !important;
  }

  /* Team/ST/RG/CTRY columns sit closer together now that their headers
     are gone — tighter left/right padding on just these three columns
     (rather than the table's normal 10px) pulls them visually closer
     without changing their percentage widths. */
  #teams-result th:nth-child(3),
  #teams-result td:nth-child(3),
  #teams-result th:nth-child(4),
  #teams-result td:nth-child(4),
  #teams-result th:nth-child(5),
  #teams-result td:nth-child(5) {
    padding-left: 3px;
    padding-right: 3px;
  }

  /* Team profile event cards: date/location and the Qual/Playoff/OPR
     stats row, 10% smaller on mobile (1rem -> 0.9rem). */
  .team-event-card-date,
  .team-event-card-location,
  .event-stat {
    font-size: 0.9rem;
  }

  /* Combined np/auto/teleOPR stat (see eventCardStat/buildEventCard in
     app.js) replaces the three separate OPR/Auto OPR/Tele OPR stats on
     mobile — same info, one line instead of three. */
  .event-stat-opr-split {
    display: none;
  }

  /* !important needed — the element's own inline style (set once in
     buildEventCard in app.js, `oprCombinedStat.wrap.style.display =
     'none'`) otherwise always wins over a plain external rule regardless
     of media query or specificity. */
  .event-stat-opr-combined {
    display: inline-flex !important;
  }

  /* Mobile-only tab-context swap (see switchTab in renderTeamEventMatches
     in app.js) — Qual + combined OPR hide while the Playoff tab is
     active, and vice versa for Playoff + Playoff ×, so the stats row only
     ever carries one pair's worth of stats at a time. Must come after
     .event-stat-opr-combined above (same specificity — source order
     decides) so this can actually hide the combined stat when needed. */
  .event-stat-hidden-mobile {
    display: none !important;
  }

  /* Date + location: kept on one row (never wrapping from each other),
     event name gets its own row above them so there's more width to work
     with. If date+location still don't fit, the location text (the
     longer, more variable part) truncates with an ellipsis instead of
     wrapping or pushing date off-row. */
  .team-event-card-row1 {
    flex-direction: column;
    align-items: flex-start;
  }

  .team-event-card-datetime {
    flex-wrap: nowrap;
    width: 100%;
    min-width: 0;
  }

  .team-event-card-date {
    flex-shrink: 0;
  }

  .team-event-card-location {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
  }

  .team-event-card-location-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    display: inline-block;
    max-width: 100%;
  }

  /* Qual record + OPR stat(s) + Playoff record + Playoff × — kept to one
     row (no wrapping between stats); if they don't all fit, the row
     scrolls horizontally instead of wrapping onto a second line. */
  .team-event-card-stats {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .event-stat {
    flex-shrink: 0;
    white-space: nowrap;
  }

  /* Event page's own tab bar (Qualification/Playoff/Leaderboard/Award/
     Advancement/Registration — see .event-tab-bar/tabBar in
     showEventDetail) — no flex-wrap set by default (so all 6 currently
     cram onto one row/overflow), rearranged into two 3-tab rows on
     mobile: Qualification/Playoff/Award first, Leaderboard/Advancement/
     Registration second. DOM order is Qual(1)/Playoff(2)/Leaderboard(3)/
     Award(4)/Advancement(5)/Registration(6), so only Leaderboard and
     Award need their visual order swapped (via `order`) to land in the
     right row. */
  .event-tab-bar {
    flex-wrap: wrap;
    gap: 8px 16px;
  }

  .event-tab-btn {
    flex: 1 1 28%;
    text-align: center;
    padding-left: 0;
    padding-right: 0;
  }

  .event-tab-bar .event-tab-btn:nth-child(1) { order: 1; }
  .event-tab-bar .event-tab-btn:nth-child(2) { order: 2; }
  .event-tab-bar .event-tab-btn:nth-child(4) { order: 3; } /* Award -> row 1 */
  .event-tab-bar .event-tab-btn:nth-child(3) { order: 4; } /* Leaderboard -> row 2 */
  .event-tab-bar .event-tab-btn:nth-child(5) { order: 5; }
  .event-tab-bar .event-tab-btn:nth-child(6) { order: 6; }

  /* Qual tab: "Qualification" shortened to "Qualify" on mobile — the real
     text is hidden via font-size:0 and replaced with generated content
     instead of changing it in app.js, same trick as the match table's
     RED/BLUE headers. */
  .event-tab-qual-label {
    font-size: 0;
  }

  .event-tab-qual-label::after {
    content: 'Qualify';
    font-size: 1rem;
  }

  /* Scouting tab (see registration-table in showEventDetail — column
     order is # / Team / Location / npOPR / autoOPR / teleOPR / pRank /
     Luckiness / aRank; see the base, non-mobile rules above for desktop
     widths). Mobile now hides Location and pRank instead (Location was
     shown here for a while, but Luckiness/aRank are more useful to a
     phone visitor than a redundant flag+code column already visible on
     each team's own profile; pRank stays cut since it's the least
     actionable of the three rank-ish columns without also seeing
     Luckiness/aRank alongside it). display:none alone doesn't reliably
     collapse a table-layout:fixed column's reserved width in every
     browser, so width/padding/border are explicitly zeroed too — same
     fix used for Top Teams' hidden Country column elsewhere in this
     file. Team is also cut by 1/3 here (20% desktop → 13.33% mobile);
     the total space freed by hiding Location+pRank and shrinking Team
     is redistributed evenly across the three OPR columns below. */
  .registration-table:not(.playoff-columns) th:nth-child(3),
  .registration-table:not(.playoff-columns) td:nth-child(3),
  .registration-table:not(.playoff-columns) th:nth-child(7),
  .registration-table:not(.playoff-columns) td:nth-child(7) {
    display: none;
    width: 0;
    padding: 0;
    border: none;
  }
  .registration-table:not(.playoff-columns) th:nth-child(1), .registration-table:not(.playoff-columns) td:nth-child(1) { width: 5%; }
  .registration-table:not(.playoff-columns) th:nth-child(2), .registration-table:not(.playoff-columns) td:nth-child(2) { width: 13.33%; }
  .registration-table:not(.playoff-columns) th:nth-child(4), .registration-table:not(.playoff-columns) td:nth-child(4),
  .registration-table:not(.playoff-columns) th:nth-child(5), .registration-table:not(.playoff-columns) td:nth-child(5),
  .registration-table:not(.playoff-columns) th:nth-child(6), .registration-table:not(.playoff-columns) td:nth-child(6) { width: 17.767%; }
  .registration-table:not(.playoff-columns) th:nth-child(8), .registration-table:not(.playoff-columns) td:nth-child(8) { width: 17.564%; }
  .registration-table:not(.playoff-columns) th:nth-child(9), .registration-table:not(.playoff-columns) td:nth-child(9) { width: 10.805%; }

  /* Pre-Qualification (8 columns, no aRank — see the base, non-mobile
     .pre-columns rule above) absorbed aRank's mobile width into Luckiness
     (17.564 + 10.805 = 28.369%), then per a later request Luckiness/"Luck"
     (see the mobile-label swap below) gives up 1/3 of THAT back to the
     three OPR columns: 28.369 × 2⁄3 ≈ 18.913%, freeing 9.456 points ÷ 3 =
     3.152 points each → 17.767 + 3.152 ≈ 20.919%. */
  .registration-table.pre-columns th:nth-child(4),
  .registration-table.pre-columns td:nth-child(4),
  .registration-table.pre-columns th:nth-child(5),
  .registration-table.pre-columns td:nth-child(5),
  .registration-table.pre-columns th:nth-child(6),
  .registration-table.pre-columns td:nth-child(6) { width: 20.919%; }
  .registration-table.pre-columns th:nth-child(8),
  .registration-table.pre-columns td:nth-child(8) { width: 18.913%; }

  /* This header has no text at all now (just the red die icon — see
     col.headerIcon in makeSortableTable/app.js) — font-size:0 hides the
     (now-empty) label the same way it hid "Luckiness" before text was
     removed from this column entirely; the th-icon span survives via its
     own explicit font-size, same as every other mobile-shrunk header on
     this table. Applies to both Pre-Qualification and Qualification
     (this column sits at the same position 8 either way — only Playoff's
     column set differs, so it's excluded here same as everywhere else in
     this file). */
  .registration-table:not(.playoff-columns) th:nth-child(8) {
    font-size: 0;
    position: relative;
  }
  /* This column is sortable, so its ::after sort arrow (⇅/↑/↓ — see
     .sortable-th::after) would otherwise inherit the font-size:0 above
     and disappear too; restored to its normal size same as the Team
     header's own ::after fix earlier in this file. */
  .registration-table:not(.playoff-columns) th:nth-child(8)::after {
    font-size: 12px;
  }

  /* Playoff view on mobile — labels are real text now (see
     buildPlayoffMobileThead in app.js: a genuine two-row header with
     "Qual OPR"/"PlayOPR" colspan=2 group cells over the Indiv/Comb pairs,
     and Alliance/Team/Playoff-Mult as rowspan=2 cells), so no CSS text-
     swapping is needed here anymore — only widths, via the same
     <colgroup> approach as the desktop rules above (a header row's
     nth-child position doesn't line up 1:1 with columns once colspan/
     rowspan are involved, but a <col>'s width isn't affected by that).
     Alliance (1) is half the other data columns' width, and the 7 points
     that frees goes to Team (2); Team was then cut further twice more —
     first 60% off (23% → 9.2%), then another 50% off (9.2% → 4.6%) — with
     the freed space each time split evenly across the four OPR columns
     (3–6): 14 + 13.8⁄4 (first cut) + 4.6⁄4 (second cut) = 17.45 + 1.15 =
     18.6% each. Playoff Multiplier (7) isn't an OPR figure, so it's left
     alone at 14%.
     A later request cut Alliance AND Team by 50% again (7% → 3.5%, 4.6% →
     2.3%; 5.8 points freed total), this time given only to the two
     INDIVIDUAL OPR columns (3 and 5 — Indiv-QualOPR/Indiv-PlayOPR, not
     their Comb siblings): 18.6 + 5.8⁄2 = 21.5% each.
     Team was cut another 40% (2.3% → 1.38%, freeing 0.92 points) and that
     went entirely to Playoff Multiplier/"PO X" (7): 14 + 0.92 = 14.92%.
     Most recently, all four OPR columns (3–6) were cut 5% EACH off their
     own value and all of that went to Team (2): 21.5×0.95 = 20.425 (cols
     3/5, freeing 1.075 each), 18.6×0.95 = 17.67 (cols 4/6, freeing 0.93
     each) — 4.01 points total freed → 1.38 + 4.01 = 5.39%.
     Per the latest request, the horizontal PADDING between OPR cells
     (10px per side, from the site-wide th/td rule) was also halved to
     5px per side (see below) — that's a real, measurable px change, but
     "give the saved space to Team" can't convert exactly from px to %
     width (px padding and % column width are different units, and the
     actual on-screen px-per-% ratio changes with viewport size); as a
     reasonable stand-in, 1 percentage point was shaved off each of the
     four OPR columns' own width (4 points total) and added to Team:
     5.39 + 4 = 9.39%. */
  .registration-table.playoff-columns colgroup col:nth-child(1) { width: 3.5%; }
  .registration-table.playoff-columns colgroup col:nth-child(2) { width: 9.39%; }
  .registration-table.playoff-columns colgroup col:nth-child(3),
  .registration-table.playoff-columns colgroup col:nth-child(5) { width: 19.425%; }
  .registration-table.playoff-columns colgroup col:nth-child(4),
  .registration-table.playoff-columns colgroup col:nth-child(6) { width: 16.67%; }
  .registration-table.playoff-columns colgroup col:nth-child(7) { width: 14.92%; }

  /* Halved horizontal padding (10px → 5px, matching the site-wide th/td
     rule's own value) between the OPR cells specifically, per request —
     tightens the visual gap between adjacent OPR columns. Vertical
     padding is left alone. Class-based (not nth-child) — a header cell's
     position among ITS row's children doesn't match real column index
     once the mobile header's colspan/rowspan grouping is involved (row 1
     has 5 children, row 2 has 4), and body rows vary too (a rowspan-ed
     first row has 7 tds, a later teammate row only 3) — see
     .scouting-opr-cell, added directly in app.js to every OPR header/data
     cell regardless of which row shape it's in. */
  .registration-table.playoff-columns .scouting-opr-cell {
    padding-left: 5px;
    padding-right: 5px;
  }

  /* "X teams registered" is dropped on mobile — the toggle is the more
     useful thing to keep visible in that limited header width. */
  .scouting-team-count {
    display: none;
  }

  /* .scouting-mode-row's desktop layout is a 1fr/auto/1fr grid so the
     toggle stays centered regardless of the team-count text's width (see
     that rule's own comment) — but a grid's "auto" track has an implicit
     MINIMUM size of min-content, not max-content, so on a narrow mobile
     screen the middle column was shrinking below what "Pre-Qualification
     / Qualification / Playoff" actually needs, wrapping/clipping the
     button labels (reported via screenshot). Since scouting-team-count is
     already hidden on mobile (immediately above), there's nothing left
     to balance against anyway — switched to a plain centered flex row
     instead of the grid, which lets the toggle take its natural content
     width with no shrink-to-fit fight. */
  .scouting-mode-row {
    display: flex;
    justify-content: center;
  }

  /* "Team" column header text is dropped on mobile (the logo+code cells
     make the column obvious without a label; freeing that vertical space
     matters more at this width). The sort arrow (::after) is kept but
     reset to a normal size, since font-size:0 on the th would shrink it
     to nothing too. Class-based (not nth-child) since Team sits at a
     different column index in the Playoff table than the normal one. */
  .registration-table .scouting-team-th {
    font-size: 0;
  }
  .registration-table .scouting-team-th::after {
    font-size: 12px;
  }

  /* All Scouting tab headers centered on mobile, including the two-line
     ones — .sortable-th's padding-right:16px (reserved for the sort
     arrow) otherwise pushes centered text visually left, so a matching
     left padding balances it back to true center. */
  .registration-table th {
    text-align: center;
    padding-left: 16px;
  }
  .registration-table .th-two-line span {
    text-align: center;
  }

  /* Schedule Luck's die icon at the midpoint between left and center on
     mobile too — overrides the general "all Scouting headers centered on
     mobile" rule right above. Fixed px (not the earlier percentage
     attempt, which broke on table cells — see the desktop rule's comment)
     — a smaller value than desktop's since this column is narrower here. */
  .registration-table:not(.playoff-columns) th:nth-child(8) {
    text-align: left;
    padding-left: 14px;
  }
  .registration-table:not(.playoff-columns) th:nth-child(8)::after {
    left: 14px;
  }

  /* "rp/Rank" (aRank, the last column) shifted left toward Luckiness —
     dropping the centering left-padding added above and left-aligning
     instead pulls its text back over next to the Luckiness column
     rather than sitting centered/pushed right in its own cell. */
  .registration-table th:nth-child(9) {
    text-align: left;
    padding-left: 4px;
  }
  .registration-table th:nth-child(9).th-two-line span:first-child {
    text-align: center;
  }
  .registration-table th:nth-child(9).th-two-line span:last-child {
    text-align: left;
  }

  /* Team code moves under the logo (stacked) instead of beside it, only
     within the Scouting tab — elsewhere on mobile the code still shows
     inline next to other elements. Left-aligned within the Team column
     (inline-flex so the box shrinks to its content, td left-aligns it),
     with the logo and code CENTERED relative to each other (align-items:
     center) so the narrower code text sits centered under the logo
     instead of flush to one side of it. Class-based (not nth-child) since
     Team sits at a different column index in the Playoff table than the
     normal one. */
  .registration-table .scouting-team-td {
    text-align: left;
  }
  .registration-table .leaderboard-team {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
  }

  /* Advancement tab, mobile only, per request:
     - Team logo/code stacked (code under logo), same pattern as the
       Scouting tables' team cell above.
     - # and Team columns each cut in half; the space that frees up (plus
       the space freed by dropping Qual Rank entirely, below) is given to
       the remaining data columns. table-layout:fixed + explicit per-
       column widths, same standing pattern used everywhere else on this
       page for exact column sizing (auto layout can't guarantee it).
       Column order once Qual Rank is dropped: # / Team / Qual / Alli /
       PlayO / Awd / Tot (7 columns). */
  .advancement-table:not(.registration-table) .leaderboard-team {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
  }
  /* :not(.registration-table) on every rule in this section — the
     Advancement tab's table carries ONLY the advancement-table class, but
     the three Scouting tabs' table carries BOTH advancement-table AND
     registration-table (it reuses advancement-table's base font/padding
     on purpose — see the comment near .advancement-table's original
     definition). A plain `.advancement-table ...` selector was therefore
     leaking every Advancement-only header rename/width/hide rule below
     onto Pre-Qualification/Qualification/Playoff too — caught after the
     fact via user report and fixed by excluding .registration-table here. */
  .advancement-table:not(.registration-table) {
    table-layout: fixed;
  }

  /* "X teams in Qualification rounds" note dropped on mobile per request
     (class-based — see the advancement-team-count class added in app.js —
     so only this specific line is hidden, not every .muted note). */
  .advancement-team-count {
    display: none;
  }

  /* # and Qual Rank are plain numbers, not team-badge cells — centered on
     mobile per request (the shared .advancement-table th/td rule left-
     aligns everything by default). The other data columns (Qual/Alli/
     PlayO/Awd/Tot) are centered for free once they switch to
     numberBarCell in app.js — that helper's own markup already centers
     its text regardless of the td's text-align. */
  .advancement-table:not(.registration-table) td:nth-child(1),
  .advancement-table:not(.registration-table) td:nth-child(3) {
    text-align: center;
  }
  /* Each numeric column (4-8) cut 2% RELATIVE to its own prior width
     (×0.98 — same "percent of the column's own value" convention used for
     the earlier "cut OPR columns by 5%" request elsewhere on this page),
     with the freed 1.66 points (0.32×2 + 0.34×3) added to column 1 (#):
     5% → 6.66%. */
  .advancement-table:not(.registration-table) th:nth-child(1),
  .advancement-table:not(.registration-table) td:nth-child(1) { width: 6.66%; }
  .advancement-table:not(.registration-table) th:nth-child(2),
  .advancement-table:not(.registration-table) td:nth-child(2) { width: 12%; }
  .advancement-table:not(.registration-table) th:nth-child(4),
  .advancement-table:not(.registration-table) td:nth-child(4) { width: 15.68%; }
  .advancement-table:not(.registration-table) th:nth-child(5),
  .advancement-table:not(.registration-table) td:nth-child(5) { width: 15.68%; }
  .advancement-table:not(.registration-table) th:nth-child(6),
  .advancement-table:not(.registration-table) td:nth-child(6) { width: 16.66%; }
  .advancement-table:not(.registration-table) th:nth-child(7),
  .advancement-table:not(.registration-table) td:nth-child(7) { width: 16.66%; }
  .advancement-table:not(.registration-table) th:nth-child(8),
  .advancement-table:not(.registration-table) td:nth-child(8) { width: 16.66%; }

  /* "Team" header hidden (column itself stays — the cell already shows
     the logo/code, no text label needed), same font-size:0 + ::before
     trick used for the Scouting tables' own Team header. This column IS
     sortable (key: 'teamNumber'), so its ::after sort arrow needs the
     same font-size restore as the other renamed headers below. */
  .advancement-table:not(.registration-table) th:nth-child(2) {
    font-size: 0;
    position: relative;
  }
  .advancement-table:not(.registration-table) th:nth-child(2)::after {
    font-size: 12px;
  }

  /* Qual Rank (column 3) dropped entirely on mobile per request — explicit
     width:0/padding:0/border:none alongside display:none, since
     display:none alone doesn't reliably collapse a table-layout:fixed
     column's reserved width. */
  .advancement-table:not(.registration-table) th:nth-child(3),
  .advancement-table:not(.registration-table) td:nth-child(3) {
    display: none;
    width: 0;
    padding: 0;
    border: none;
  }

  /* Shorter mobile labels for the remaining data columns — same font-
     size:0 + ::before content-swap trick used elsewhere on this page.
     None of these headers are sortable icons that need an ::after
     restore (advancement-table's sort arrows use the shared
     .advancement-table th.sorted-asc/desc::after rule, which isn't
     affected by a child element's own font-size). */
  .advancement-table:not(.registration-table) th:nth-child(4) {
    font-size: 0;
    position: relative;
  }
  .advancement-table:not(.registration-table) th:nth-child(4)::before {
    content: 'Qual';
    font-size: 12px;
  }
  .advancement-table:not(.registration-table) th:nth-child(4)::after {
    font-size: 12px;
  }
  .advancement-table:not(.registration-table) th:nth-child(5) {
    font-size: 0;
    position: relative;
  }
  .advancement-table:not(.registration-table) th:nth-child(5)::before {
    content: 'Alli';
    font-size: 12px;
  }
  .advancement-table:not(.registration-table) th:nth-child(5)::after {
    font-size: 12px;
  }
  .advancement-table:not(.registration-table) th:nth-child(6) {
    font-size: 0;
    position: relative;
  }
  .advancement-table:not(.registration-table) th:nth-child(6)::before {
    content: 'PlayO';
    font-size: 12px;
  }
  .advancement-table:not(.registration-table) th:nth-child(6)::after {
    font-size: 12px;
  }
  .advancement-table:not(.registration-table) th:nth-child(7) {
    font-size: 0;
    position: relative;
  }
  .advancement-table:not(.registration-table) th:nth-child(7)::before {
    content: 'Awd';
    font-size: 12px;
  }
  .advancement-table:not(.registration-table) th:nth-child(7)::after {
    font-size: 12px;
  }
  .advancement-table:not(.registration-table) th:nth-child(8) {
    font-size: 0;
    position: relative;
  }
  .advancement-table:not(.registration-table) th:nth-child(8)::before {
    content: 'Tot';
    font-size: 12px;
  }
  .advancement-table:not(.registration-table) th:nth-child(8)::after {
    font-size: 12px;
  }

  /* Tighter gap between Live/Upcoming/Championship/All now that all four
     share the row on mobile (was 16px, sized for the original three) —
     frees up the room the new All tab needs. */
  .timeline-tab-group {
    gap: 9px !important;
  }

  /* Live/Upcoming/Championship/All swap to their short labels (see
     .timeline-tab-label-short above) — "All Events" shortens to "All". */
  .timeline-tab-label-full {
    display: none;
  }

  .timeline-tab-label-short {
    display: inline;
  }

  .timeline-col-label-full {
    display: none;
  }

  .timeline-col-label-short {
    display: inline;
  }

  /* The "from <Country/State>" filter readout next to the tab row (see
     #timeline-location-label / updateEventSectionHeadings in app.js) —
     dropped on mobile per request. !important needed — the element carries
     an inline `style="display:inline-flex"` (see index.html), which always
     wins over a plain external rule regardless of media query. */
  #timeline-location-label {
    display: none !important;
  }

  /* Events list (Live/Upcoming/Championship/All — see
     .timeline-events-table in loadTimelineEvents): TYPE column dropped
     entirely (Location stays — see below); Event/Date/Location/#Teams
     wrap onto multiple lines instead of forcing the table wider than the
     screen — table-layout:fixed + explicit widths is what makes the
     wrapping actually happen instead of the browser just widening the
     columns to fit one line. */
  .timeline-events-table {
    table-layout: fixed;
    width: 100%;
  }

  .timeline-events-table th:nth-child(2),
  .timeline-events-table td:nth-child(2) {
    display: none;
  }

  .timeline-events-table th:nth-child(1),
  .timeline-events-table td:nth-child(1) {
    width: 40%;
    white-space: normal;
    word-break: break-word;
  }

  .timeline-events-table th:nth-child(3),
  .timeline-events-table td:nth-child(3) {
    width: 20%;
    white-space: normal;
    word-break: break-word;
  }

  .timeline-events-table th:nth-child(4),
  .timeline-events-table td:nth-child(4) {
    width: 28%;
    white-space: normal;
    word-break: break-word;
  }

  .timeline-events-table th:nth-child(5),
  .timeline-events-table td:nth-child(5) {
    width: 12%;
    white-space: normal;
  }
}
