:root {
  /* SaturDynasty vintage-collegiate-pennant theme — tokens sampled directly
     from frontend/assets/new-logo.png (the pennant wordmark artwork: navy
     felt, cream lettering, gold "Dynasty," oxblood leather strap) rather
     than invented, then adjusted only for a navy/cream two-surface system
     and checked against WCAG contrast — see DECISIONS.md for the exact
     sampling method and the contrast ratio for every pairing below.

     --card-bg stays the one surface that's warm-light rather than dark
     (unchanged in spirit from the original dark-theme's one holdover): team
     logos and school colors read better against a light card than the dark
     --bg-card, so anything holding a team logo uses it explicitly — see
     .pick-chip/.team-card/.roster-card/.modal-card below. Warm ivory now,
     not the old cool gray — the neutral frame a school's own colors (Ohio
     State scarlet, Michigan maize, ...) should still pop against; the
     --team-color custom property those cards set per-team is untouched. */
  --bg: #081420;
  --bg-elevated: #0d1f32;
  --bg-card: #112b45;
  --card-bg: #f2e7d3;
  /* Text/border/status tones for content living on --card-bg — the main
     --text/--text-dim/--accent/--success are tuned for the dark surfaces
     and are unreadable (or too washed out) on a light card. */
  --card-text: #2a1f16;
  --card-text-dim: #8a7355;
  --card-border: #b89c6e;
  --card-success: #1f7a43;
  --card-accent: #7a2420;
  --border: #1c3a5c;
  --text: #f2e7d3;
  --text-dim: #a89a82;
  --accent: #c1902f;
  --accent-hover: #d9a94a;
  /* Secondary brand accent (the pennant's leather strap) — a fill/border
     color, deliberately never used as text directly on --bg (contrast
     ~1.5:1 there); on --card-bg it's a strong 8.2:1, see --card-accent.
     Replaces --accent-2, a bright blue that was defined but never actually
     referenced anywhere in this file or app.js. */
  --burgundy: #5c1c1a;
  --success: #2f9959;
  --danger: #d15a5a;
  --radius: 12px;
  /* Collegiate/varsity display face — brand wordmark and major titles only
     (see the selector list where this is applied below). Body copy,
     buttons, tables, and forms stay on the system-sans stack. */
  --font-display: "Anton", -apple-system, BlinkMacSystemFont, sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  /* Flat, not a radial-gradient: a gradient's percentage stops are relative
     to the full scrollable page height, and the Draft tab (~130 team cards)
     is far taller than Standings/Rosters — a gradient made the visible
     background look different per tab instead of consistent. Kept this fix
     from the light-theme experiment even though the color scheme reverted. */
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
}

.dev-badge {
  position: fixed;
  top: 8px; right: 8px;
  background: var(--danger);
  color: white;
  font-size: 11px;
  font-weight: 700;
  padding: 4px 8px;
  border-radius: 6px;
  z-index: 999;
  letter-spacing: 0.05em;
}

/* ─── Confetti (src/app.js's fireConfetti) ─────────────────────────────────── */

.confetti-burst {
  position: fixed;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 2000;
}

.confetti-piece {
  position: absolute;
  top: -12px;
  width: 8px;
  height: 14px;
  opacity: 0.9;
  animation-name: confetti-fall;
  animation-timing-function: cubic-bezier(0.15, 0.5, 0.4, 1);
  animation-fill-mode: forwards;
}

@keyframes confetti-fall {
  0% { transform: translate(0, 0) rotate(0deg); opacity: 1; }
  100% { transform: translate(var(--drift), 100vh) rotate(calc(var(--spin) * 540deg)); opacity: 0; }
}

/* ─── Login ─────────────────────────────────────────────────────────────── */

.login-screen {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Author CSS beats the browser's default `[hidden] { display: none }` rule
   at equal specificity, so `.login-screen`'s unconditional `display: flex`
   above silently wins over the `hidden` attribute the app sets after login
   unless this is spelled out explicitly. */
.login-screen[hidden] {
  display: none;
}

.login-card {
  text-align: center;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-top: 3px solid var(--accent);
  border-radius: var(--radius);
  padding: 40px 40px 48px;
  box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.5);
  max-width: 340px;
}

/* The full pennant render (frontend/assets/new-logo.png) — also used at
   header scale (.brand-mark below), so the wordmark baked into the art
   itself carries the brand there instead of a separate text element. */
.login-pennant {
  display: block;
  width: 100%;
  max-width: 220px;
  margin: 0 auto 20px;
  filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.5));
}

.login-card h1 { font-family: var(--font-display); font-weight: 400; letter-spacing: 0.03em; font-size: 1.9rem; margin: 0 0 8px; }
/* "Choose a League" only — smaller than the "SaturDynasty" wordmark above
   shares this same h1/.login-card, but this one reads as too large a
   brand moment for what's really just a picker screen's title. */
#league-select-screen h1 { font-size: 1.4rem; }
.login-card p { color: var(--text-dim); margin: 0 0 24px; font-size: 0.95rem; }

.login-footnote {
  margin: 20px 0 0 !important;
  font-size: 0.8rem !important;
  color: var(--text-dim);
  opacity: 0.7;
}
.login-footnote a { color: var(--accent); }

.league-options {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.league-option {
  width: 100%;
  background: var(--bg-card);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px 16px;
  font-size: 0.95rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: transform 0.05s ease, border-color 0.15s ease;
}
.league-option:hover { border-color: var(--accent); }
.league-option:active { transform: scale(0.98); }

/* Google's own sign-in button branding guidelines call for a neutral
   white/gray button with the multicolor "G" mark, not our app's accent
   gradient — keeps the button recognizable as a Google auth affordance. */
.btn-google {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  background: #fff;
  color: #3c4043;
  border: 1px solid #dadce0;
  border-radius: 8px;
  padding: 10px 16px;
  font-size: 0.95rem;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: box-shadow 0.15s ease, transform 0.05s ease;
}
.btn-google:hover { box-shadow: 0 1px 6px rgba(0, 0, 0, 0.3); }
.btn-google:active { transform: scale(0.98); }

/* ─── Layout ────────────────────────────────────────────────────────────── */

/* Header: SaturDynasty mark, the current league name next to it, account
   menu on the right (see DECISIONS.md "Reskin" — tried both a separate
   burgundy stripe and a separate navy stripe for the league name on their
   own row; both got pulled back in favor of this simpler inline layout).
   #league-name is the one dynamic piece here, set by enterApp(). */
.app-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 24px;
  border-bottom: 1px solid var(--border);
}

/* The pennant art (new-logo.png) has the "SaturDynasty" wordmark baked
   into it, so there's no separate text element to keep it in sync with —
   it's a background-removed, tightly-cropped PNG (background knocked out
   via a border-connected flood fill, not a hand-drawn asset — see git
   history) so it floats directly on the navy header instead of sitting in
   a framed tile the way the original opaque render needed to. */
.brand { display: flex; align-items: center; gap: 14px; }
.brand-mark {
  height: 56px;
  width: auto;
  object-fit: contain;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.45));
  flex-shrink: 0;
}
.league-name {
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* Switch League — a small opaque, subdued circle button next to the league
   name (not a menu item — see DECISIONS.md "Reskin"; it used to live
   inside the account dropdown, moved back out here since it acts directly
   on the league identity next to it, not the signed-in account). Opaque
   fill (var(--bg-elevated), not a transparent/ghost treatment) so it
   reads as a real button, muted icon color so it stays secondary to the
   league name itself. */
.league-switch-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
  color: var(--text-dim);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.league-switch-btn:hover { color: var(--accent); border-color: var(--accent); }
.league-switch-btn svg { flex-shrink: 0; }
/* Same [hidden]-vs-class-selector specificity gotcha as elsewhere in this
   file (.login-screen[hidden], the old #switch-league-btn[hidden]) — a
   class selector doesn't automatically beat the browser's default
   `[hidden] { display: none }` UA rule, so this needs to be explicit. */
.league-switch-btn[hidden] {
  display: none;
}

/* Account menu — an initials avatar replacing the old name-text +
   separate Sign out/Switch League buttons (see DECISIONS.md "Reskin").
   Lives on the nav row (.tabs), right-aligned opposite .tab-list, not the
   header row — see .tabs below. .user-menu is the positioning context for
   the dropdown card below it. */
.user-menu { position: relative; flex-shrink: 0; }

.user-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: none;
  background: linear-gradient(135deg, var(--accent), var(--accent-hover));
  color: var(--bg);
  /* Plain system-sans, not --font-display — two-letter initials are
     exactly the "normal UI copy" case the display face isn't for (same
     reasoning as .on-the-clock h2 / .roster-column-header above). */
  font-weight: 700;
  font-size: 0.8rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: box-shadow 0.15s ease;
}
.user-avatar:hover { box-shadow: 0 0 0 3px rgba(193, 144, 47, 0.25); }
.user-menu.open .user-avatar { box-shadow: 0 0 0 3px rgba(193, 144, 47, 0.4); }

.user-menu-dropdown {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 20;
  min-width: 200px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px;
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
}

.user-menu-name {
  padding: 8px 10px 10px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--text);
}

.user-menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  background: none;
  border: none;
  border-radius: 6px;
  padding: 9px 10px;
  color: var(--text);
  font-size: 0.9rem;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.user-menu-item:hover { background: var(--bg-elevated); }

.tabs {
  display: flex;
  gap: 4px;
  padding: 8px 16px;
  overflow-x: auto;
  /* One of this reskin's few deliberate "vintage" touches — a thin gold
     rule under the nav (see DECISIONS.md "Reskin") — low-opacity so it
     reads as a rule, not another loud gold bar. */
  border-bottom: 1px solid rgba(193, 144, 47, 0.35);
}

.tab-btn {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--text-dim);
  padding: 10px 16px 8px;
  border-radius: 8px 8px 0 0;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.tab-btn:hover { background: var(--bg-card); color: var(--text); }
/* Restrained gold treatment — a subtle wash + underline, not a solid
   filled pill (see DECISIONS.md "Reskin": the old bright-orange pill read
   as loud, not selected). rgba here is --accent's rgb triplet at low
   opacity; CSS custom properties can't be torn apart into components for
   color-mix without a fallback for older browsers, so it's spelled out. */
.tab-btn.active {
  background: rgba(193, 144, 47, 0.14);
  color: var(--accent);
  border-bottom-color: var(--accent);
}

main#tab-content { padding: 20px 24px 60px; max-width: 1100px; margin: 0 auto; }

/* ─── Buttons ───────────────────────────────────────────────────────────── */

.btn {
  border: none;
  border-radius: 8px;
  padding: 10px 20px;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.05s ease, opacity 0.15s ease;
}
.btn:active { transform: scale(0.97); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }

/* Dark navy text, not white — gold is light enough that white-on-gold
   fails contrast (checked: ~2.9:1), navy-on-gold is a strong ~6.5:1 and
   reads like a gold medal/varsity letter rather than a generic tinted
   button. */
.btn-primary { background: linear-gradient(135deg, var(--accent), var(--accent-hover)); color: var(--bg); }
.btn-ghost { background: var(--bg-card); color: var(--text); border: 1px solid var(--border); }
.btn-small { padding: 6px 12px; font-size: 0.85rem; }

/* ─── Draft ─────────────────────────────────────────────────────────────── */

.on-the-clock {
  background: linear-gradient(135deg, var(--bg-elevated), var(--bg-card));
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

/* Plain system-sans, not --font-display — this is a full sentence with a
   real owner's name in it ("Joey Weaver is on the clock"), not a short
   brand/title moment. Anton is a single heavy "black" weight with no
   lighter cut, and reads as overbearing at sentence length. */
.on-the-clock h2 { font-weight: 800; margin: 0 0 4px; font-size: 1.3rem; }
.on-the-clock p { margin: 0; color: var(--text-dim); }
.on-the-clock.complete { border-color: var(--success); }
.on-the-clock.my-turn { border-color: var(--success); box-shadow: 0 0 0 2px rgba(47, 153, 89, 0.25); }

.previous-pick {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 20px;
}

.previous-pick-label {
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-dim);
}

.pick-chip {
  background: var(--card-bg);
  color: var(--card-text);
  border: 1px solid var(--card-border);
  border-radius: 20px;
  padding: 4px 10px 4px 4px;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.8rem;
}
.pick-chip img { width: 20px; height: 20px; border-radius: 50%; }

.search-row { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; flex-wrap: wrap; }
.search-row input {
  flex: 1;
  min-width: 160px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 14px;
  color: var(--text);
  font-size: 0.95rem;
}

.filter-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
}

.filter-toggle-label {
  color: var(--text-dim);
  font-weight: 600;
  transition: color 0.15s ease;
}

/* Real checkbox stays present (for click target + accessibility) but
   visually replaced by the pill/knob pair below it. */
.toggle-switch-input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.toggle-switch {
  position: relative;
  width: 38px;
  height: 22px;
  flex-shrink: 0;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 999px;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.toggle-switch::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--text-dim);
  transition: transform 0.15s ease, background 0.15s ease;
}

.toggle-switch-input:checked + .toggle-switch {
  background: var(--accent);
  border-color: var(--accent);
}
.toggle-switch-input:checked + .toggle-switch::before {
  transform: translateX(16px);
  background: white;
}

.toggle-switch-input:focus-visible + .toggle-switch {
  box-shadow: 0 0 0 2px rgba(193, 144, 47, 0.4);
}

.filter-toggle:has(.toggle-switch-input:checked) .filter-toggle-label {
  color: var(--text);
}

.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
}

.team-card {
  background: var(--card-bg);
  color: var(--card-text);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 14px;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.15s ease, transform 0.1s ease;
  border-top: 3px solid var(--team-color, var(--card-border));
}
.team-card:hover { transform: translateY(-2px); border-color: var(--accent); }
.team-card.taken { opacity: 0.35; cursor: default; }
.team-card.taken:hover { transform: none; border-color: var(--card-border); }
.team-card.selected { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(193, 144, 47, 0.35); }
.team-card img { width: 48px; height: 48px; object-fit: contain; margin-bottom: 8px; }
.team-card .team-name {
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1.25;
  /* Same reasoning as .roster-card .name: reserve 2 lines' height so a
     1-line school name and a 2-line one produce equal-height cards in the
     same grid row, and clamp a 3rd line to "…" instead of growing further. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 2.5em;
}
.team-card .team-owner { font-size: 0.75rem; color: var(--card-text-dim); margin-top: 4px; }

.confirm-pick-bar {
  background: var(--bg-elevated);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 14px 20px;
  margin-bottom: 16px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* ─── Standings ─────────────────────────────────────────────────────────── */

.standings-logo {
  display: block;
  width: 140px;
  height: 140px;
  object-fit: contain;
  margin: 4px auto 20px;
  filter: drop-shadow(0 8px 20px rgba(0, 0, 0, 0.45));
}

/* Fallback for leagues without a branded logo asset (see renderStandingsTab) */
.standings-league-name {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: 0.02em;
  text-align: center;
  margin: 4px 0 20px;
  font-size: 1.6rem;
}

.standings-list { display: flex; flex-direction: column; gap: 10px; }

.standings-row {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  cursor: pointer;
  transition: border-color 0.15s ease, transform 0.1s ease;
}
.standings-row:hover { border-color: var(--accent); transform: translateY(-1px); }
/* The one "championship" touch on this tab — a thin gold top rule + a
   small star by the #1 rank, not decoration on every row (see
   DECISIONS.md "Reskin" — vintage elements stay restrained). */
.standings-row:first-child { border-top: 2px solid var(--accent); }

.standings-rank {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1.5rem;
  color: var(--text-dim);
  width: 32px;
}
.standings-row:first-child .standings-rank { color: var(--accent); }
.standings-row:first-child .standings-rank::after { content: " \2605"; font-size: 0.75rem; vertical-align: super; }
.standings-name { flex: 1; font-weight: 700; font-size: 1.05rem; }
.standings-points { font-size: 1.3rem; font-weight: 800; color: var(--success); }

/* ─── Rosters (all 5 owners, side by side) ─────────────────────────────────── */

.rosters-board-wrap {
  overflow-x: auto;  /* 5 fixed columns don't fit a phone screen — scroll instead of squeezing */
}

.rosters-board {
  display: grid;
  grid-template-columns: repeat(5, minmax(170px, 1fr));
  gap: 12px;
  min-width: 850px;  /* keeps columns readable; wrapper scrolls below that width */
}

.roster-column {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px;
}

/* Plain system-sans, not --font-display — this heads a real owner's name,
   not a short brand/title moment, and Anton (a single heavy "black" weight,
   no lighter cut available) reads as overbearing on an actual person's
   name (see DECISIONS.md "Reskin" — same reasoning as .on-the-clock h2
   below). */
.roster-column-header {
  font-weight: 800;
  text-align: center;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 10px;
}

.roster-swaps-remaining {
  text-align: center;
  font-size: 0.72rem;
  color: var(--text-dim);
  margin: -4px 0 10px;
}

/* Desktop: one team per row, stacked — see the @media block for the mobile
   "pills that wrap and fill the card" layout of this same container. */
.roster-pills {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.roster-card {
  background: var(--card-bg);
  color: var(--card-text);
  border: 1px solid var(--card-border);
  border-radius: var(--radius);
  padding: 10px;
  display: flex;
  align-items: center;
  gap: 10px;
  border-left: 3px solid var(--team-color, var(--card-border));
  cursor: pointer;
  transition: border-color 0.15s ease, transform 0.1s ease;
}
.roster-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.roster-card img { width: 32px; height: 32px; object-fit: contain; flex-shrink: 0; }
.roster-card .name {
  font-weight: 700;
  font-size: 0.82rem;
  line-height: 1.3;
  /* Clamp at 2 lines (3rd+ line truncates to "…"), and reserve that same
     2-line height even for a 1-line name — otherwise a column of cards has
     a different height per card depending on how long each team's name is. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 2.6em;
}
.roster-card .points { color: var(--card-success); font-weight: 700; font-size: 0.78rem; }
/* Sized in em, not rem, so it stays proportionally smaller than whatever
   .points is in each context (the Rosters tab's narrow columns vs. the
   Standings modal's wider list) instead of needing its own override per
   context — keeps "(since swap)"/"(dropped)" from forcing a line wrap. */
.points-note { font-size: 0.75em; font-weight: 500; opacity: 0.85; }

.roster-banked {
  text-align: center;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--success);
  padding-top: 8px;
  margin-top: 4px;
  border-top: 1px dashed var(--border);
}

/* ─── Team points-breakdown modal (src/app.js's showTeamBreakdownModal) ────── */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(5, 8, 15, 0.72);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}

.modal-card {
  position: relative;
  width: 100%;
  max-width: 360px;
  background: var(--card-bg);
  color: var(--card-text);
  border: 1px solid var(--card-border);
  border-top: 4px solid var(--team-color, var(--accent));
  border-radius: var(--radius);
  padding: 32px 28px 26px;
  text-align: center;
  box-shadow: 0 24px 60px -18px rgba(0, 0, 0, 0.6);
}

.modal-close {
  position: absolute;
  top: 10px;
  right: 10px;
  background: none;
  border: none;
  color: var(--card-text-dim);
  font-size: 1.6rem;
  line-height: 1;
  padding: 6px 10px;
  cursor: pointer;
}
.modal-close:hover { color: var(--card-text); }

.modal-card img { display: block; width: 96px; height: 96px; object-fit: contain; margin: 0 auto 14px; filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.14)); }
.modal-card h2 { font-family: var(--font-display); font-weight: 400; letter-spacing: 0.02em; margin: 0 0 16px; font-size: 1.4rem; }
.modal-subtitle { margin: 0; color: var(--card-text-dim); font-size: 0.9rem; }

.breakdown-list { display: flex; flex-direction: column; gap: 2px; text-align: left; }

.breakdown-row {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  font-size: 0.88rem;
  padding: 8px 0;
  border-bottom: 1px solid var(--card-border);
}
.breakdown-row span:last-child { color: var(--card-success); font-weight: 700; white-space: nowrap; }

.breakdown-row.total {
  border-bottom: none;
  border-top: 2px solid var(--card-border);
  margin-top: 6px;
  padding-top: 14px;
  font-weight: 800;
  font-size: 1rem;
}
.breakdown-row.total span:last-child { color: var(--card-accent); }

/* ─── Owner points-breakdown modal (src/app.js's showOwnerBreakdownModal) ──── */
/* A roster's worth of team pills can run taller than we want the modal to
   be, so only the pill list scrolls (not the whole card) — the owner's name
   (+ close button) up top and the Total row at the bottom are separate
   flex-shrink: 0 siblings around it, so both stay pinned in view. */
.owner-breakdown-card {
  max-width: 420px;
  max-height: 55vh;
  display: flex;
  flex-direction: column;
}
.owner-breakdown-card h2 { flex-shrink: 0; }
.owner-breakdown-total { flex-shrink: 0; text-align: left; }

.owner-breakdown-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-align: left;
  overflow-y: auto;
  min-height: 0;
  /* Fade-and-shadow at whichever edge still has pills scrolled out of
     view, so "there's more below" (or above) reads as a visual cue instead
     of relying on the scrollbar alone. Classic CSS-only scroll-shadow trick:
     the two solid-to-transparent "cover" gradients scroll WITH the content
     (background-attachment: local) and mask the two shadow gradients —
     which stay pinned to the list's viewport (attachment: scroll) — right
     where there's nothing left to scroll toward, so each shadow only shows
     once there are pills scrolled past that edge. */
  background-image:
    linear-gradient(var(--card-bg) 30%, rgba(0, 0, 0, 0)),
    linear-gradient(rgba(0, 0, 0, 0), var(--card-bg) 70%),
    radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, 0.22), rgba(0, 0, 0, 0)),
    radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, 0.22), rgba(0, 0, 0, 0));
  background-position: top, bottom, top, bottom;
  background-repeat: no-repeat;
  background-size: 100% 28px, 100% 28px, 100% 10px, 100% 10px;
  background-attachment: local, local, scroll, scroll;
}

/* These pills are informational only (no click-through, unlike the
   identical-looking cards on the Rosters tab) — drop the pointer cursor and
   hover lift so they don't imply an interaction that isn't there. */
.owner-breakdown-list .roster-card { cursor: default; }
.owner-breakdown-list .roster-card:hover { border-color: var(--card-border); transform: none; }

/* These cards are wide enough that the Rosters tab's narrow-column layout
   (name stacked above points) leaves an awkward blank gap on the right —
   put name and points on one row instead: name grows and wraps, points
   stays pinned to the right edge, both vertically centered against the logo. */
.owner-breakdown-list .roster-card {
  padding: 8px 10px;
  gap: 8px;
}
.owner-breakdown-list .roster-card img { width: 24px; height: 24px; }
.owner-breakdown-list .roster-card > div {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex: 1;
  min-width: 0;
}
.owner-breakdown-list .roster-card .name {
  min-height: 0;
  font-size: 0.95rem;
  line-height: 1.2;
  flex: 1;
  min-width: 0;
}
.owner-breakdown-list .roster-card .points {
  font-size: 0.85rem;
  flex-shrink: 0;
}

/* A previously-held, now-dropped team — same pill, dialed back to read as
   inactive rather than a current roster spot. */
.roster-card-dropped { opacity: 0.55; filter: grayscale(0.35); }
.roster-card-dropped .points { color: var(--card-text-dim); }

/* ─── Swap ──────────────────────────────────────────────────────────────── */

.swap-status-banner {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  font-size: 0.8rem;
  color: var(--text-dim);
  padding: 10px 14px;
  margin-bottom: 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.swap-panel {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 20px;
}
.swap-panel label {
  display: block;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-dim);
  margin-bottom: 6px;
}

/* Custom dropdown (src/app.js's createStyledDropdown) — replaces a native
   <select> entirely, including its open popup list, which can't be styled
   cross-browser through CSS alone. */
.styled-dropdown {
  position: relative;
  margin-bottom: 16px;
}

.styled-dropdown-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  background-color: var(--bg-card);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8l3.5-3.5' stroke='%23a89a82' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 11px 36px 11px 14px;
  color: var(--text);
  font-size: 0.95rem;
  font-family: inherit;
  cursor: pointer;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.styled-dropdown-toggle:hover { border-color: var(--accent); }
.styled-dropdown.open .styled-dropdown-toggle {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(193, 144, 47, 0.25);
}
/* Team logos in an option (drop/add swap dropdowns) — plain <img>, no
   src/alt attrs to override here, just sizing/shape to match the option row. */
.styled-dropdown-toggle img,
.styled-dropdown-option img {
  width: 20px;
  height: 20px;
  object-fit: contain;
  flex-shrink: 0;
}

.styled-dropdown-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 20;
  max-height: 260px;
  overflow-y: auto;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px;
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
}

.styled-dropdown-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 12px;
  border-radius: 6px;
  font-size: 0.9rem;
  cursor: pointer;
}
.styled-dropdown-option:hover { background: var(--bg-elevated); }
.styled-dropdown-option.selected { background: var(--accent); color: var(--bg); }

/* ─── Create League modal (src/app.js's showCreateLeagueModal) ─────────────── */
/* Wider and left-aligned, unlike the read-only breakdown modals above this
   card inherits .modal-card's centered/narrow defaults from — this one
   holds a real form. Inputs use their own light-context tokens (a white
   wash over --card-bg, --card-border/--card-text) rather than the dark
   .search-row/.admin-section input styles, which assume the dark app
   chrome those live in, not this light card surface. */
.create-league-card { max-width: 480px; text-align: left; }
.create-league-card h2 { text-align: center; }
.create-league-card .modal-subtitle { text-align: center; margin-bottom: 18px; }

.form-row { margin-bottom: 16px; }
.form-row label {
  display: block;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--card-text-dim);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-bottom: 6px;
}
.form-row input[type="text"], .form-row input[type="email"] {
  width: 100%;
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid var(--card-border);
  border-radius: 8px;
  padding: 10px 12px;
  color: var(--card-text);
  font-size: 0.95rem;
  font-family: inherit;
}

.owner-rows { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
/* Two stacked inputs (name, optional email) per owner, not one row — the
   remove × sits top-right over both via absolute positioning so it doesn't
   need its own flex slot. */
.owner-row {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  background: rgba(255, 255, 255, 0.28);
  border: 1px solid var(--card-border);
  border-radius: 8px;
  padding: 10px 34px 10px 10px;
}
.owner-row-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  background: none;
  border: none;
  color: var(--card-text-dim);
  font-size: 1.3rem;
  line-height: 1;
  padding: 4px 8px;
  cursor: pointer;
}
.owner-row-remove:hover { color: var(--danger); }

.btn-add-owner {
  width: 100%;
  background: none;
  border: 1px dashed var(--card-border);
  border-radius: 8px;
  padding: 9px;
  color: var(--card-text-dim);
  font-size: 0.85rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.btn-add-owner:hover { border-color: var(--card-accent); color: var(--card-accent); }

.form-error {
  color: var(--danger);
  background: rgba(209, 90, 90, 0.12);
  border-radius: 6px;
  padding: 8px 10px;
  font-size: 0.85rem;
  margin: 0 0 14px;
}

.form-actions { display: flex; justify-content: center; margin-top: 8px; }

/* Post-creation: one row per invited owner, each with a copyable link —
   the self-serve equivalent of backend/scripts/generate_invites.py's
   terminal output, but for a commissioner creating a league through the
   UI instead of the one-time migration bootstrap. */
.invite-links-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0 0 18px;
  max-height: 40vh;
  overflow-y: auto;
}
.invite-link-row {
  background: rgba(255, 255, 255, 0.4);
  border: 1px solid var(--card-border);
  border-radius: 8px;
  padding: 10px 12px;
}
.invite-link-name { font-weight: 700; font-size: 0.88rem; margin-bottom: 6px; }
.invite-link-status { font-size: 0.78rem; margin-bottom: 6px; }
.invite-link-status.emailed { color: var(--card-success); font-weight: 600; }
.invite-link-status.failed { color: var(--card-text-dim); }
.invite-link-copy-row { display: flex; gap: 8px; }
.invite-link-copy-row input {
  flex: 1;
  min-width: 0;
  background: rgba(255, 255, 255, 0.6);
  border: 1px solid var(--card-border);
  border-radius: 6px;
  padding: 6px 8px;
  color: var(--card-text-dim);
  font-size: 0.76rem;
  font-family: monospace;
}
.copy-invite-btn { flex-shrink: 0; }

.create-league-picker-btn { margin-top: 14px; width: 100%; }

/* ─── Admin ─────────────────────────────────────────────────────────────── */

.admin-section {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 16px;
}
.admin-section h3 { margin-top: 0; }
.admin-section .seed-row { display: flex; gap: 8px; margin-bottom: 8px; align-items: center; }
.admin-section .seed-row label { width: 60px; color: var(--text-dim); font-size: 0.85rem; }
.admin-section select { flex: 1; background: var(--bg-card); border: 1px solid var(--border); border-radius: 6px; padding: 8px; color: var(--text); }

.error-banner {
  background: rgba(209, 90, 90, 0.15);
  border: 1px solid var(--danger);
  color: var(--danger);
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 16px;
  font-size: 0.9rem;
}

.empty-state { color: var(--text-dim); text-align: center; padding: 40px 20px; }
.roster-column .empty-state { padding: 16px 4px; font-size: 0.8rem; }

@media (max-width: 600px) {
  /* Header and nav both just shrink a little at this width — neither needs
     to restructure. */
  .app-header { padding: 10px 16px; }
  .brand-mark { height: 44px; }
  .league-name { font-size: 0.76rem; }
  .tabs { padding: 8px 10px; }
  main#tab-content { padding: 16px 12px 60px; }
  .team-grid { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }

  /* Rosters: vertical stack of member cards instead of 5 fixed columns,
     each card's teams as wrapping pills instead of one-per-row blocks. */
  .rosters-board-wrap { overflow-x: visible; }
  .rosters-board { grid-template-columns: 1fr; min-width: 0; gap: 10px; }

  .roster-pills { flex-direction: row; flex-wrap: wrap; gap: 6px; }

  .roster-card {
    /* Exactly 3 per row regardless of card width, gap included in the math
       (2 gaps per row of 3), rather than a fixed min-width that wraps at an
       unpredictable count. */
    flex: 0 0 calc((100% - 12px) / 3);
    min-width: 0;  /* let the name below actually wrap instead of forcing the row wider */
    align-items: flex-start;  /* logo stays top-aligned once names wrap to 2 lines */
    border-left: none;
    border-radius: 14px;  /* a full 999px pill reads oddly once it's several lines tall */
    padding: 5px 8px;
    gap: 6px;
  }
  .roster-card img { width: 18px; height: 18px; }
  .roster-card > div { min-width: 0; }
  .roster-card .name {
    font-size: 0.66rem;
    line-height: 1.25;
    white-space: normal;
    overflow-wrap: break-word;
    min-height: 2.5em;
  }
  .roster-card .points { font-size: 0.6rem; white-space: nowrap; }
}
