/* No custom font anywhere -- the whole site runs on each device's own system
   font stack, the same one Tailwind's default font-sans ships (ui-sans-serif,
   system-ui, ... "Apple Color Emoji"). That means Segoe UI on Windows, Roboto
   on Android, San Francisco on iOS -- one CSS rule, no @font-face, no Google
   Fonts request to wait on. Inter, Inter Tight, Montserrat and JetBrains Mono
   (and the display "poster" look they gave the public pages' headlines) are
   gone; every family below points at the same stack now. */
:root {
  --ink: #06080c;
  --ink-2: #0b0e14;
  --ink-3: #121720;
  --line: rgba(255, 255, 255, 0.08);
  --line-strong: rgba(255, 255, 255, 0.15);
  --brand: #1fae5e;
  --brand-dim: #158548;
  --brand-soft: rgba(31, 174, 94, 0.14);
  --brand-pink: #ec008c;
  --brand-pink-soft: rgba(236, 0, 140, 0.12);
  --text: #eef2f7;
  --muted: #8d99a9;
  --ok: #34d399;
  --warn: #fbbf24;
  --bad: #f87171;
  --radius: 12px;
  --shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
  --font-display: var(--font);
  --font-title: var(--font);
  --mono: ui-monospace, "Cascadia Mono", "Segoe UI Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
  /* The parts the browser draws itself, which no stylesheet reaches: the list a
     <select> pops open, and the scrollbars. Without this the popup stays on the
     OS default white while its text inherits the select's near-white ink, and
     the options are invisible until you hover one. */
  color-scheme: dark;
}

/* Light theme. The dark values above stay the default; the toggle in the header
   puts data-theme="light" on <html> and this block swaps the surface, text and
   line tokens. The rules after the token list patch the places that hard-code
   white on a dark surface. Photo sections — the hero, active sports, the login
   card's backdrop — keep their dark scrims in both themes: their text sits on a
   picture, not on a surface colour. */
/* Light grey rather than paper-white: neutral grey surfaces, the page a shade
   deeper than the cards sitting on it. Nothing on screen is pure #fff. */
:root[data-theme="light"] {
  --ink: #d6d8da;
  --ink-2: #e9eaec;
  --ink-3: #c9ccd0;
  --line: rgba(30, 34, 38, 0.14);
  --line-strong: rgba(30, 34, 38, 0.26);
  --text: #1a1d21;
  --muted: #585d63;
  --shadow: 0 20px 50px rgba(30, 34, 38, 0.14);
  color-scheme: light;
}

:root[data-theme="light"] a:hover {
  color: var(--brand-dim);
}

:root[data-theme="light"] .brand,
:root[data-theme="light"] .section-head h2,
:root[data-theme="light"] .stats-card-header h2,
:root[data-theme="light"] .meta-detail strong,
:root[data-theme="light"] .stat-number,
:root[data-theme="light"] .sport-card h3,
:root[data-theme="light"] .sport-card dd,
/* .pick--plain deliberately keeps its title muted like its caption, in both themes. */
:root[data-theme="light"] .pick:not(.pick--plain) .txt b,
:root[data-theme="light"] #regForm h3,
:root[data-theme="light"] .modal-title {
  color: var(--text);
}

/* The homepage header sits on the hero photo, which stays dark in both themes. */
:root[data-theme="light"] .page-home .site-header .brand {
  color: #fff;
}

:root[data-theme="light"] input[type="text"],
:root[data-theme="light"] input[type="email"],
:root[data-theme="light"] input[type="tel"],
:root[data-theme="light"] input[type="password"],
:root[data-theme="light"] input[type="number"],
:root[data-theme="light"] input[type="date"],
:root[data-theme="light"] input[type="time"],
:root[data-theme="light"] select,
:root[data-theme="light"] textarea,
:root[data-theme="light"] .pick,
/* background-color, not the background shorthand: a <select> carries its drop
   arrow as a background-image, and the shorthand was resetting it to none — the
   filters lost their arrow on this theme and read as plain text boxes. */
:root[data-theme="light"] .radio-pill {
  background-color: #f0f1f3;
}

:root[data-theme="light"] .sport-card {
  background: rgba(240, 241, 243, 0.92);
}

/* The schedule/venue block inside a sport card is a dark translucent panel in the
   dark theme, which only turns grey over a light card — light green instead. */
:root[data-theme="light"] .sport-card dl {
  background: #e1f5e9;
  border-color: rgba(31, 174, 94, 0.3);
}

:root[data-theme="light"] .pick:hover,
:root[data-theme="light"] .radio-pill:hover:not(:has(input:checked)) {
  border-color: rgba(30, 34, 38, 0.36);
  color: var(--text);
}

:root[data-theme="light"] .pick:has(input:checked) {
  background: #eaf9f0;
}

/* The checked pill is white-on-brand-green in the dark theme. On this theme the
   blanket background-color a few rules up outweighs that rule's background —
   `:root[data-theme] .radio-pill` scores higher than `.radio-pill:has(...)` — so
   the pill was keeping its pale grey fill while the white text carried over,
   leaving the one selected option as the only unreadable label in the row.
   Light green fill with dark type instead: the selected pill still separates from
   the plain grey ones beside it without going to a solid green that would need
   white text to sit on. Restated here rather than exempting the pill from the
   blanket rule, so both colours are set in one place. */
:root[data-theme="light"] .radio-pill:has(input:checked),
:root[data-theme="light"] .radio-pill.is-on {
  background-color: #e1f5e9;
  color: var(--text);
}

:root[data-theme="light"] .page-home .site-footer {
  background: linear-gradient(180deg, #e2e4e6 0%, #d6d8da 55%, #cacdd0 100%);
}

:root[data-theme="light"] .modal-close-btn,
:root[data-theme="light"] .card-close-btn {
  background: rgba(30, 34, 38, 0.06);
  border-color: var(--line-strong);
  color: var(--muted);
}

:root[data-theme="light"] .modal-close-btn:hover,
:root[data-theme="light"] .card-close-btn:hover {
  background: rgba(30, 34, 38, 0.12);
  color: var(--text);
}

* {
  box-sizing: border-box;
}

/* One full-bleed section or another has, at some width, always ended up a
   few pixels wider than the viewport -- the calendar grid and the phone
   hamburger drawer both did before they were caught. Rather than run that
   fire drill again for the next one, the document itself refuses to scroll
   sideways: whatever overflows just gets clipped instead of leaving a strip
   of the browser's own white past the edge of the (dark) page. */
html {
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}

body {
  margin: 0;
  overflow-x: hidden;
  background: var(--ink);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* color-scheme (above) is what themes native popups and, in theory, the
   scrollbar -- but plenty of Android browsers draw their own accent-coloured
   scrollbar over that regardless (a persistent lavender-grey band down the
   right edge on some devices, not the thin auto-hiding line Chrome itself
   draws), which reads as a bug in the page even though nothing here put it
   there. Drawing it here explicitly, rather than trusting color-scheme
   alone, is what actually reaches every one of them. */
::-webkit-scrollbar {
  width: 8px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 4px;
}

a {
  color: var(--brand);
  text-decoration: none;
  transition: color 0.15s;
}

a:hover {
  color: #fff;
}

img {
  max-width: 100%;
}

h1,
h2,
h3,
h4 {
  letter-spacing: -0.02em;
  line-height: 1.1;
  margin: 0 0 0.5em;
}

h1 {
  font-size: clamp(2.2rem, 6vw, 4rem);
  font-weight: 900;
}

h2 {
  font-size: clamp(1.6rem, 4vw, 2.5rem);
  font-weight: 800;
}

h3 {
  font-size: 1.25rem;
  font-weight: 700;
}

p {
  margin: 0 0 1rem;
}

.wrap {
  width: min(1200px, 100% - 2.5rem);
  margin-inline: auto;

}

main.wrap {
  padding-top: 120px !important;
}

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

.small {
  font-size: 0.85rem;
}

.mono {
  font-family: var(--mono);
}

.center {
  text-align: center;
}

.hidden {
  display: none !important;
}

.stack>*+* {
  margin-top: 1rem;
}

.brand {
  font-family: var(--font);
  font-weight: 900;
  font-style: normal !important;
  font-size: 2.3rem;
  letter-spacing: -0.02em;
  color: #fff;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  text-transform: uppercase;
}

.brand__logo-box {
  width: 44px;
  height: 44px;
  background: #0fbf5e;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.brand__logo-box img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  border-radius: 6px;
}

.brand__text {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-family: var(--font);
  font-weight: 900;
  font-style: normal !important;
  letter-spacing: -0.02em;
}

.brand__text span {
  color: var(--brand);
}

.brand--lg {
  font-size: 2.5rem;
  margin-bottom: 1.5rem;
  justify-content: center;
}

.brand--lg .brand__logo-box {
  width: 50px;
  height: 50px;
  background: transparent;
  border-radius: 8px;
  overflow: hidden;
}

.brand--lg .brand__logo-box img {
  width: 50px;
  height: 50px;
  object-fit: contain;
  display: block;
  border-radius: 8px;
}

/* width:100vw rather than right:0 -- on any page tall enough to scroll (most
   of them), a classic scrollbar reserves part of the browser's own width,
   and right:0 resolves against the space that leaves rather than the true
   edge of the screen, so the header stopped short of it by however wide the
   scrollbar was. Same fix, same reason, as .phone-tabs further down. 100vw
   is measured including the scrollbar, so it reaches the real edge either
   way; the sliver it then runs under the scrollbar itself is not visible. */
.site-header {
  position: absolute;
  top: 0;
  left: 0;
  width: 100vw;
  z-index: 50;
  /* No bar or rule of its own — the header floats straight over the hero photo. */
  background: none;
  border-bottom: none;
}

/* Full bleed: the brand sits hard against the left edge, the nav against the
   right, rather than inside the centred content column. */
.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  height: 80px;
  max-width: none;
  width: 100%;
  margin-inline: 0;
  /* The brand stays at the left edge; the nav sits close to the right edge. */
  padding-inline: 2rem 1.5%;
}

@media (max-width: 700px) {
  .site-header .wrap {
    padding-inline: 1.25rem;
  }
}

.nav {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding-left: 60px;
  margin-left: auto;
}

.site-header .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  height: 80px;
}

/* Dark/light switch. Sits at the end of the nav and shows the theme it switches
   to, not the one you are in. */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex: none;
  border-radius: 50%;
  border: 1px solid rgba(6, 8, 12, 0.3);
  background: rgba(255, 255, 255, 0.55);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s, transform 0.2s;
}

.theme-toggle:hover {
  background: #fff;
  transform: translateY(-1px);
}

:root[data-theme="light"] .theme-toggle {
  background: rgba(30, 34, 38, 0.08);
  border-color: var(--line-strong);
}

:root[data-theme="light"] .theme-toggle:hover {
  background: rgba(30, 34, 38, 0.16);
}

/* With no header bar behind them, the brand and links carry their own contrast
   against the brighter photo. */
/* Tight enough to lift the wordmark off the photo without the smudged halo a
   12px blur leaves around it. */
.site-header .brand {
  text-shadow: 0 1px 3px rgba(6, 8, 12, 0.55);
  font-size: 1.7rem;
  gap: 0.5rem;
}

.site-header .brand .brand__logo-box {
  width: 34px;
  height: 34px;
}

/* On a light page the wordmark is dark ink on a plain surface — a dark shadow
   there only reads as a blur. */
:root[data-theme="light"] body:not(.page-home) .site-header .brand {
  text-shadow: none;
}

/* The links are black, so they need a light halo rather than a dark one to stay
   legible where the photo behind them goes dark. */
.site-header .nav a:not(.btn) {
  text-shadow: 0 1px 10px rgba(255, 255, 255, 0.55);
}

/* Plain text links only — nav items that are .btn keep the button sizing. */
.nav a:not(.btn) {
  color: #06080c;
  text-decoration: none;
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  transition: color 0.15s, transform 0.15s;
  font-family: var(--font-display);
}

/* Black links that deepen into a dark green on hover. */
.nav a:not(.btn):hover {
  color: #0a5c2f;
  transform: translateY(-1px);
}

/* Black reads well over the hero photo, but on the dark theme's plain pages it
   sinks into the background — those get light links and a light hover instead. */
:root[data-theme="dark"] body:not(.page-home) .site-header .nav a:not(.btn) {
  color: #cdd3dc;
  text-shadow: 0 1px 12px rgba(6, 8, 12, 0.75);
}

:root[data-theme="dark"] body:not(.page-home) .site-header .nav a:not(.btn):hover {
  color: var(--brand);
}

/* Logged-in player nav: a light-green pill holding every item, echoing the
   rounded tab bar of the reference design. */
.nav--player {
  gap: 0.3rem;
  background: var(--brand-soft);
  border-radius: 999px;
  padding: 0.45rem;
  align-self: center;
  margin-block: auto;
  margin-top: 0;
}

/* Every item is a square icon box the size of the theme toggle next to it. The
   name is collapsed to nothing rather than removed, so it still reads as the
   link's accessible name — only .is-current below opens it back up. */
.nav--player .nav-link {
  position: relative;
  padding: 0;
  gap: 0;
  min-width: 44px;
  height: 44px;
  overflow: hidden;
  border: none;
  background: transparent;
  border-radius: 999px;
  /* The box widens because the name inside it does — that is what is animated. */
  transition: background 0.2s, border-color 0.2s, color 0.2s;
}

/* Plain hover: the icon itself turns solid black rather than the site's brand
   green, matching the reference design's monochrome hover. */
.nav--player .nav-link:hover:not(.is-current) {
  color: #000;
  background: rgba(0, 0, 0, 0.06);
}

.nav--player .nav-link .nav-ico {
  flex: none;
  width: 44px;
  display: grid;
  place-items: center;
  font-size: 1.1rem;
  line-height: 1;
}

.nav--player .nav-link .nav-name {
  max-width: 0;
  opacity: 0;
  overflow: hidden;
  white-space: nowrap;
  transition: max-width 0.25s ease, opacity 0.2s ease, padding 0.25s ease;
}

/* Unread count on the bell. Anchored over the icon rather than the box's own
   top-right corner, so it stays sitting on the bell if the box ever widens. */
.nav--player .nav-dot {
  position: absolute;
  top: 3px;
  left: 20px;
  min-width: 15px;
  height: 15px;
  padding-inline: 3px;
  border-radius: 999px;
  background: var(--bad);
  color: #fff;
  font-size: 0.58rem;
  font-weight: 800;
  line-height: 15px;
  text-align: center;
  overflow: hidden;
  white-space: nowrap;
}

/* Clipped away from the page but still in the accessibility tree, so the link
   announces "Notifications 3 unread" and not a bare number. */
.nav--player .nav-dot-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* Border highlight for the page you are currently on. Icon stays square. */
.nav .nav-link.is-current {
  border-color: var(--brand);
  color: var(--brand);
  background: rgba(31, 174, 94, 0.12);
  box-shadow: inset 0 0 0 1px var(--brand);
  cursor: default;
}

/* The player nav's current tab is a solid black pill instead — the pill bar's
   own light-green background already does the "you are here" job the outline
   used to, so the active tab reads as a filled tab rather than a highlight. */
.nav--player .nav-link.is-current {
  border-color: transparent;
  color: #fff;
  background: #111318;
  box-shadow: none;
}

/* Log out sits on the row's own gap, evenly with every other box — its red tells
   it apart, so it does not need a hole beside it as well. It is never the current
   page, so it stays an icon box until you press it — dashboard.js then closes
   whichever item was open and hands it .is-open, so the box you just pressed is
   the one showing its name while the request goes through. */
.nav .nav-link--logout {
  border-color: rgba(248, 113, 113, 0.45);
  color: var(--bad);
}

.nav .nav-link--logout.is-open {
  border-color: var(--bad);
  background: rgba(248, 113, 113, 0.12);
}

.nav .nav-link--logout:hover:not(:disabled) {
  border-color: var(--bad);
  background: rgba(248, 113, 113, 0.12);
  color: var(--bad);
}

/* Narrow screens tighten the whole row at once — one gap, so Log out follows. */
@media (max-width: 900px) {
  .nav--player {
    gap: 0.35rem;
    flex-wrap: wrap;
    justify-content: flex-end;
  }
}

/* Notification Dropdown in Header */
.notif-dropdown {
  position: absolute;
  top: 48px;
  left: 50%;
  transform: translateX(-50%);
  width: 360px;
  max-width: 90vw;
  background: var(--ink-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  z-index: 1000;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* Anchored to the header itself (now the nearest positioned ancestor) rather
   than the small per-icon wrapper, so it centers on the header's own width —
   which spans the full card — instead of risking an overflow off one edge of
   the screen when the bell sits off-centre inside a wide nav. */
body.player-page .notif-dropdown {
  top: 100%;
  margin-top: 0.5rem;
}

:root[data-theme="light"] .notif-dropdown {
  background: #ffffff;
}

.notif-dropdown-header {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: rgba(255, 255, 255, 0.03);
}

.notif-dropdown-body {
  max-height: 380px;
  overflow-y: auto;
  padding: 0.5rem;
}

.notif-dropdown-body .notif-card {
  margin-bottom: 0.5rem;
  padding: 0.75rem;
  font-size: 0.85rem;
  border-radius: 8px;
}

.notif-dropdown-body .notice-ico {
  width: 28px;
  height: 28px;
  font-size: 0.85rem;
}

.notif-dropdown-body .notice-head h3 {
  font-size: 0.95rem;
}

/* Modal Backdrop & Blur Styling */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(6, 8, 12, 0.72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
}

.modal-backdrop.hidden {
  display: none !important;
}

.modal-card {
  background: var(--ink-2);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  color: var(--text);
}

:root[data-theme="light"] .modal-card {
  background: #ffffff;
}

/* --- AESTHETIC HERO --- */
.hero {
  position: relative;
  min-height: 100vh;
  display: flex;
  align-items: center;
  overflow: hidden;
  padding-top: 80px;
}

/* The golf photo, pulled down a little from its own exposure so the hero does
   not glare and the white headline keeps its contrast. */
.hero::before {
  content: "";
  position: absolute;
  /* Overshoots the section by a pixel on every side. At browser zoom levels that
     land on fractional device pixels, a background sized exactly to its box
     leaves a hairline of the page behind showing along the edge. */
  inset: -1px;
  background-image: url('../images/hero_golf_cartoon.png');
  background-size: cover;
  background-position: 66% 35%;
  filter: brightness(0.7) contrast(1.02) saturate(1.05);
  z-index: 0;
}

/* Narrow screens centre the hero text over the photo — give it its own scrim
   so the headline stays readable. */
@media (max-width: 700px) {
  .hero-text-block {
    background: rgba(6, 8, 12, 0.62);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    border-radius: 16px;
    padding: 1.5rem 1.25rem 1.75rem;
    max-width: 340px;
    margin-inline: auto;
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
  }
}

/* Darkens the left side so the headline sits on a deep enough background,
   fading out across the middle to leave the photo clean. */
.hero-bg-overlay-left {
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, rgba(6, 8, 12, 0.78) 0%, rgba(6, 8, 12, 0.38) 30%, transparent 50%);
  z-index: 1;
}

/* Barely there — the right of the photo must not be muddied. */
.hero-bg-overlay-right {
  position: absolute;
  inset: 0;
  background: linear-gradient(290deg, rgba(6, 8, 12, 0.1) 0%, transparent 22%);
  z-index: 1;
}

.hero-content {
  position: relative;
  z-index: 10;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: calc(100vh - 160px);
  min-height: 480px;
  width: 100%;
}

.hero-text-block {
  margin-top: auto;
  margin-bottom: auto;
  max-width: 650px;
  padding-left: clamp(1rem, 4vw, 4rem);
}

.hero-title {
  font-family: var(--font);
  font-weight: 900;
  font-style: normal;
  /* Stays on one line at every width — the viewport-based size shrinks it to
     fit rather than letting it wrap. */
  white-space: nowrap;
  font-size: clamp(1.9rem, 6.4vw, 4.4rem);
  text-transform: uppercase;
  color: #fff;
  line-height: 1.15;
  letter-spacing: -0.01em;
  margin-bottom: 1rem;
  text-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
}

.hero-subtitle {
  font-family: var(--font);
  font-weight: 700;
  font-style: normal;
  font-size: clamp(1rem, 1.8vw, 1.3rem);
  text-transform: uppercase;
  color: #fff;
  letter-spacing: 0.12em;
  margin-bottom: 1.8rem;
  opacity: 0.95;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}

.hero-actions-row {
  display: flex;
  gap: 1rem;
}

/* --- "ARE YOU READY?" sign card --- */
/* Phone-only: hidden everywhere else. The .hero-text-block--desktop block
   above it is what desktop and tablet keep showing instead — see the
   max-width:880px swap below. */
.ready-card--mobile {
  display: none;
}

.ready-card {
  position: relative;
  width: 100%;
  max-width: 380px;
  margin-inline: auto;
  background: linear-gradient(180deg, #0a140f 0%, #080f0b 100%);
  border: 1px solid rgba(31, 174, 94, 0.45);
  border-radius: 22px;
  padding: 2.5rem 1.75rem 1.75rem;
  text-align: center;
  box-shadow: 0 20px 45px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(31, 174, 94, 0.08) inset;
}

.ready-card__badge {
  position: absolute;
  top: -26px;
  left: 50%;
  transform: translateX(-50%);
  width: 52px;
  height: 52px;
  display: grid;
  place-items: center;
  background: #0a140f;
  border: 1px solid rgba(31, 174, 94, 0.5);
  clip-path: polygon(50% 0%, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
}

.ready-card__badge img {
  width: 26px;
  height: 26px;
  object-fit: contain;
}

.ready-card__title {
  font-family: var(--font);
  font-weight: 900;
  font-style: normal;
  text-transform: uppercase;
  line-height: 1.1;
  letter-spacing: -0.01em;
  margin: 0.5rem 0 1rem;
  display: flex;
  flex-direction: column;
  font-size: clamp(1.9rem, 8vw, 2.6rem);
}

.ready-card__title span:first-child {
  color: #fff;
}

.ready-card__title-accent {
  background: linear-gradient(180deg, #5fd68f 0%, #1fae5e 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.ready-card__divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  color: #1fae5e;
  margin-bottom: 1.1rem;
}

.ready-card__divider span {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(31, 174, 94, 0.6), transparent);
}

.ready-card__subtitle {
  font-family: var(--font);
  font-weight: 700;
  font-style: normal;
  text-transform: uppercase;
  font-size: clamp(0.85rem, 2.6vw, 1rem);
  letter-spacing: 0.06em;
  line-height: 1.5;
  color: #fff;
  margin-bottom: 1.5rem;
}

.ready-card__subtitle-accent {
  color: #5fd68f;
}

.ready-card__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  width: 100%;
  background: linear-gradient(180deg, #1fae5e 0%, #158548 100%);
  color: #fff;
  font-family: var(--font);
  font-weight: 800;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 0.95rem 1.25rem;
  border-radius: 14px;
  box-shadow: 0 10px 25px rgba(31, 174, 94, 0.4);
  transition: transform 0.2s ease;
}

.ready-card__btn:hover {
  transform: translateY(-2px);
}

.ready-card__btn-ico {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.15);
  flex: none;
}

.ready-card__features {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid rgba(31, 174, 94, 0.2);
}

.ready-card__feature {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  flex: 1;
  min-width: 0;
  color: #1fae5e;
}

.ready-card__feature div {
  line-height: 1.3;
}

.ready-card__feature strong {
  display: block;
  font-family: var(--font);
  font-size: 0.7rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #fff;
}

.ready-card__feature small {
  display: block;
  font-size: 0.62rem;
  color: rgba(255, 255, 255, 0.55);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.btn-aesthetic {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--brand);
  color: #fff;
  font-family: var(--font);
  font-weight: 800;
  font-style: normal;
  font-size: 0.8rem;
  letter-spacing: 0.1em;
  padding: 0.78rem 1.6rem;
  border: 1px solid var(--brand);
  border-radius: 0;
  cursor: pointer;
  text-transform: uppercase;
  transition: all 0.25s ease;
  clip-path: polygon(8px 0%, 100% 0%, calc(100% - 8px) 100%, 0% 100%);
}

/* The arrow rides a step forward on hover — the button is inline-flex, so it
   sits on its own baseline-free line box and the nudge costs no reflow. */
.btn-arrow {
  display: inline-block;
  margin-inline-start: 0.55rem;
  transition: transform 0.25s ease;
}

.btn-aesthetic:hover .btn-arrow {
  transform: translateX(4px);
}

.btn-aesthetic:hover {
  background: #fff;
  color: var(--ink);
  border-color: #fff;
  transform: translateY(-2px);
  box-shadow: 0 10px 20px rgba(31, 174, 94, 0.3);
}

.hero-scroll-indicator {
  display: flex;
  align-items: center;
  gap: 1rem;
  /* Pinned to the bottom of the hero, then nudged a little lower and in from the
     left edge so it does not sit hard in the corner. */
  margin-top: auto;
  margin-bottom: -1.25rem;
  margin-left: clamp(0.5rem, 2vw, 2rem);
  color: rgba(255, 255, 255, 0.5);
  font-size: 0.9rem;
}

.mouse-icon {
  width: 18px;
  height: 29px;
  border: 1.5px solid rgba(255, 255, 255, 0.5);
  border-radius: 11px;
  position: relative;
  display: flex;
  justify-content: center;
}

.mouse-wheel {
  width: 2px;
  height: 5px;
  background: #fff;
  border-radius: 1px;
  position: absolute;
  top: 5px;
  animation: scrollMouse 1.8s ease infinite;
}

@keyframes scrollMouse {
  0% {
    transform: translateY(0);
    opacity: 1;
  }

  50% {
    transform: translateY(5px);
    opacity: 0;
  }

  100% {
    transform: translateY(0);
    opacity: 1;
  }
}

/* --- CYAN BANNER --- */
.blue-announcement-banner {
  background: var(--brand);
  color: #fff;
  padding: 0.58rem 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  overflow: hidden;
  position: relative;
  width: 100%;
  /* No edging of its own — a translucent white rule on cyan just reads as a pale
     stripe against the dark sections either side of it. */
  border-bottom: none;
  /* The strip is one flat colour top to bottom: a text line box taller than the
     strip would leave slivers of the section behind showing at the edges. */
  line-height: 1.6;
}

@media (max-width: 520px) {
  /* Smaller text reads as scrolling rather than as a word getting cut off
     mid-letter every time it crosses the edge -- more of each word is on
     screen at once at this size. The shorter padding below follows the
     smaller text down rather than leaving the strip as tall as it was. */
  .blue-announcement-banner {
    padding: 0.45rem 0;
    font-size: 0.56rem;
    letter-spacing: 0.12em;
  }
}

/* The marquee element also carries .wrap, whose centred 1200px width fights the
   scrolling: it pads the strip out to a fixed width, so the animation spends most
   of its cycle dragging empty blue past and then snaps the text through. Sizing
   it to its own text — and starting it one banner-width off the right edge —
   makes the scroll continuous. */
.banner-text {
  display: inline-block;
  white-space: nowrap;
  width: max-content;
  max-width: none;
  margin-inline: 0;
  padding-left: 100%;
  animation: marquee 25s linear infinite;
}

@keyframes marquee {
  0% {
    transform: translate3d(0, 0, 0);
  }

  100% {
    transform: translate3d(-100%, 0, 0);
  }
}

/* Both cyan strips scroll the same way from the same starting point — no delay
   on either, so they stay in step. */

/* --- COLLECTION INTRO --- */
.collection-intro-section {
  position: relative;
  padding: clamp(6rem, 12vw, 10rem) 0;
  overflow: hidden;
}

.collection-intro-section::before {
  content: "";
  position: absolute;
  inset: -1px;
  background-image: url('../images/basketball_hd.png');
  background-size: cover;
  background-position: center;
  filter: brightness(1.05) contrast(1.1) saturate(1.05);
  z-index: 0;
}

.collection-bg-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, rgba(6, 8, 12, 0.8) 0%, rgba(6, 8, 12, 0.45) 45%, rgba(6, 8, 12, 0.15) 75%, transparent 100%);
  z-index: 1;
}

.collection-intro-content {
  position: relative;
  z-index: 10;
}

.collection-text-block {
  max-width: 580px;
}

.collection-text-block h2 {
  font-family: var(--font-display);
  font-weight: 900;
  font-style: italic;
  font-size: clamp(2rem, 5vw, 3rem);
  text-transform: uppercase;
  color: #fff;
  letter-spacing: -0.02em;
  margin-bottom: 0.5rem;
}

.collection-sub {
  font-size: 1rem;
  color: #cdd3dc;
  margin-bottom: 2.2rem;
  font-style: italic;
}

/* --- DIVISIONS GRID --- */
/* Light ground. This block used to carry the login card's backdrop -- the same
   bg.webp photo under a near-black scrim and a green radial glow -- which put a
   dark band between the announcement bar and the light footer and read as the
   heaviest thing on the page. The cards are dark and photo-backed on their own,
   so they carry the contrast; the section behind them only has to get out of
   their way. The dark theme keeps the old backdrop, below. */
.divisions-section {
  padding: 2.5rem 0 3rem;
  background: #f5f7f9;
  /* The cyan banner above already separates this block. A translucent white rule
     on top of it only reads as a pale seam between the two. */
  border-top: none;
}

:root[data-theme="dark"] .divisions-section {
  background:
    radial-gradient(760px 420px at 50% -12%, rgba(31, 174, 94, 0.15), transparent 60%),
    linear-gradient(180deg, rgba(6, 8, 12, 0.86) 0%, rgba(6, 8, 12, 0.78) 50%, rgba(6, 8, 12, 0.9) 100%),
    url('../images/bg.webp?v=2') center / cover no-repeat,
    var(--ink);
}

/* --- The section's masthead --- */
/* Sized off the same width formula as .tickets-grid rather than .wrap (1200px),
   so its edges line up with the grid of cards under it at every width. */
.sports-intro {
  position: relative;
  width: min(1420px, 100vw - 2.5rem);
  margin: 0 auto 1.5rem;
  min-height: 160px;
  display: flex;
  align-items: center;
  border-radius: 14px;
  overflow: hidden;
  background: #0b2418;
}

.sports-intro__photo {
  position: absolute;
  inset: 0 0 0 30%;
  background: url('../images/lawn-tennis.webp') center / cover no-repeat;
}

/* Painted over the photo, under the copy: the generated box comes after the
   photo element in paint order, and .sports-intro__text lifts itself above
   both with a z-index. */
.sports-intro::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, #0b2418 0%, #0b2418 40%, rgba(11, 36, 24, 0.72) 62%, rgba(11, 36, 24, 0.1) 100%);
}

.sports-intro__text {
  position: relative;
  z-index: 2;
  padding: 1.6rem clamp(1.25rem, 3vw, 2.25rem);
}

.sports-intro__eyebrow {
  margin: 0;
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--brand);
}

.sports-intro__title {
  margin: 0.4rem 0 0;
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 2.6vw, 1.95rem);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  line-height: 1.15;
  color: #fff;
}

.sports-intro__title span {
  color: var(--brand);
}

/* The line directly under the title -- what the title itself used to say. */
.sports-intro__lede {
  margin: 0.45rem 0 0;
  font-size: 0.9rem;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.82);
}

.sports-intro__blurb {
  margin: 0.55rem 0 0;
  max-width: 38ch;
  font-size: 0.86rem;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.78);
}

/* Shorter on a wide screen: the blurb held to 38ch ran to three lines and
   set the panel's height, so the copy takes up to 60% of the panel instead
   -- one line at full width, two on a small laptop, still over the dark
   side of the gradient -- and the padding and gaps come in a step. */
@media (min-width: 881px) {
  .sports-intro {
    min-height: 0;
  }

  .sports-intro__text {
    max-width: 60%;
    padding-block: 1.1rem;
  }

  .sports-intro__title {
    margin-top: 0.25rem;
  }

  .sports-intro__lede {
    margin-top: 0.3rem;
  }

  .sports-intro__blurb {
    max-width: none;
    margin-top: 0.35rem;
  }
}

/* The photo has nowhere useful to sit once the panel is only as wide as one
   card, and the copy would be reading over the busiest part of it. */
@media (max-width: 700px) {
  .sports-intro__photo {
    inset: 0 0 0 55%;
  }

  .sports-intro::after {
    background: linear-gradient(90deg, #0b2418 0%, #0b2418 58%, rgba(11, 36, 24, 0.8) 78%, rgba(11, 36, 24, 0.25) 100%);
  }
}

/* The "Active Sports" showcase reuses the .ticket component (same stub +
   perforation + photo look as the registration page's sport tickets) instead
   of the old edge-to-edge photo tiles — .tickets-grid already handles the
   four-across layout, this just adds the section's own spacing on top and
   keeps the per-slug photo classes .ticket__photo reads its background from. */
.divisions-grid {
  margin-top: 0;
  /* This element carries both .wrap (margin-inline:auto) and .tickets-grid
     (position:relative;left:50%;transform:translateX(-50%)) — two different
     centering methods stacked on the same box. Both fire at once: the auto
     margins centre the element first, then the left/transform shift is
     layered on top of that already-centred position, which is what was
     pushing the whole grid off toward the right edge. .wrap's margins add
     nothing once .tickets-grid's own left/transform trick is centring it,
     so they're zeroed out here to leave only the one centering method. */
  margin-inline: 0;
}

.division-ticket .ticket__body {
  min-height: 200px;
}

.division-basketball {
  background-image: url('../images/basketball.webp');
}

.division-golf {
  background-image: url('../images/golf.webp');
}

.division-pickleball {
  background-image: url('../images/pickleball.webp');
}

.division-funrun {
  background-image: url('../images/funrun.webp');
}

.division-badminton {
  background-image: url('../images/badminton.webp');
}

.division-volleyball {
  background-image: url('../images/volleyball.webp');
}

.division-lawntennis {
  background-image: url('../images/lawn-tennis.webp');
}

/* The shooting range photo is filed under its own name rather than the slug. */
.division-shooting {
  background-image: url('../images/firerange.webp');
}

.division-ticket__blurb {
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--muted);
  margin: 0.4rem 0 0;
}

/* The two notches .ticket paints to read as a torn-off ticket are circles
   filled with the page ink, so they only disappear against a dark section. On
   the light ground above they showed as a dark blob top and bottom of every
   card. Dropped here along with the stub's dashed perforation, which was the
   other half of the same effect. */
.division-ticket::before,
.division-ticket::after {
  display: none;
}

.division-ticket .ticket__stub {
  border-right: none;
}

/* A card floating on light grey needs its own drop; .ticket's border is a
   translucent white that vanishes there. */
.division-ticket {
  box-shadow: 0 10px 26px rgba(16, 24, 40, 0.1);
}

:root[data-theme="dark"] .division-ticket {
  box-shadow: none;
}

/* The spine reads the sport's name, not its slug, so "lawntennis" is "LAWN
   TENNIS" -- which is a label rather than a serial number, hence the interface
   font in place of .ticket__serial's mono. */
.division-ticket .ticket__serial {
  font-family: var(--font);
  font-size: 0.56rem;
  font-weight: 800;
  letter-spacing: 0.13em;
}

.division-ticket .ticket__body h3 {
  font-style: normal;
  font-weight: 800;
}

/* Calendar and pin in front of the labels. min-width holds the two dd values in
   one column the way .ticket__meta's own 52px did before the icons widened the
   term. */
.division-ticket__meta dt {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  min-width: 68px;
}

.division-ticket__meta dt .icon {
  flex: none;
  opacity: 0.8;
}

/* The sport photo, left to read as a photograph. .ticket dims it to 0.55 and
   then lays a scrim over the whole card that never falls below 0.35 -- two
   veils stacked, which is what left every picture here grey and unreadable.
   The card still needs a dark ground under its copy, so the dimming moves
   entirely into the scrim below and is spent where the text actually sits. */
.division-ticket .ticket__photo,
.division-ticket:hover .ticket__photo {
  opacity: 1;
}

/* Near-solid behind the title, blurb and meta rows on the left, then open by
   the middle of the card so the subject of the photo -- the ball, the racket,
   the runners -- comes through at full strength on the right. */
.division-ticket .ticket__body::after {
  background: linear-gradient(100deg,
      rgba(9, 12, 17, 0.95) 0%,
      rgba(9, 12, 17, 0.9) 34%,
      rgba(9, 12, 17, 0.46) 60%,
      rgba(9, 12, 17, 0.06) 100%);
}

/* --muted (#8d99a9) was picked to sit on a flat dark panel; over the brighter
   half of a photo it is the first thing to go soft. */
.division-ticket .division-ticket__blurb {
  color: #d7dee7;
}

/* The phone card's copy, the strip under the grid and the standing version of
   the announcement are all phone-only; the media queries below turn them on. */
.division-ticket__blurb--short,
.sports-dash,
.banner-stack {
  display: none;
}

/* --- STATS DASHBOARD --- */
.stats-dashboard-section {
  padding: 3rem 0;
  background: var(--ink);
  border-top: 1px solid var(--line);
}

.stats-glass-card {
  background: rgba(11, 14, 20, 0.65);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.05);
  border-radius: var(--radius);
  padding: 2.2rem;
  box-shadow: var(--shadow);
}

.stats-card-header {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 2rem;
  border-bottom: 1px solid var(--line);
  padding-bottom: 1.5rem;
}

.status-indicator {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-display);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

#statusDot {
  font-size: 0.6rem;
}

.stats-card-header h2 {
  margin: 0.2rem 0 0.8rem;
  font-size: 1.6rem;
  font-weight: 700;
  color: #fff;
}

.event-details-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  font-size: 0.88rem;
}

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

.meta-detail strong {
  color: #fff;
}

.stats-dashboard-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.25rem;
  margin-bottom: 1.8rem;
}

.stat-box {
  background: rgba(6, 8, 12, 0.5);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.stat-label {
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.stat-number {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 2.1rem;
  color: #fff;
  line-height: 1;
}

.stats-announcement {
  background: var(--brand-soft);
  border-left: 3px solid var(--brand);
  border-radius: 4px;
  padding: 1rem 1.25rem;
}

.announcement-label {
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  color: var(--brand);
  display: block;
  margin-bottom: 0.25rem;
}

.announcement-text {
  margin: 0;
  font-size: 0.9rem;
  color: #d1d9e2;
  font-weight: 500;
}

/* --- BUTTONS --- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border: 1px solid transparent;
  border-radius: 8px;
  padding: 0.8rem 1.5rem;
  font: inherit;
  font-weight: 700;
  font-size: 0.9rem;
  cursor: pointer;
  text-decoration: none;
  transition: all 0.2s ease;
  font-family: var(--font-display);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.btn:active {
  transform: translateY(1px);
}

.btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.btn--primary {
  background: var(--brand);
  color: #fff;
}

.btn--primary:hover:not(:disabled) {
  background: var(--brand-dim);
  box-shadow: 0 4px 15px rgba(31, 174, 94, 0.25);
}

.btn--ghost {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--text);
}

.btn--ghost:hover:not(:disabled) {
  border-color: var(--brand);
  color: var(--brand);
}

.btn--danger {
  background: transparent;
  border-color: rgba(248, 113, 113, 0.4);
  color: var(--bad);
}

.btn--danger:hover:not(:disabled) {
  background: rgba(248, 113, 113, 0.12);
}

/* Same reason as the winner mark: --bad is a pale red for dark panels and sits
   at roughly 2:1 on a light card, which is what makes Clear hard to read in the
   bracket panel. The filled [data-rsvp="no"] state below keeps its own colours —
   white on solid red reads on either theme. */
:root[data-theme="light"] .btn--danger:not([data-rsvp="no"]) {
  border-color: rgba(185, 28, 28, 0.45);
  color: #b91c1c;
}

/* The RSVP pair on the dashboard: dashboard.js hands .btn--danger to "I can't
   join" only while that is the answer on record, so here it is a picked state
   rather than a warning. It fills solid to answer the solid green of "Yes" — a
   faint outline would not read as the chosen one across from a filled button. */
[data-rsvp="no"].btn--danger {
  background: var(--bad);
  border-color: var(--bad);
  color: #fff;
}

[data-rsvp="no"].btn--danger:hover:not(:disabled) {
  background: #ef4444;
  border-color: #ef4444;
  box-shadow: 0 4px 15px rgba(248, 113, 113, 0.25);
}

.btn--sm {
  padding: 0.5rem 1rem;
  font-size: 0.78rem;
  border-radius: 6px;
}

.btn--block {
  width: 100%;
}

.row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

/* --- SECTIONS & CARDS --- */
.section {
  padding: clamp(4rem, 8vw, 6rem) 0;
  border-top: 1px solid var(--line);
  background: var(--ink);
}

/* The active-sports section runs over a photo collage instead of flat ink. A
   dark scrim sits between the picture and the content — the sepia image is
   bright in places and the heading and cards have to stay readable over it.

   Every rule in this block is qualified with .section, the class that parked
   list carries. The id moved to .divisions-section so the footer's "Sports"
   link has somewhere to land, and unqualified these rules followed it there
   and buried the now-light divisions section under this collage and scrim. */
.section#sports {
  position: relative;
  /* No rule across the top — the photo has to emerge out of the dark section
     above it, not start on a visible line. */
  border-top: none;
  background-image: url('../images/sports_bg.png');
  background-size: cover;
  /* Sits low in the frame — the collage's players read better below the heading. */
  background-position: center 30%;
  background-repeat: no-repeat;
}

/* Solid black at the very top, fading in over the first slice of the section so
   there is no hard seam, then opening up to let the collage through. */
.section#sports::before {
  content: "";
  position: absolute;
  inset: -1px;
  background: linear-gradient(180deg,
      rgb(6, 8, 12) 0%,
      rgba(6, 8, 12, 0.92) 6%,
      rgba(6, 8, 12, 0.72) 22%,
      rgba(6, 8, 12, 0.7) 60%,
      rgba(6, 8, 12, 0.78) 100%);
}

/* Lifts the content back above the scrim. */
.section#sports>.wrap {
  position: relative;
  z-index: 1;
}

/* "Active sports this season" steps out to the left of the centred column the
   sport tickets sit in. Only where there is gutter to spare — on a narrow window
   the column already reaches the edge and this would run off-screen. */
@media (min-width: 1100px) {
  .section#sports .section-head {
    margin-inline-start: -7rem;
  }
}

/* Brand green heading on the light theme. The line under it stays white in both
   themes — the section keeps its dark photo scrim either way. */
:root[data-theme="light"] .section#sports .section-head h2 {
  color: var(--brand);
}

.section#sports .section-head p {
  color: #fff;
}

.section-head {
    max-width: 62ch;
    margin-bottom: 3rem;
}

.section-head h2 {
  font-family: var(--font-display);
  font-weight: 900;
  font-style: italic;
  text-transform: uppercase;
  color: #fff;
  font-size: clamp(1.8rem, 3.5vw, 2.3rem);
}

.section-head p {
  color: var(--muted);
  margin: 0;
  font-size: 0.95rem;
}

/* Admin console panels (Users, Registrants, Email Blast, Settings, Outbox)
   read better in the same calm, upright font as the login brand, rather than
   the italic display caps the marketing pages use. */
#console .section-head h2 {
  font-family: var(--font);
  font-style: normal;
  text-transform: none;
}

.card {
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 2rem;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}

/* --- SPORTS GRID --- */
.sports-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
  gap: 1.75rem;
}

.sport-card {
  position: relative;
  background: rgba(11, 14, 20, 0.65);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 2rem 1.75rem;
  overflow: hidden;
  transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
  display: flex;
  flex-direction: column;
  /* The same lift .card and the schedule table have — these used to be flat until
     hovered, which left them sunk next to everything around them. */
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}

.sport-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 4px;
  background: linear-gradient(90deg, var(--sport-color, var(--brand)), transparent);
}

.sport-card-image {
  position: relative;
  width: calc(100% + 3.5rem);
  aspect-ratio: 16 / 10;
  margin: -2rem -1.75rem 1.35rem;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.04);
}

.sport-card-image::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(11, 14, 20, 0.7), transparent 60%);
  pointer-events: none;
}

.sport-card-image img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}

.sport-card:hover {
  transform: translateY(-5px);
  border-color: var(--sport-color, var(--brand));
  box-shadow: 0 15px 35px rgba(0, 0, 0, 0.4), 0 0 20px rgba(31, 174, 94, 0.1);
  background: rgba(11, 14, 20, 0.85);
}

/* The emoji and the box it sits in were sized for the line drawing this used to
   hold, which is mostly empty space inside its own viewBox. An emoji fills its
   square, so both come down a step to land at the same visual weight. */
.sport-card .icon {
  width: 46px;
  height: 46px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--line-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1.5rem;
  transition: all 0.3s ease;
  font-size: 1.35rem;
  line-height: 1;
}

.sport-card:hover .icon {
  background: var(--sport-color-trans, rgba(31, 174, 94, 0.1));
  border-color: var(--sport-color, var(--brand));
  box-shadow: 0 0 15px var(--sport-color);
}

/* A sport drawing with no size of its own would fill whatever box it lands in —
   an <svg> with only a viewBox resolves to 100% of its container. This keeps it
   text-sized by default; the panels that want it bigger set their own. */
.svg-sport {
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.2em;
  stroke: var(--sport-color, var(--brand));
}

/* Sport name cells in the schedule table: drawing and name on one line. */
.cell-with-icon {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.sport-card .emoji-fallback {
  font-size: 1.5rem;
  line-height: 1;
}

.sport-card .svg-sport {
  width: 26px;
  height: 26px;
  stroke: var(--sport-color, var(--brand));
  filter: drop-shadow(0 0 2px var(--sport-color));
}

.sport-card h3 {
  font-family: var(--font-display);
  font-weight: 900;
  font-style: italic;
  text-transform: uppercase;
  font-size: 1.35rem;
  margin: 0 0 0.25rem;
  color: #fff;
  letter-spacing: -0.01em;
}

/* Held at two lines whether the format needs them or not. "18 holes, stableford"
   fits on one while "Doubles — men's, women's, mixed" wraps to two, and without
   a reserved height the wrapped ones started their details panel a line lower
   than every other card in the row. */
.sport-card .fmt {
  font-size: 0.72rem;
  color: var(--sport-color, var(--brand));
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: 1rem;
  line-height: 1.35;
  min-height: calc(1.35em * 2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.sport-card .desc {
  font-size: 0.86rem;
  color: var(--muted);
  margin: 0 0 1.5rem;
  line-height: 1.5;
  min-height: 48px;
}

/* Only the last block in a card absorbs the spare height. On the public cards
   that is the details list; on the admin cards it is the action row below it. */
.sport-card dl {
  margin-top: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  background: rgba(6, 8, 12, 0.4);
  padding: 1.25rem;
  border-radius: 8px;
  border: 1px solid var(--line);
}

.sport-card .dl-row {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  align-items: flex-start;
  font-size: 0.8rem;
}

.sport-card dt {
  color: var(--muted);
  font-weight: 700;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* Two lines reserved per value for the same reason as .fmt, one level down: a
   long venue ("Stelsen Sports Hub Court 1-4") wraps where a short one does not,
   which made that card's whole panel taller than its neighbours' rather than
   just lower. Reserving here fixes the panel's height as well as its top edge,
   so every card in the row closes at the same point. */
.sport-card dd {
  margin: 0;
  font-weight: 600;
  color: #fff;
  font-size: 0.88rem;
  line-height: 1.35;
  min-height: calc(1.35em * 2);
}

.sport-card dl:last-child {
  margin-top: auto;
}

/* Admin cards close with Deactivate / Edit / Delete. The row is pinned to the
   bottom of the card so the buttons line up across the whole grid no matter how
   long each sport's venue or schedule runs, and the even columns keep the
   buttons themselves the same width in every card. */
.sport-card .row-actions {
  margin-top: auto;
  padding-top: 1.5rem;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.6rem;
}

.sport-card .row-actions .btn {
  width: 100%;
}

.slot-bar {
  height: 4px;
  border-radius: 99px;
  background: rgba(255, 255, 255, 0.05);
  margin-top: 1.2rem;
  overflow: hidden;
  position: relative;
}

.slot-bar i {
  display: block;
  height: 100%;
  background: var(--sport-color, var(--brand));
  border-radius: 99px;
  box-shadow: 0 0 8px var(--sport-color);
}

/* --- FORM STYLING --- */
.modal-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.5rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: #fff;
  margin-bottom: 0.4rem;
}

/* The calendar day popup's title -- "TUESDAY, SEPTEMBER 15" -- runs longer
   than anything else .modal-title is used for, and at the same 1.5rem as
   everywhere else it wraps to two lines and runs under the close button,
   which is absolutely positioned and doesn't reflow with the text. Smaller
   here specifically, plus room on the right so even a long day name (e.g.
   "WEDNESDAY, SEPTEMBER 23") clears the button instead of wrapping under it. */
#dayModalTitle,
#playerDayModalTitle {
  font-size: 1.15rem;
  padding-right: 2.25rem;
}

#regForm h3 {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #fff;
  margin-top: 1.7rem;
  margin-bottom: 0.9rem;
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

#regForm h3 .step-num {
  font-size: 0.75rem;
  background: var(--brand);
  color: #fff;
  padding: 0.18rem 0.55rem;
  border-radius: 4px;
  font-weight: 800;
  font-family: var(--font-display);
  letter-spacing: 0;
  box-shadow: 0 0 10px rgba(31, 174, 94, 0.45);
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.25rem;
}

/* Desktop/tablet only, on purpose: below 880px .form-grid already drops to
   one column (further down this file), which already gives Date and Time
   each the full row -- this only needs to fix the width Time's two native
   inputs get squeezed into when they're sharing a row with Date, which is
   only true above that width in the first place. */
@media (min-width: 881px) {
  .form-grid--datetime {
    grid-template-columns: 1fr 1.6fr;
  }
}

.field {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.field--full {
  grid-column: 1 / -1;
}

label,
.label {
  font-family: var(--font-display);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.req {
  color: var(--brand);
}

input[type="text"],
input[type="email"],
input[type="tel"],
input[type="password"],
input[type="number"],
input[type="date"],
input[type="time"],
select,
textarea {
  width: 100%;
  background: rgba(255, 255, 255, 0.015);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  padding: 0.85rem 1rem;
  color: var(--text);
  font: inherit;
  font-size: 0.95rem;
  transition: border-color 0.18s, box-shadow 0.18s;
}

input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(31, 174, 94, 0.35);
}

textarea {
  resize: vertical;
  min-height: 100px;
}

/* The drop arrow, drawn as two half-filled squares butted together into a ▼. It
   is painted in --muted rather than baked into a data: URI so it follows the
   theme. Sized 6px a side: the 5px it was is nearly invisible at a glance, and
   these boxes have to read as dropdowns and not as plain text fields. */
select {
  appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 21px) 50%, calc(100% - 15px) 50%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  padding-right: 2.4rem;
}

/* The list a select pops open is drawn by the browser, outside the page, and on
   Windows it ignores both the select's own background and color-scheme: it comes
   up white while the options inherit the near-white ink of the dark theme, so the
   choices are invisible until the highlight passes over one. Colouring the option
   itself is the one thing that lands. Light grey with dark ink, the same in both
   themes — the popup is the browser's surface, not the page's. */
select option {
  background-color: #e9eaec;
  color: #1a1d21;
}

.hint {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: 0.1rem;
}

/* A row of controls that filters the list under it — the registrants toolbar.
   Everything on the line is pinned to one height and one type size, so the
   search box, the two dropdowns and the export button read as a single strip
   instead of three form fields with a small button tacked on the end. */
.filter-bar {
  align-items: center;
  gap: 0.6rem;
}

.filter-bar input[type="text"],
.filter-bar select,
.filter-bar .btn {
  height: 38px;
  font-size: 0.85rem;
}

.filter-bar input[type="text"],
.filter-bar select {
  width: auto;
  padding-block: 0;
}

/* Sized for what you actually type into it — a name, or the first few letters of
   one — rather than for a whole email address. It is still the control that takes
   up the slack when the toolbar wraps onto its own line. */
.filter-bar input[type="text"] {
  flex: 1 1 170px;
  min-width: 140px;
  max-width: 230px;
}

@media (max-width: 700px) {
  .filter-bar {
    width: 100%;
  }

  .filter-bar input[type="text"] {
    max-width: none;
  }
}

/* --- SPORT PICKER & PILLS --- */
.pick-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 0.8rem;
}

.pick {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  background: rgba(255, 255, 255, 0.01);
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  padding: 1rem 1.15rem;
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
}

.pick:hover {
  border-color: rgba(255, 255, 255, 0.2);
}

/* Tight left/right padding and gap so one-word labels (BASKETBALL, VOLLEYBALL)
   fit on a single line inside the narrow columns. */
.pick--compact {
  padding: 0.38rem 0.4rem;
  gap: 0.3rem;
  border-radius: 5px;
}

.pick--compact .box {
  width: 13px;
  height: 13px;
  border-width: 1.5px;
  font-size: 0.5rem;
}

/* Written as .pick.pick--compact rather than .pick--compact: the plain
   .pick .emo default further down carries the same specificity, so on a tie it
   would win on source order and blow these icons back up past their own box. */
.pick.pick--compact .emo {
  flex: none;
  width: 16px;
  height: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 0.9rem;
  line-height: 1;
}

/* The narrow columns leave little room for long names like SHOOTING RANGE,
   so let the label wrap instead of spilling past the card border. */
.pick--compact .txt {
  line-height: 1.25;
  min-width: 0;
  overflow-wrap: break-word;
}

/* Same type size as the category pills (.radio-pill) in step 02. */
.pick--compact .txt b {
  font-size: 0.82rem;
}

.pick--compact .txt small {
  font-size: 0.7rem;
}

.pick input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.pick .box {
  flex: none;
  width: 22px;
  height: 22px;
  border: 2px solid var(--line-strong);
  border-radius: 4px;
  display: grid;
  place-items: center;
  font-size: 0.75rem;
  color: transparent;
  transition: all 0.15s;
}

.pick input:checked~.box {
  background: var(--sport-color, var(--brand));
  border-color: var(--sport-color, var(--brand));
  color: #fff;
}

.pick:has(input:checked) {
  border-color: var(--sport-color, var(--brand));
  background: rgba(255, 255, 255, 0.02);
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
}

.pick .txt {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}

.pick .txt b {
  font-size: 0.95rem;
  font-weight: 700;
  color: #fff;
}

.pick .txt small {
  color: var(--muted);
  font-size: 0.78rem;
}

.pick .emo {
  font-size: 1.35rem;
}

/* Opt-in: the title is set exactly like the caption under it, so the two lines read
   as one block instead of a heading + subtext. Declared together and last so they
   cannot drift apart and so they win over the .pick / .pick--compact rules above. */
.pick--plain .txt b,
.pick--plain .txt small {
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--muted);
  transition: color 0.2s;
}

/* Same muted-until-pointed-at behaviour as the sport picker and .radio-pill. Both
   lines lift together — they are styled as one block, so lifting only the title
   would split them apart again. */
.pick--plain:hover .txt b,
.pick--plain:hover .txt small,
.pick--plain:has(input:checked) .txt b,
.pick--plain:has(input:checked) .txt small {
  color: #fff;
}

:root[data-theme="light"] .pick--plain:hover .txt b,
:root[data-theme="light"] .pick--plain:hover .txt small,
:root[data-theme="light"] .pick--plain:has(input:checked) .txt b,
:root[data-theme="light"] .pick--plain:has(input:checked) .txt small {
  color: var(--text);
}

/* Sport names are lettered like the category pills (.radio-pill) in step 02 — same
   family, size, weight and tracking — so both sets of choices read as one system.
   The bare `label` rule would otherwise leak its wider 0.08em tracking in here. */
#sportPicker .txt b {
  font-family: var(--font-display);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color 0.2s;
}

/* Muted until you point at it or pick it, exactly like .radio-pill. Hover is on the
   card because the whole .pick is the <label>. Checked is included so a chosen sport
   does not stay dim once the pointer moves away. */
#sportPicker .pick:hover .txt b,
#sportPicker .pick:has(input:checked) .txt b {
  color: #fff;
}

:root[data-theme="light"] #sportPicker .pick:hover .txt b,
:root[data-theme="light"] #sportPicker .pick:has(input:checked) .txt b {
  color: var(--text);
}

/* The waiver consent is lettered like the category pills (.radio-pill) in step 02 —
   same family, size, weight and tracking on both lines — so the last thing you tick
   reads like the rest of the form. Only the title is uppercased: the line under it
   is a full sentence
   and would turn into a wall of capitals. Scoped by the #agree checkbox it wraps,
   which gives it the ID specificity it needs to beat the light-theme .txt b rule. */
.pick:has(#agree) .txt b,
.pick:has(#agree) .txt small {
  font-family: var(--font-display);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--muted);
  transition: color 0.2s;
}

.pick:has(#agree) .txt b {
  text-transform: uppercase;
}

/* Muted until you point at it or tick it, exactly like .radio-pill. Both lines lift
   together so they keep reading as one block. */
.pick:has(#agree):hover .txt b,
.pick:has(#agree):hover .txt small,
.pick:has(#agree:checked) .txt b,
.pick:has(#agree:checked) .txt small {
  color: #fff;
}

:root[data-theme="light"] .pick:has(#agree):hover .txt b,
:root[data-theme="light"] .pick:has(#agree):hover .txt small,
:root[data-theme="light"] .pick:has(#agree:checked) .txt b,
:root[data-theme="light"] .pick:has(#agree:checked) .txt small {
  color: var(--text);
}

/* Step 03's follow-up questions: the groupings a sport needs before the entry
   means anything — the division a basketball team plays in, the distance a
   runner signs up for. One block per ticked sport, carrying that sport's own
   colour down its left edge so it reads as belonging to the card above it
   rather than as a new section of the form. */
.sport-opts {
  border: 1px solid var(--line-strong);
  border-left: 3px solid var(--sport-color, var(--brand));
  border-radius: 6px;
  padding: 1rem 1.15rem;
}

.sport-opts+.sport-opts {
  margin-top: 0.7rem;
}

.sport-opts__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.85rem;
  font-family: var(--font-display);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text);
}

.sport-opts__head .emo {
  font-size: 1.05rem;
  line-height: 1;
}

/* The roster inside a drafted team. Numbered so a side of six can be counted at
   a glance against the size the sport is played at. */
.team-members {
  margin: 0;
  padding-left: 1.2rem;
  font-size: 0.8rem;
  line-height: 1.6;
  color: var(--text);
}

.team-members .mono {
  font-size: 0.68rem;
}

.sport-opts__group+.sport-opts__group {
  margin-top: 0.85rem;
}

.sport-opts__group .label {
  display: block;
  margin-bottom: 0.5rem;
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.radio-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
}

.radio-pill {
  position: relative;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  padding: 0.65rem 1.3rem;
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  transition: all 0.2s;
  background: rgba(255, 255, 255, 0.01);
  color: var(--muted);
  font-family: var(--font-display);
}

.radio-pill input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

/* .is-on is the same state without a radio behind it: the bracket division
   picker is a row of buttons, because choosing one re-renders the panel rather
   than submitting anything, and :has(input:checked) has nothing to match on. */
.radio-pill:has(input:checked),
.radio-pill.is-on {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
  box-shadow: 0 4px 12px rgba(31, 174, 94, 0.15);
}

.radio-pill:hover:not(:has(input:checked)):not(.is-on) {
  border-color: rgba(255, 255, 255, 0.2);
  color: #fff;
}

/* Inherited from the surrounding card on a <label>, but a <button> brings the
   browser's own control font with it and would otherwise sit a size out. */
button.radio-pill {
  font-size: 0.82rem;
  line-height: 1.2;
  cursor: pointer;
}

.radio-pill .muted {
  color: inherit;
  opacity: 0.65;
}

/* --- ALERTS & BADGES --- */
.alert {
  border-radius: 8px;
  padding: 0.95rem 1.25rem;
  font-size: 0.9rem;
  border: 1px solid;
  margin-bottom: 1.25rem;
  font-weight: 500;
}

.alert--bad {
  background: rgba(248, 113, 113, 0.08);
  border-color: rgba(248, 113, 113, 0.3);
  color: #fca5a5;
}

.alert--ok {
  background: rgba(52, 211, 153, 0.08);
  border-color: rgba(52, 211, 153, 0.25);
  color: #6ee7b7;
}

.alert--info {
  background: rgba(31, 174, 94, 0.08);
  border-color: rgba(31, 174, 94, 0.25);
  color: #8ff0b8;
}

/* Not an error — something the organizer has to act on before it becomes one,
   such as a sport with too few players to draw. */
.alert--warn {
  background: rgba(251, 191, 36, 0.08);
  border-color: rgba(251, 191, 36, 0.3);
  color: #fcd34d;
}

:root[data-theme="light"] .alert--warn {
  background: rgba(217, 119, 6, 0.12);
  border-color: rgba(217, 119, 6, 0.4);
  color: #92400e;
}

/* The other three carry pale type — #8ff0b8, #6ee7b7, #fca5a5 — picked to sit on
   a dark panel. On this theme they land on an almost-white card and wash out to
   the point of being unreadable, which is how the bracket panel's "pick a
   division" line came to be invisible. Near-black type instead, with the tint
   moved into the background and border so each kind still reads at a glance. */
:root[data-theme="light"] .alert--info {
  background: rgba(31, 174, 94, 0.1);
  border-color: rgba(31, 174, 94, 0.35);
  color: var(--text);
}

:root[data-theme="light"] .alert--ok {
  background: rgba(5, 150, 105, 0.12);
  border-color: rgba(5, 150, 105, 0.35);
  color: var(--text);
}

:root[data-theme="light"] .alert--bad {
  background: rgba(220, 38, 38, 0.1);
  border-color: rgba(220, 38, 38, 0.35);
  color: var(--text);
}

.badge {
  display: inline-block;
  border-radius: 999px;
  padding: 0.25rem 0.75rem;
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  border: 1px solid var(--line-strong);
  color: var(--muted);
  font-family: var(--font-display);
  text-transform: uppercase;
  white-space: nowrap;
}

/* A badge that leads with an icon and closes with a status dot, sized and
   cornered to match the buttons it sits beside rather than keeping the
   pill shape a badge has when it sits inline in a sentence. */
.badge--lead {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.8rem;
  border-radius: 8px;
}

.badge__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
}

.badge--ok {
  background: rgba(52, 211, 153, 0.12);
  border-color: rgba(52, 211, 153, 0.35);
  color: #6ee7b7;
}

.badge--bad {
  background: rgba(248, 113, 113, 0.12);
  border-color: rgba(248, 113, 113, 0.35);
  color: #fca5a5;
}

.badge--warn {
  background: rgba(251, 191, 36, 0.12);
  border-color: rgba(251, 191, 36, 0.35);
  color: #fcd34d;
}

.badge--brand {
  background: var(--brand-soft);
  border-color: rgba(31, 174, 94, 0.35);
  color: #8ff0b8;
}

/* The badge text above is pale on purpose — it is meant for a near-black panel.
   On the light theme those same pastels wash out, so each state gets a deeper
   ink on a light tint of its own colour. */
:root[data-theme="light"] .badge {
  color: #475569;
  background: rgba(15, 23, 42, 0.04);
}

:root[data-theme="light"] .badge--ok {
  background: rgba(16, 185, 129, 0.14);
  border-color: rgba(16, 185, 129, 0.45);
  color: #047857;
}

:root[data-theme="light"] .badge--bad {
  background: rgba(220, 38, 38, 0.12);
  border-color: rgba(220, 38, 38, 0.42);
  color: #b91c1c;
}

:root[data-theme="light"] .badge--warn {
  background: rgba(217, 119, 6, 0.14);
  border-color: rgba(217, 119, 6, 0.45);
  color: #b45309;
}

:root[data-theme="light"] .badge--brand {
  background: rgba(31, 174, 94, 0.14);
  border-color: rgba(31, 174, 94, 0.45);
  color: #158548;
}

/* A sport on the public list that has not drawn enough players to bracket yet.
   Amber and quiet — it is a nudge to sign up, not a fault with the sport. */
.ticket__thin {
  margin: 0.4rem 0 0;
  font-size: 0.72rem;
  line-height: 1.45;
  color: var(--warn);
}

:root[data-theme="light"] .ticket__thin {
  color: #b45309;
}

/* --- NOTIFICATIONS --- */
/* One card per notice the organizer sent. Same surface as .card, with the
   padding pulled in: the page is a stack of these and full .card padding turns
   a two-line schedule note into a very tall box. */
.notif-card {
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 1.25rem 1.5rem;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}

.notif-card+.notif-card {
  margin-top: 0.9rem;
}

/* Unread ones carry the brand down their left edge, so you can find what is new
   in the stack without reading the dates. */
.notif-card--new {
  border-left-color: var(--brand);
  background: var(--brand-soft);
}

/* Game invitations answered right on the card, not just from the modal the
   card opens into -- the buttons sit under the message, same gap the card
   already uses between its own head and body. */
.notif-card__actions {
  margin-top: 0.85rem;
}

/* A size down from the shared .btn--sm these use everywhere else -- next
   to the compact card above them, the normal small size still read as
   oversized. */
.notif-card__actions .btn {
  padding: 0.4rem 0.75rem;
  font-size: 0.72rem;
}

.notif-card--game .notif-card__actions {
  margin-top: 0.65rem;
}

/* --- A game invitation's compact layout: icon disc, title + status on one
   line, date/time/venue run together on the next. --- */
/* A shorter card than the shared .notif-card base wants -- less padding,
   and the head/facts/actions gaps below all pulled in a step to match. */
.notif-card--game {
  padding: 0.9rem 1.15rem;
}

.notif-card__game-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.notif-card__game-head .badge {
  flex: none;
  padding: 0.22rem 0.5rem;
  font-size: 0.58rem;
  letter-spacing: 0.02em;
}

/* The sport's own emoji, not the line-icon set the rest of the app draws
   with -- SportIcons.get()'s thin outline read as barely-there on the
   small disc here, where the real ball art actually has room. No disc
   behind it -- the emoji's own art carries enough colour on its own --
   and .notif-card__game-head's align-items:center keeps it level with
   the middle of the title, even when that title wraps to two lines. */
.notif-card__game-ico {
  flex: none;
  font-size: 1.3rem;
  line-height: 1;
}

.notif-card__game-title {
  flex: 1;
  min-width: 0;
  margin: 0;
  font-size: 0.84rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.25;
}

.notif-card__game-facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  margin: 0.45rem 0 0;
  padding-left: calc(28.6px + 0.75rem);
  font-size: 0.72rem;
  color: var(--muted);
}

/* Each fact is its own unit (icon + text), so wrapping never splits one
   apart -- the separator is a border on the fact itself rather than a
   standalone "|" character, which could otherwise end up alone at the
   start of a wrapped line. */
.notif-card__game-fact {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding-right: 0.6rem;
  margin-right: 0.6rem;
  border-right: 1px solid var(--line-strong);
  line-height: 1.3;
}

.notif-card__game-fact:last-child {
  padding-right: 0;
  margin-right: 0;
  border-right: none;
}

@media (max-width: 420px) {
  .notif-card__game-facts {
    padding-left: 0;
  }
}

.notice-head {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
}

.notice-ico {
  font-size: 1.25rem;
  line-height: 1.4;
  flex: none;
}

/* Takes the slack so the "New" badge is pushed out to the right edge. */
.notice-meta {
  flex: 1;
  min-width: 0;
}

.notice-meta h3 {
  margin: 0 0 0.15rem;
  font-size: 1rem;
}

/* The organizer types these by hand, so the line breaks they put in are part of
   the message — a schedule is usually one game per line. */
.notice-body {
  margin: 0.75rem 0 0;
  padding-inline-start: 2.1rem;
  white-space: pre-line;
  font-size: 0.92rem;
  line-height: 1.6;
  color: var(--muted);
}

/* --- OTHER PAGES & UTILITIES --- */

/* Full-bleed content column for the logged-in pages. Their headings and cards
   line up near the left edge, under the brand, instead of sitting inside the
   centred 1200px column. */
.wrap--flush {
  max-width: none;
  width: 100%;
  margin-inline: 0;
  padding-inline: 2rem;
}

@media (max-width: 700px) {
  .wrap--flush {
    padding-inline: 1.25rem;
  }
}

/* --- PLAYERS HUB --- */
/* One card per sport the player joined, each holding that sport's roster. The
   sport's own colour runs down the left edge, so the cards are told apart by the
   same colour their tile and their picker entry already use. */
.hub-cards {
  display: grid;
  gap: 1rem;
}

.hub-card {
  padding: 1.15rem 1.35rem;
  border-left: 3px solid var(--sport-color, var(--brand));
}

.hub-ico {
  font-size: 1.1rem;
}

.roster {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Opens showing the first eight; the button under the list drops the class. The
   count in ROSTER_PREVIEW in dashboard.js has to match the 9 here. */
.roster.is-clipped .roster-row:nth-child(n + 9) {
  display: none;
}

.hub-more {
  margin-top: 0.9rem;
}

/* A hairline between people rather than a box each: this is one list, and eight
   bordered cards inside a card reads as a pile of boxes. */
.roster-row {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.55rem 0.6rem;
  border-top: 1px solid var(--line);
}

.roster-row:first-child {
  border-top: none;
}

/* Stands in for a photo the site never asks for. */
.roster-ini {
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: #fff;
  background: var(--sport-color, var(--brand));
}

/* Takes the slack, so the division and the badge are pushed out to the right
   edge however long the name is. */
.roster-who {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
}

.roster-who b {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--text);
}

.roster-who small {
  font-size: 0.75rem;
  color: var(--muted);
}

.roster-div {
  flex: none;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Your own row, marked so you can find yourself in a long roster. */
.roster-row.is-me {
  background: var(--brand-soft);
  border-radius: 8px;
}

.roster-you {
  font-size: 0.6rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brand);
  margin-inline-start: 0.35rem;
}

@media (max-width: 640px) {

  /* The division and the play/watch badge drop under the name rather than
     squeezing it down to two words a line. */
  .roster-row {
    flex-wrap: wrap;
  }

  .roster-who {
    flex-basis: calc(100% - 3.5rem);
  }

  .roster-div,
  .roster-row .badge {
    margin-inline-start: 3.5rem;
  }
}

/* An emoji glyph paints its own colours and ignores `color`, so a filter is the
   only way to tone it down. The pale grey gear washes out on the light theme —
   this darkens it to sit with the heading beside it. */
:root[data-theme="light"] .title-emoji {
  filter: grayscale(0.35) brightness(0.55) contrast(1.15);
}

/* Page titles on those pages: a step down from the display sizes used on the
   public pages, and set in the quieter title face rather than the slanted caps
   the public sections use — the signed-in pages are read, not scanned. */
/* The second selector is only there to outrank `.section-head h2`, which sets a
   much larger display size and the italic caps for the public sections. */
.page-title,
.section-head .page-title {
  font-family: var(--font-title);
  font-size: clamp(1.35rem, 2.6vw, 1.8rem);
  font-weight: 800;
  font-style: normal;
  text-transform: none;
  letter-spacing: -0.015em;
  line-height: 1.25;
  margin-bottom: 1.5rem;
}

.section-title {
  font-size: clamp(1.1rem, 2vw, 1.35rem);
  margin-bottom: 1rem;
}

/* Content pages that run on the hero's golf photo instead of flat ink. The image
   is fixed to the viewport so it holds still while a long document scrolls over
   it — sized to the page it would otherwise stretch the length of the article —
   and a heavy scrim keeps the body text readable. */
.page-photo-bg {
  background:
    linear-gradient(180deg, rgba(6, 8, 12, 0.88) 0%, rgba(6, 8, 12, 0.93) 100%),
    url('../images/hero_golf1.webp') center / cover no-repeat fixed,
    var(--ink);
}

/* Same idea on the sports collage — the picture behind the active-sports section. */
.page-sports-bg {
  background:
    linear-gradient(180deg, rgba(6, 8, 12, 0.86) 0%, rgba(6, 8, 12, 0.92) 100%),
    url('../images/sports_bg.png') center / cover no-repeat fixed,
    var(--ink);
}

/* Login and the admin gate sit on the same photo as the registration modal. The
   scrim is layered over it in the same background shorthand, so the card in the
   middle keeps its contrast. */
.centered-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 3rem 1.25rem;
  background:
    radial-gradient(760px 420px at 50% -12%, rgba(31, 174, 94, 0.15), transparent 60%),
    linear-gradient(180deg, rgba(6, 8, 12, 0.86) 0%, rgba(6, 8, 12, 0.78) 50%, rgba(6, 8, 12, 0.9) 100%),
    url('../images/bg.webp?v=2') center / cover no-repeat,
    var(--ink);
}

/* Desktop only: framed lower so more of the top of the photo shows, rather
   than the dead-center crop `cover` picks by default. Left alone on phone,
   where the login card itself takes up most of the height anyway. */
@media (min-width: 881px) {
  .centered-page {
    background-position: center 20%;
  }
}

.centered-page .card {
  width: min(480px, 100%);
  box-shadow: var(--shadow);
  padding: 2.5rem;
  position: relative;
}

/* Phones narrow enough that a long email address (account-details' own
   card, mainly) would otherwise wrap to a second line inside 2.5rem of
   padding on both sides -- trimmed down here instead of shrinking the
   text further, so the card grows to fit the value rather than the value
   shrinking to fit the card. */
@media (max-width: 420px) {
  .centered-page {
    padding: 2rem 0.6rem;
  }

  .centered-page .card {
    padding: 1.75rem 1.1rem;
  }
}

/* Close button on centered cards (player login, admin gate) — mirrors .modal-close-btn */
.card-close-btn {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line);
  color: var(--muted);
  width: 34px;
  height: 34px;
  border-radius: 50%;
  font-size: 1.35rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  text-decoration: none;
  transition: all 0.2s ease;
  z-index: 10;
}

.card-close-btn:hover {
  background: rgba(255, 255, 255, 0.1);
  color: var(--text);
  border-color: rgba(255, 255, 255, 0.2);
  transform: rotate(90deg);
}

@media (max-width: 520px) {
  .card-close-btn {
    top: 1rem;
    right: 1rem;
    width: 30px;
    height: 30px;
    font-size: 1.15rem;
  }
}

.notice {
  text-align: center;
}

.notice h1 {
  font-size: 1.8rem;
}

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

.notice .row-actions {
  justify-content: center;
  margin-top: 1.5rem;
}

/* A panel like the cards around it, rather than a bare grid sitting on the page
   background. */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--ink-2);
  /* Exactly the lift .card has, in both themes — the softened light-mode variant
     that used to be here read weaker than the cards it sits under. */
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
  min-width: 780px;
}

/* SPORT / FORMAT / SCHEDULE / VENUE ride a brand green band across the top of the
   panel, white on the brand colour in both themes. */
th {
  text-align: left;
  padding: 1rem 1.25rem;
  background: var(--brand);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: #fff;
  font-weight: 800;
  white-space: nowrap;
  font-family: var(--font-display);
}

td {
  padding: 1rem 1.25rem;
  border-top: 1px solid var(--line);
  vertical-align: middle;
  color: #cdd3dc;
}

tbody tr:hover {
  background: rgba(255, 255, 255, 0.015);
}

/* That cell colour is a pale grey for a near-black panel; on the light theme it
   washes out to nearly nothing, so the cells take the theme's own ink. */
:root[data-theme="light"] td {
  color: var(--text);
}

:root[data-theme="light"] tbody tr:hover {
  background: rgba(30, 34, 38, 0.05);
}

/* The secondary columns are muted on purpose, but the dark theme's grey is too
   faint once the panel is light. */
:root[data-theme="light"] td.muted {
  color: #4d535a;
}

.tabs {
  display: flex;
  gap: 0.5rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: 2.2rem;
  overflow-x: auto;
}

.tab {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--muted);
  font: inherit;
  font-weight: 700;
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  padding: 0.95rem 1.3rem;
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.2s;
  font-family: var(--font);
  text-transform: uppercase;
}

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

.tab.is-active {
  color: var(--brand);
  border-bottom-color: var(--brand);
}

/* --- Admin's phone-only hamburger menu, in place of the tab strip --- */
.admin-tabs-bar {
  display: none;
  align-items: center;
  gap: 0.85rem;
}

.admin-tabs-bar__title {
  font-family: var(--font);
  font-weight: 800;
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--player-ink);
}

.admin-tabs-toggle {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--player-line);
  border-radius: 8px;
  background: var(--player-surface-2);
  cursor: pointer;
  flex: none;
}

.admin-tabs-toggle span {
  display: block;
  width: 18px;
  height: 2px;
  margin-inline: auto;
  background: var(--player-ink);
  border-radius: 2px;
}

.admin-tabs-drawer {
  display: none;
}

.admin-tabs-drawer.is-open {
  display: block;
  position: fixed;
  inset: 0;
  z-index: 200;
}

.admin-tabs-drawer__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(6, 8, 12, 0.55);
}

.admin-tabs-drawer__panel {
  position: relative;
  width: min(78vw, 300px);
  height: 100%;
  background: var(--player-surface);
  border-right: 1px solid var(--player-line);
  box-shadow: 12px 0 30px rgba(4, 16, 42, 0.3);
  display: flex;
  flex-direction: column;
  padding: 1.25rem 0.75rem;
  gap: 0.25rem;
  overflow-y: auto;
  animation: admin-drawer-in 0.2s ease;
}

@keyframes admin-drawer-in {
  from { transform: translateX(-12px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}

.admin-tabs-drawer__panel .tab {
  text-align: left;
  border-bottom: none;
  border-radius: 8px;
  padding: 0.85rem 1rem;
  width: 100%;
}

.admin-tabs-drawer__panel .tab.is-active {
  background: var(--player-bar);
  color: var(--brand);
}

.admin-tabs-drawer__divider {
  height: 1px;
  background: var(--player-line);
  margin: 0.5rem 0.25rem;
}

.admin-tabs-drawer__logout {
  color: var(--bad);
}

@media (max-width: 880px) {
  .admin-tabs-bar {
    display: flex;
    margin-top: 0.5rem;
  }

  main > .tabs {
    display: none;
  }

  /* The header (ADMIN badge, demo-mode badge, Log out) is gone on phone —
     the hamburger drawer covers navigation, and Log out moves to its foot. */
  #console > .player-shell > .player-card > .site-header {
    display: none;
  }

  /* Breathing room under the hamburger bar — the panel content sat right
     against it otherwise. .admin-screen-title now names the panel in that
     space and carries its own margin, so this only has to clear that. */
  .panel {
    margin-top: 0.5rem;
  }

  /* The "Create a player account…" line under User management is forced
     onto one line for desktop, which just runs the sentence off the edge of
     a phone screen — let it wrap here instead. */
  [data-panel="users"] .section-head p {
    white-space: normal !important;
  }
}

.split {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: space-between;
  align-items: center;
}

.grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 1.5rem;
}

.grid-4 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1.25rem;
}

/* Small fact tiles: a label and one line of text do not need a 2rem card. */
.grid-4--compact {
  gap: 0.85rem;
}

.grid-4--compact .card {
  padding: 0.85rem 1rem;
}

.grid-4--compact .card strong {
  font-size: 0.95rem;
}

.spinner {
  display: inline-block;
  width: 16px;
  height: 16px;
  border: 2px solid rgba(255, 255, 255, 0.25);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.cred-box {
  background: rgba(31, 174, 94, 0.04);
  border: 1px dashed rgba(31, 174, 94, 0.3);
  border-radius: var(--radius);
  padding: 1.5rem;
}

.cred-box .k {
  font-family: var(--font-display);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
  font-weight: 800;
}

.cred-box .v {
  font-family: var(--mono);
  font-size: 1.4rem;
  font-weight: 700;
  color: var(--brand);
  word-break: break-all;
}

/* --- RESPONSIVE LAYOUT --- */
/* .tickets-grid's own rules (further down) drop 4-across to 2 at 1220px and to
   1 at 590px — the homepage's eight division tickets follow that same
   hand-off as-is now (one straight column of eight on phone), so nothing
   extra is needed here below 1220px. */
@media (max-width: 960px) {
  .stats-dashboard-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  /* .tickets-grid's own width (min(1420px, 100vw - 2.5rem)) leaves a 20px
     gutter each side, sized for the four/two-across grid it usually holds.
     Once the division tickets are down to one full-width column on phone,
     that same gutter reads as a lot of bare space either side of a single
     card — tightened just for this grid, and (see .division-ticket below)
     narrowed further to size the whole column down, so every ticket ends
     up the same width regardless of how long its own title or blurb runs. */
  .divisions-grid.tickets-grid {
    width: min(1420px, 78vw);
    gap: 0.6rem;
  }

  /* Earlier passes kept shrinking padding and font-size on each part
     separately — asked instead to shrink the ticket itself as a whole,
     text included at its original proportions. zoom scales the card's
     content (stub, icon, title, blurb, meta rows) together at the ratio it
     already had. A zoomed grid item still *stretches* to fill its column by
     default, which is what's wanted here -- every ticket ends up exactly as
     wide as the column above, not sized to its own (varying) content the
     way justify-self:center left it, where Basketball's longer blurb made
     it visibly wider than Fun Run's shorter one. */
  .division-ticket {
    zoom: 0.7;
  }
}

@media (max-width: 880px) {
  .hero-text-block--desktop,
  .hero-scroll-indicator {
    display: none;
  }

  .ready-card--mobile {
    display: block;
  }

  /* Wider than it is tall on phone, not the portrait card desktop gets --
     the desktop sizing (and even the first cut at shrinking it) left this
     crowding the rest of the hero on a short screen before anyone had
     scrolled at all. */
  .ready-card {
    max-width: 320px;
    padding: 1.35rem 1.25rem 1.1rem;
    border-radius: 16px;
  }

  .ready-card__badge {
    top: -18px;
    width: 38px;
    height: 38px;
  }

  .ready-card__badge img {
    width: 18px;
    height: 18px;
  }

  .ready-card__title {
    margin: 0.3rem 0 0.65rem;
    font-size: clamp(1.35rem, 6.2vw, 1.85rem);
  }

  .ready-card__divider {
    margin-bottom: 0.7rem;
  }

  .ready-card__subtitle {
    font-size: clamp(0.72rem, 2.1vw, 0.82rem);
    margin-bottom: 0.9rem;
  }

  .ready-card__btn {
    padding: 0.68rem 0.95rem;
    font-size: 0.8rem;
  }

  .ready-card__btn-ico {
    width: 18px;
    height: 18px;
  }

  /* Dropped on phone rather than shrunk further: with the badge, title,
     subtitle and button already trimmed down, three more icons and their
     captions were the one thing keeping this a tall card instead of a
     squat, landscape-leaning one. */
  .ready-card__features {
    display: none;
  }

  .form-grid {
    grid-template-columns: 1fr;
  }

  .nav {
    gap: 1rem;
  }

  .nav a:not(.btn) {
    font-size: 0.88rem;
  }

  .nav a.nav-hide {
    display: none;
  }

  /* The dark bar behind the logo, without the sticky positioning that used to
     come bundled with it — this scrolls away with the page like everything
     else, it just isn't transparent over the photo any more. */
  .site-header {
    background: rgba(6, 8, 12, 0.95);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
  }

  /* Shorter still than the 80px desktop bar wants -- that height was tuned
     for a bigger logo mark sitting over a photo, and on a phone it just left
     the solid bar looking thicker than it needed to be. The logo mark comes
     down with it so it is not left crowding the now-shorter bar. */
  .site-header .wrap {
    height: 48px;
  }

  .site-header .brand .brand__logo-box {
    width: 28px;
    height: 28px;
  }

  .hero {
    height: 80vh;
    min-height: 80vh;
    padding-top: 0;
  }

  .hero-content {
    height: 100%;
    padding: 4rem 0 3rem;
    gap: 3rem;
    justify-content: flex-end;
  }

  /* Belt and suspenders: forces the card down with a direct offset instead of
     leaning on the flex container's height resolving the way it should. */
  .hero-text-block {
    margin-top: 40vh !important;
  }
}

@media (max-width: 520px) {
  .stats-dashboard-grid {
    grid-template-columns: 1fr;
  }

  .event-details-meta {
    flex-direction: column;
    gap: 0.5rem;
  }

  .hero-actions-row {
    flex-direction: column;
    width: 100%;
    gap: 0.75rem;
  }

  .btn-aesthetic {
    width: 100%;
    text-align: center;
    padding: 0.55rem 1rem !important;
    font-size: 0.68rem !important;
    clip-path: none !important;
  }

  .hero-title {
    font-size: clamp(1.6rem, 7vw, 2.1rem) !important;
    line-height: 1.15;
    letter-spacing: -0.01em;
    margin-bottom: 0.6rem;
    white-space: normal;
  }

  .hero-subtitle {
    font-size: 0.72rem !important;
    letter-spacing: 0.08em;
    line-height: 1.5;
    margin-bottom: 1.25rem;
  }

  .hero-scroll-indicator {
    display: none !important;
  }

  .brand {
    font-size: 1.1rem !important;
    gap: 0.35rem !important;
  }

  .brand__logo-box {
    width: 28px !important;
    height: 28px !important;
  }

  .site-header .wrap {
    justify-content: space-between !important;
  }

  .nav {
    gap: 0.5rem !important;
    margin-left: 0 !important;
  }

  .nav a:not(.btn) {
    font-size: 0.72rem !important;
  }

  .site-header .wrap {
    padding: 0 0.5rem;
  }

  .hero-text-block {
    text-align: center;
    padding-left: 0;
  }

  .hero::before {
    background-position: 66% 48%;
  }

  .hero-actions-row {
    justify-content: center;
  }
}

/* --- SPORT TICKETS (home page "active sports") --- */
/* Four across, so the eight sports land in exactly two rows. Four ~340px
   tickets plus their gaps need ~1420px, which is wider than the 1200px .wrap
   this sits in — so the grid breaks out of the wrap and centres on the viewport
   instead. left:50% is measured against the centred wrap, which puts it on the
   viewport's midline; translateX pulls the grid back by half its own width.
   The 100vw clamp keeps it off the scrollbar on narrower screens. */
.tickets-grid {
  display: grid;
  position: relative;
  left: 50%;
  transform: translateX(-50%);
  width: min(1420px, 100vw - 2.5rem);
  grid-template-columns: repeat(4, 1fr);
  gap: 1.25rem;
}

/* The tickets are built for a dark card: white type over a dimmed sport photo,
   a coloured stub, and notches that read as punched out of the dark section
   behind them. They keep that look on the light theme too — rather than
   restyling each part, the ticket re-declares the dark tokens for its subtree,
   and the badges inside it go back to their dark-panel colours. */
:root[data-theme="light"] .ticket {
  --ink: #06080c;
  --ink-2: #0b0e14;
  --ink-3: #121720;
  --line: rgba(255, 255, 255, 0.08);
  --line-strong: rgba(255, 255, 255, 0.15);
  --text: #eef2f7;
  --muted: #8d99a9;
}

:root[data-theme="light"] .ticket .badge {
  background: none;
  color: var(--muted);
}

:root[data-theme="light"] .ticket .badge--ok {
  background: rgba(52, 211, 153, 0.12);
  border-color: rgba(52, 211, 153, 0.35);
  color: #6ee7b7;
}

:root[data-theme="light"] .ticket .badge--bad {
  background: rgba(248, 113, 113, 0.12);
  border-color: rgba(248, 113, 113, 0.35);
  color: #fca5a5;
}

.ticket {
  --stub: 68px;
  position: relative;
  display: grid;
  grid-template-columns: var(--stub) 1fr;
  min-height: 168px;
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.3s, box-shadow 0.3s;
}

.ticket:hover {
  transform: translateY(-5px);
  border-color: var(--sport-color, var(--brand));
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
}

.ticket.is-full {
  opacity: 0.72;
}

/* The two punched notches that make it read as a torn-off ticket. */
.ticket::before,
.ticket::after {
  content: "";
  position: absolute;
  left: var(--stub);
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--ink);
  transform: translateX(-50%);
  z-index: 3;
}

.ticket::before {
  top: -11px;
}

.ticket::after {
  bottom: -11px;
}

.ticket__stub {
  position: relative;
  background: var(--sport-color, var(--brand));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.9rem 0.35rem;
  /* Dashed perforation, drawn on the stub's own edge. */
  border-right: 2px dashed rgba(6, 8, 12, 0.55);
}

.ticket__stub .svg-sport {
  width: 24px;
  height: 24px;
  stroke: #fff;
  filter: none;
}

.ticket__stub .emoji-fallback {
  font-size: 1.35rem;
  line-height: 1;
}

.ticket__serial {
  font-family: var(--mono);
  font-size: 0.54rem;
  letter-spacing: 0.1em;
  color: rgba(255, 255, 255, 0.85);
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  text-transform: uppercase;
}

.ticket__body {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 1.05rem 1.1rem;
  overflow: hidden;
}

/* The sport photo sits behind the body, dimmed so the text stays readable. */
.ticket__photo {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0.55;
  transition: opacity 0.3s ease, transform 0.5s ease;
}

.ticket:hover .ticket__photo {
  opacity: 0.72;
  transform: scale(1.06);
}

.ticket__body::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, var(--ink-2) 4%, rgba(17, 21, 28, 0.82) 40%, rgba(17, 21, 28, 0.35));
}

/* :not() — the photo must keep its own absolute positioning. */
.ticket__body>*:not(.ticket__photo) {
  position: relative;
  z-index: 2;
}

.ticket__head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 0.5rem;
}

.ticket__body h3 {
  font-family: var(--font-display);
  font-weight: 900;
  font-style: italic;
  text-transform: uppercase;
  font-size: 1.02rem;
  color: #fff;
  margin: 0;
  letter-spacing: -0.01em;
}

.ticket__head .badge {
  font-size: 0.58rem;
  padding: 0.25rem 0.5rem;
}

/* Always two lines tall, whether the format needs them or not. Short formats
   ("Singles & doubles") sit on one line while long ones ("Team of 4, 3 games
   total pinfall") wrap to two, and without a reserved height that one wrapped
   line pushed the whole meta block of those cards a line below their
   neighbours'. Clamped at two so an unusually long format can't reintroduce the
   same drift from the other direction. */
.ticket__fmt {
  font-size: 0.6rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sport-color, var(--brand));
  margin-top: 0.15rem;
  line-height: 1.35;
  min-height: calc(1.35em * 2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.ticket__meta {
  margin: 0.5rem 0 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.ticket__meta div {
  display: flex;
  gap: 0.4rem;
  font-size: 0.7rem;
}

.ticket__meta dt {
  color: var(--muted);
  min-width: 52px;
  font-size: 0.55rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 700;
  padding-top: 0.12rem;
}

.ticket__meta dd {
  margin: 0;
  color: #e7ecf2;
  font-weight: 600;
}

/* Sits directly under the meta rows rather than being pushed to the bottom of
   the card. margin-top:auto would eat the leftover height as a gap, which on a
   row of cards sized to the tallest one left the slots line floating far below
   the venue. */
.ticket__foot {
  margin-top: 0.55rem;
  padding-top: 0;
}

.ticket__slots {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.4rem;
  font-size: 0.62rem;
  color: var(--muted);
  margin-bottom: 0.3rem;
}

.ticket__slots strong {
  color: #fff;
  font-family: var(--mono);
  font-size: 0.76rem;
}

/* Overrides the global .slot-bar's 1.2rem top margin — that spacing is tuned
   for the roomier dashboard cards and reopens the gap this block just closed. */
.ticket__foot .slot-bar {
  margin-top: 0.35rem;
}

/* Below ~1220px four columns would squeeze each ticket under 280px and the meta
   rows start wrapping, so drop to two and then one. */
@media (max-width: 1220px) {
  .tickets-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 590px) {
  .tickets-grid {
    grid-template-columns: 1fr;
  }

  .ticket {
    --stub: 68px;
  }
}

/* --- Phone: the sports section's own layout --- */
/* Eight wide cards in one column ran the section several screens long. The
   phone reference answers that with two compact cards per row: the picture
   fills the card, a white wash carries the name and one line of copy at the
   foot of it, and the stub becomes a round badge in the corner. Placed after
   the .tickets-grid breakpoints above -- and after the 960px block that shrinks
   these cards with zoom -- because it overrides both. */
@media (max-width: 700px) {
  .divisions-grid.tickets-grid {
    width: min(1420px, 100vw - 2rem);
    grid-template-columns: repeat(2, 1fr);
    gap: 0.7rem;
  }

  /* The masthead drops its panel and photo here: the copy would be reading
     over the busiest part of the picture at this width, and the panel spent
     most of a screen before the first card. */
  .sports-intro {
    background: none;
    border-radius: 0;
    min-height: 0;
    margin-bottom: 1.1rem;
  }

  .sports-intro__photo,
  .sports-intro::after {
    display: none;
  }

  .sports-intro__text {
    padding: 0.25rem 0 0;
  }

  .sports-intro__title {
    color: #10151d;
    font-size: 1.3rem;
  }

  /* This panel turns light at this width, so the lede needs dark ink. */
  .sports-intro__lede {
    color: #4a5460;
    font-size: 0.84rem;
  }

  .sports-intro__blurb {
    display: none;
  }

  /* The short brand rule the reference closes the heading with. */
  .sports-intro__text::after {
    content: "";
    display: block;
    width: 58px;
    height: 3px;
    margin-top: 0.85rem;
    border-radius: 2px;
    background: var(--brand);
  }

  /* zoom:normal cancels the 0.7 the 960px block applies -- this card is drawn
     for the width it gets rather than scaled down into it. */
  .division-ticket {
    zoom: normal;
    display: block;
    min-height: 0;
    background: #fff;
    border: 1px solid rgba(16, 24, 40, 0.09);
    border-radius: 12px;
    box-shadow: 0 4px 14px rgba(16, 24, 40, 0.08);
  }

  .division-ticket .ticket__stub {
    position: absolute;
    z-index: 3;
    top: 0.55rem;
    left: 0.55rem;
    width: 34px;
    height: 34px;
    padding: 0;
    gap: 0;
    border-radius: 50%;
  }

  .division-ticket .ticket__stub .svg-sport {
    width: 17px;
    height: 17px;
  }

  /* The sport's name is already the card's heading; on the badge it would be
     a second copy of it at 6px. */
  .division-ticket .ticket__serial {
    display: none;
  }

  .division-ticket .ticket__body {
    justify-content: flex-end;
    min-height: 136px;
    padding: 0.7rem;
    border-radius: 12px;
  }

  /* The mirror of the wide card's scrim: white under the copy, clear over the
     picture. Vertical rather than diagonal because the blurb runs to two lines
     on some sports and three on others -- a diagonal that covered the longest
     of them washed out the top of every card that was shorter. */
  .division-ticket .ticket__body::after {
    background: linear-gradient(0deg,
        rgba(255, 255, 255, 0.98) 0%,
        rgba(255, 255, 255, 0.96) 44%,
        rgba(255, 255, 255, 0.5) 64%,
        rgba(255, 255, 255, 0) 86%);
  }

  .division-ticket .ticket__body h3 {
    color: #10151d;
    font-size: 0.84rem;
  }

  .division-ticket .division-ticket__blurb {
    color: #4d5663;
    font-size: 0.71rem;
    line-height: 1.35;
    margin-top: 0.2rem;
  }

  /* Name and one line of copy only, per the reference -- a player reads the
     schedule and the venue on their dashboard, and four meta rows across two
     narrow columns was most of what made the section long. */
  .division-ticket .division-ticket__meta,
  .division-ticket .division-ticket__blurb:not(.division-ticket__blurb--short) {
    display: none;
  }

  .division-ticket .division-ticket__blurb--short {
    display: block;
  }

  /* Where the schedule and venue went. */
  .sports-dash {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    width: min(1420px, 100vw - 2rem);
    margin: 1rem auto 0;
    padding: 0.8rem 0.9rem;
    border-radius: 12px;
    background: #0b1119;
    color: #fff;
    text-decoration: none;
  }

  .sports-dash__ico {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: var(--brand);
    color: #fff;
  }

  .sports-dash__text {
    flex: 1;
    font-size: 0.76rem;
    line-height: 1.3;
    color: rgba(255, 255, 255, 0.86);
  }

  /* The rule the reference puts between the sentence and the call to action. */
  .sports-dash__cta {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    flex: none;
    padding-left: 0.8rem;
    border-left: 1px solid rgba(255, 255, 255, 0.16);
    font-size: 0.7rem;
    font-weight: 800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--brand);
  }

  /* Two rows of two is what fits above the fold; the rest of the sports are a
     tap away on the strip below. */
  .division-ticket--off-phone {
    display: none;
  }
}

/* ==========================================================================
   PHONE: THE HOMEPAGE IN ONE SCREEN
   Asked for by name -- header, hero, announcement, the sports section and the
   footer all visible on a 360x800 phone without scrolling. The hero alone was
   spending that whole screen (min-height:100vh) and the footer another two
   thirds of one, so both are put on a height budget here and the section
   between them is tightened to match. Scoped to .page-home: no other page is
   being asked to fit a screen, and the footer's link columns stay as they are
   everywhere else.
   ========================================================================== */
@media (max-width: 700px) {
  /* Height comes off the content rather than a figure of its own. Both the
     880px block's height:80vh and the base min-height:100vh have to go: .hero
     clips what overflows, so a fixed height is a guess that cuts the ENTER THE
     GAME button off the moment the content wants one pixel more. */
  .page-home .hero {
    height: auto;
    min-height: 0;
    padding: 56px 0 20px;
  }

  /* hero_golf_cartoon.png is portrait (1023x1537) and the phone hero is a wide,
     short window, so background-size:cover scales the picture to the width and
     the vertical position alone decides what is in frame. At the 35% the wide
     layout uses, a window this short opens below Sully's head and cut it off.
     Nearer the top of the picture puts his face back in it. */
  .page-home .hero::before {
    background-position: 66% 12%;
  }

  /* .hero-content is the one that actually held the screen open -- height:
     calc(100vh - 160px) with a 480px floor under it, and at 880px a further
     4rem/3rem of padding and a 3rem gap. None of that is wanted once the card
     is the only thing left in here. */
  /* The side padding is this element's own: it carries .wrap, but .wrap's
     width:min(1200px, 100% - 2.5rem) loses to .hero-content's own width:100%
     further down the file at equal specificity, so the gutter every other
     section gets from .wrap never reached the hero and the headline sat hard
     against the left edge of the screen. */
  .page-home .hero-content {
    height: auto;
    min-height: 0;
    padding: 0 1.15rem;
    gap: 0;
    justify-content: center;
  }

  /* The panel comes off: per the reference the copy sits straight on the
     photograph, ranged left, with the button running the full width of the
     column under it. The dark card, its green border, its drop shadow and the
     logo badge notched into its top edge all go with it. */
  .page-home .ready-card {
    max-width: none;
    margin-inline: 0;
    padding: 0;
    background: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
    text-align: left;
  }

  .page-home .ready-card__badge {
    display: none;
  }

  .page-home .ready-card__title {
    margin: 0 0 0.5rem;
    font-size: clamp(1.4rem, 7.2vw, 1.85rem);
    /* The photo behind the type is bright in places -- grass and sky both. */
    text-shadow: 0 2px 14px rgba(0, 0, 0, 0.55);
  }

  /* Ranged left, the trophy leads and one rule runs off to its right; the rule
     on its left would be pinning it to a centre that is no longer there. */
  .page-home .ready-card__divider {
    margin-bottom: 0.7rem;
  }

  .page-home .ready-card__divider span:first-child {
    display: none;
  }

  /* flex:1 ran the remaining rule the full width of the column, straight across
     Sully's face. A short fixed one sits beside the trophy instead. */
  .page-home .ready-card__divider span {
    flex: none;
    width: 46px;
  }

  .page-home .ready-card__subtitle {
    margin-bottom: 0.85rem;
    font-size: 0.68rem;
    text-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
  }

  .page-home .ready-card__btn {
    padding: 0.72rem 1rem;
    font-size: 0.76rem;
  }

  .page-home .ready-card__btn-ico {
    width: 20px;
    height: 20px;
  }

  /* The strip stops scrolling and stands still, two lines with a megaphone in
     front of it. Left aligned rather than the marquee's centred caps -- it is
     a notice now, not a ticker. */
  .page-home .blue-announcement-banner {
    padding: 0.4rem 0;
    letter-spacing: normal;
    text-transform: none;
    line-height: 1.3;
  }

  .page-home .banner-text {
    display: none;
  }

  .page-home .banner-stack {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: min(1200px, 100% - 1.5rem);
    margin-inline: auto;
  }

  .page-home .banner-stack .icon {
    flex: none;
  }

  .page-home .banner-stack p {
    margin: 0;
    font-size: 0.6rem;
  }

  .page-home .banner-stack strong {
    display: block;
    font-size: 0.66rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }

  .page-home .banner-stack span {
    opacity: 0.92;
  }

  .page-home .divisions-section {
    padding: 1.1rem 0 1.25rem;
  }

  .page-home .sports-intro {
    margin-bottom: 0.8rem;
  }

  .page-home .sports-intro__title {
    font-size: 1.15rem;
  }

  .page-home .sports-intro__text::after {
    margin-top: 0.6rem;
  }

  .page-home .division-ticket .ticket__body {
    min-height: 106px;
    padding: 0.6rem;
  }

  .page-home .sports-dash {
    margin-top: 0.8rem;
    padding: 0.55rem 0.7rem;
    gap: 0.6rem;
  }

  .page-home .sports-dash__ico {
    width: 32px;
    height: 32px;
  }

  /* Two lines at this size; at 0.76rem the sentence ran to four in a 360px
     strip and the bar was taller than the row of cards above it. */
  .page-home .sports-dash__text {
    font-size: 0.68rem;
    line-height: 1.25;
  }

  .page-home .sports-dash__cta {
    padding-left: 0.6rem;
    font-size: 0.62rem;
  }

  /* --- The footer as a single bar --- */
  /* The tagline, the dates, and the three link columns are what made this
     roughly two thirds of a screen. Everything but the brand comes off here;
     .footer-bottom stays as the one line beside it, so Terms, Privacy and the
     rest are still one tap away on every other page's footer. */
  .page-home .site-footer {
    padding: 0.7rem 0;
  }

  .page-home .footer-grid {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0;
  }

  .page-home .footer-grid > div:not(:first-child),
  .page-home .footer-grid > div:first-child p {
    display: none;
  }

  .page-home .footer-bottom {
    border-top: none;
    padding-top: 0;
    margin-left: auto;
    justify-content: flex-end;
    font-size: 0.62rem;
    line-height: 1.3;
  }

  /* The copyright is the longer of the two and the one a phone can most afford
     to lose; the organizer's address is the useful half. */
  .page-home .footer-bottom span:first-child {
    display: none;
  }

  .page-home .site-footer .wrap {
    display: flex;
    align-items: center;
  }
}

/* --- BRACKETS --- */
.bracket-tabs {
  flex-wrap: wrap;
  margin-bottom: 1.5rem;
}

/* A sport with too few players to draw. Dimmed rather than removed: you still
   have to be able to open it and see who is signed up so far. Kept legible when
   it is the tab you are on — the point is to find it, not to lose it. */
.bracket-tab.is-thin {
  opacity: 0.5;
}

.bracket-tab.is-thin:hover,
.bracket-tab.is-thin.is-active {
  opacity: 1;
}

/* One side of a matchup: name, Player ID, and the winner mark under it. The
   table's 1.6 line-height spread those three lines far enough apart that they
   stopped reading as one person — this pulls them back together. */
.bracket-side {
  display: block;
  line-height: 1.3;
}

.bracket-side__id {
  display: block;
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--muted);
}

.bracket-winner {
  position: relative;
}

.bracket-winner strong {
  color: var(--ok);
}

.bracket-winner::after {
  content: "▲ winner";
  display: block;
  font-size: 0.62rem;
  line-height: 1.3;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ok);
  font-weight: 800;
}

/* --ok is a mint green chosen to glow on a dark panel; on this theme it lands on
   an almost-white table row at about 1.7:1 and the winner mark all but vanishes.
   Deepened here rather than on the token itself, which the home page's header
   also reads — that header stays dark on both themes and would lose its own
   contrast the other way. */
:root[data-theme="light"] .bracket-winner strong,
:root[data-theme="light"] .bracket-winner::after {
  color: #047857;
}

.match-card {
  border: 1px solid var(--line);
  border-left: 3px solid var(--sport-color, var(--brand));
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.02);
  padding: 1.1rem 1.25rem;
}

.match-card .vs {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 900;
  color: var(--muted);
  font-size: 0.8rem;
  letter-spacing: 0.1em;
}

.match-score {
  font-family: var(--mono);
  font-size: 1.35rem;
  font-weight: 700;
  color: #fff;
}

/* --- SITE FOOTER --- */
.site-footer {
  border-top: 1px solid var(--line);
  background: var(--ink-2);
  padding: 3.5rem 0 2rem;
  margin-top: 4rem;
}

/* The homepage footer butts straight against the section above it — no gap and
   no dividing rule — and carries no picture: it just fades from a lighter black
   at the top down to the deepest black at the very bottom of the page. */
.page-home .site-footer {
  margin-top: 0;
  border-top: none;
  background: linear-gradient(180deg, #151a23 0%, #0b0e14 55%, #06080c 100%);
}

.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr repeat(3, 1fr);
  gap: 2.5rem;
  margin-bottom: 2.5rem;
}

/* COMPANY / LEGAL / PLAYERS pick up the brand colour on the light theme; white
   would disappear on the light footer. */
:root[data-theme="light"] .site-footer h4 {
  color: var(--brand);
}

.site-footer h4 {
  font-family: var(--font-display);
  font-size: 0.74rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #fff;
  margin: 0 0 1rem;
}

.site-footer ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-footer li+li {
  margin-top: 0.6rem;
}

.site-footer a {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.86rem;
  transition: color 0.15s;
}

.site-footer a:hover {
  color: var(--brand);
}

.site-footer p {
  color: var(--muted);
  font-size: 0.86rem;
  line-height: 1.6;
  margin: 0;
}

.footer-bottom {
  border-top: 1px solid var(--line);
  padding-top: 1.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: space-between;
  align-items: center;
  font-size: 0.8rem;
  color: var(--muted);
}

@media (max-width: 860px) {
  .footer-grid {
    grid-template-columns: 1fr 1fr;
    gap: 2rem;
  }
}

@media (max-width: 520px) {
  .footer-grid {
    grid-template-columns: 1fr;
  }
}

/* --- MODAL SYSTEM --- */
/* The registration form opens over the same photo as the login and admin gates,
   dimmed hard so the white form card stays the focus. The blur only applies to
   what is behind the overlay, so the photo itself stays sharp. */
.modal-overlay {
  position: fixed;
  inset: 0;
  background:
    linear-gradient(180deg, rgba(6, 8, 12, 0.7) 0%, rgba(6, 8, 12, 0.62) 50%, rgba(6, 8, 12, 0.75) 100%),
    url('../images/bg.webp?v=2') center / cover no-repeat,
    var(--ink);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  z-index: 9999;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  overflow-y: auto;
  /* iOS Safari otherwise renders this scrollable in name only — no momentum,
     and a touch-drag over it can just fall through to the page underneath. */
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 2.25rem 1.25rem;
}

/* Same dark panel as the login card, so registering and logging in look like
   the same surface. */
.modal-card {
  position: relative;
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 2.25rem;
  width: 100%;
  max-width: 720px;
  box-shadow: 0 30px 60px rgba(0, 0, 0, 0.35);
  margin-top: auto;
  margin-bottom: auto;
  animation: modalScaleIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}

/* No transform here on purpose -- a will-change/backface-visibility hint on
   the old scale()+translateY() version was not enough to stop it, on an
   actual iPhone, from leaving a ghost of the card's start position behind in
   .modal-overlay's blurred backdrop (a doubled, offset border around the
   settled card). A fade needs no re-composite of the blur underneath it as
   it plays, which is what a moving, resizing element does -- so it is the
   one animation left that cannot leave that trail. */
@keyframes modalScaleIn {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

/* The QR itself: PlayerQr::svg() always draws it at a fixed 320px (see
   app/Support/PlayerQr.php), which is wider than #qrModal's own card on a
   phone -- the actual bug behind what looked like a stray sliver down the
   card's right edge. inline-block sizes #qrHolder to whatever its child
   comes out to, so both it and the svg inside need their own limit for
   either to actually shrink. */
#qrHolder {
  max-width: 100%;
  box-sizing: border-box;
}

#qrHolder svg {
  display: block;
  width: 100%;
  height: auto;
  max-width: 288px;
}

.modal-close-btn {
  position: absolute;
  top: 1.15rem;
  right: 1.15rem;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--line);
  color: var(--muted);
  width: 34px;
  height: 34px;
  border-radius: 50%;
  font-size: 1.35rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  transition: all 0.2s ease;
  z-index: 10;
}

.modal-close-btn:hover {
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  border-color: var(--line-strong);
  transform: rotate(90deg);
}

/* The modal used to be a white panel and carried a full set of light-theme
   overrides for its text, inputs and pills. It is a dark panel now, like the
   login card, so the site's own dark form styling applies unchanged — only the
   step number keeps a rule of its own. */
.modal-card h3 .step-num {
  background: var(--brand);
  color: #fff;
  box-shadow: 0 3px 8px rgba(31, 174, 94, 0.25);
}

@media (max-width: 520px) {
  .modal-overlay {
    padding: 1rem 0.5rem;
  }

  .modal-card {
    padding: 2rem 1.25rem;
  }

  .modal-close-btn {
    top: 1rem;
    right: 1rem;
    width: 32px;
    height: 32px;
    font-size: 1.2rem;
  }
}

.settings-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  margin: 0 10px;
  border-radius: 50%;
  text-decoration: none;
  font-size: 18px;
  transition: background 0.2s ease;
}

.settings-btn:hover {
  background: rgba(255, 255, 255, .15);
}

/* The start → end pair on the Create Game form. A grid rather than a flex row so
   the two inputs stay the same width whatever the arrow between them measures. */
.time-range {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 0.6rem;
}

/* Stand-in for the "mm/dd/yyyy" / clock face Chrome draws inside an empty
   date or time input by itself — iOS Safari draws nothing there, so this
   overlays the same cue and steps aside the moment a value is picked. */
.native-placeholder-wrap {
  position: relative;
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
  /* Once iOS has a value to show ("Aug 12, 2026" instead of "mm/dd/yyyy") it
     renders wider still, no matter what width is set on the input itself —
     this is the container actually enforcing the limit, by cutting off
     whatever spills past its own edge rather than trying to make the input
     agree to a size. */
  overflow: hidden;
}

.native-placeholder-wrap input {
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}

.native-placeholder {
  display: none;
}

/* Chrome and Firefox already draw their own "mm/dd/yyyy" / clock face inside
   an empty date or time input, so the overlay only needs to exist for the one
   engine that draws nothing there. -webkit-touch-callout is a real, if
   unrelated, iOS-only feature — its presence is just this codebase's way of
   asking "is this Safari/iOS" without a user-agent sniff. */
@supports (-webkit-touch-callout: none) {
  .native-placeholder {
    position: absolute;
    left: 1rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--muted);
    font-size: 0.95rem;
    pointer-events: none;
  }

  .native-placeholder-wrap.is-empty .native-placeholder {
    display: block;
  }

  /* Turns out iOS renders a *populated* date/time control both wider and
     taller than the padding/font-size above ask for, ignoring both the same
     way -- not just wider the way an earlier note here assumed. Width is
     left alone here: matchNativeControlWidths() in admin.js already pins it
     to Location's own width via a real layout width, and
     .native-placeholder-wrap's overflow: hidden crops whatever iOS still
     insists on past that. Height has no such layout property to win iOS
     over with, so that same function measures Location's actual rendered
     height and sets the scaleY transform (below) needed to match it exactly
     -- a guessed-at fixed ratio here held for one screenshot and not the
     next. transform-origin: top keeps the box's top edge (and the label
     sitting right above it) lined up with every other field's, letting only
     the extra height at the bottom disappear. */
  input[type="date"],
  input[type="time"] {
    font-size: 0.85rem;
    transform-origin: top center;
  }
}

@media (max-width: 520px) {
  /* The native time picker has its own minimum width, and two of them side by
     side with an arrow between just doesn't fit a phone's card — stack them
     instead, arrow lying on its side to still read as "start to end". */
  .time-range {
    grid-template-columns: 1fr;
    gap: 0.4rem;
  }

  /* > , not a descendant selector — the arrow is the only direct-child span;
     .native-placeholder is a span too, but nested a level deeper, and rotating
     that one sideways was never the intent. */
  .time-range > span {
    transform: rotate(90deg);
    justify-self: center;
  }
}

.time-range span {
  color: var(--muted);
}

/* Location's suggestion menu, standing in for <input list> + <datalist> --
   see the note beside #gameLocationField in admin.blade.php. */
.autocomplete {
  position: relative;
}

/* The same ▼ Game type's native <select> draws (see the comment beside
   `select` above) -- so Location reads as a dropdown too, on an iPhone where
   nothing else marks it as one. .player-card wins the specificity fight
   against the plain input[type="text"] rule there, so the surface colour is
   repeated here rather than only adding the arrow on top of it. */
.player-card .autocomplete input {
  background-color: var(--player-surface);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--player-ink-soft) 50%),
    linear-gradient(135deg, var(--player-ink-soft) 50%, transparent 50%);
  background-position: calc(100% - 21px) 50%, calc(100% - 15px) 50%;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  padding-right: 2.4rem;
}

/* Dark and blurred, not themed off --player-surface: this copies the iOS
   system picker Game type's native <select> pops up (dark card, blur, a
   checkmark on the current value) rather than the light card look the rest
   of this form uses -- the point was to read as that same kind of menu. */
.autocomplete__menu {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(100% + 0.4rem);
  z-index: 20;
  max-height: 14rem;
  overflow-y: auto;
  background: rgba(40, 40, 44, 0.92);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  border-radius: 14px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
  padding: 0;
}

.autocomplete__option {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
  padding: 0.85rem 1.1rem;
  color: #fff;
  font: inherit;
  font-size: 1rem;
  cursor: pointer;
}

.autocomplete__option:last-child {
  border-bottom: none;
}

.autocomplete__option:hover,
.autocomplete__option:focus-visible {
  background: rgba(255, 255, 255, 0.1);
  outline: none;
}

/* Fixed-width whether or not this row is the current value, so the label
   text lines up the same either way -- the same trick the meta-row icons use. */
.autocomplete__check {
  flex: none;
  width: 1.1rem;
  font-weight: 700;
}

/* The native date/time widgets draw their own mm/dd/yyyy and --:-- text inside
   the box, at whatever size the input's font-size sets — the general field
   rule already covers that, but a touch larger here: these two are read at a
   glance while filling in a game, not typed character by character like the
   text fields around them. */
input[type="date"],
input[type="time"] {
  font-size: 1.05rem;
  font-weight: 500;
  padding-block: 0.95rem;
}

/* --- Games calendar ------------------------------------------------------
   A month at a glance in the Games tab. Seven equal columns whatever the month
   does at its edges; the leading blanks are real cells so the first of the month
   lands under the right weekday. */
.calendar {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem;
}

.calendar-head {
  align-items: center;
  margin-bottom: 1rem;
}

.calendar-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}

.calendar-dow {
  font-family: var(--font-display);
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
  padding-bottom: 0.35rem;
}

.calendar-day {
  /* Grid items default to min-width: auto, i.e. never narrower than their
     content, which let a cell push its whole column wider than 1fr and
     every column after it along with it, past the edge of the screen. */
  min-width: 0;
  min-height: 74px;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.4rem;
  font-size: 0.78rem;
  background: var(--ink-2);
  cursor: pointer;
}

.calendar-day.is-blank {
  border-color: transparent;
  background: none;
  cursor: default;
}

.calendar-day.is-today {
  border-color: var(--brand);
}

.calendar-date {
  color: var(--muted);
  font-weight: 700;
}

/* One row per game inside #dayModal -- the popup a tap on a calendar cell
   opens. The cells themselves carry only the date, circled when the day
   has something on it, so this popup is where a day's games are read. */
.day-modal__game {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.2rem;
  width: 100%;
  text-align: left;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.75rem 0.9rem;
  color: var(--text);
}

.day-modal__game-sport {
  font-weight: 700;
}

/* --- Events popup (partials/events-calendar) ---
   The public pages' phone Events tab. The same .calendar parts as above, cut
   down to the dashboard's compact number-only cells so a month fits a phone
   without scrolling; the cells are <button>s here, so their own chrome is
   reset to the div look the rest of the calendar has. */
.events-cal {
  max-width: 420px;
  padding: 1.35rem 1rem 1.1rem;
}

.events-cal__title {
  margin: 0 2.5rem 1rem 0.15rem;
  font-size: 1.1rem;
}

.events-cal .calendar {
  padding: 0.75rem 0.6rem 0.85rem;
}

.events-cal .calendar-day {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 38px;
  padding: 0.2rem;
  font: inherit;
  color: inherit;
}

.events-cal .calendar-day.is-selected {
  border-color: var(--brand);
  box-shadow: inset 0 0 0 1px var(--brand);
}

.events-cal .calendar-date.has-games {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
}

.events-cal__day {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 1rem;
}

.events-cal__day p {
  margin: 0;
}

.events-cal__day-title {
  font-weight: 700;
}

.events-cal__login {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-top: 1.1rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--brand);
  text-decoration: none;
}

/* The "have your QR?" drawer on the login card. Closed by default: most people
   type their ID, and the two fields inside would otherwise crowd the form they
   actually came for. */
.qr-help {
  border-top: 1px solid var(--line);
  padding-top: 1rem;
}

.qr-help summary {
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  list-style: none;
}

.qr-help summary::-webkit-details-marker {
  display: none;
}

.qr-help summary::before {
  content: '＋ ';
}

.qr-help[open] summary::before {
  content: '－ ';
}

.qr-help summary:hover {
  color: var(--text);
}
/* ==========================================================================
   LOGGED-IN (PLAYER) PAGES
   One shell for the dashboard, profile, notifications, settings and admin:
   a full-bleed padded page, a single surface card, and the nav bar sitting in
   its head. Everything sizes off two custom properties -- the shell's inset and
   the card's own padding -- so one clamp() per dial scales the whole set from a
   360px phone to a wide desktop without a second layout.
   ========================================================================== */

/* Surface tokens. Declared on :root rather than on body.player-page because the
   invitation dialog is rendered into <body> on every page, not just these. */
:root {
  --player-surface: #eef1f5;
  --player-surface-2: #ffffff;
  --player-bar: #e3e7ee;
  --player-ink: #1a1f2b;
  --player-ink-soft: #5b6472;
  --player-line: rgba(26, 31, 43, 0.12);
}

:root[data-theme="dark"] {
  --player-surface: #10141c;
  --player-surface-2: #171d27;
  --player-bar: #1c2432;
  --player-ink: #e9eef6;
  --player-ink-soft: #98a3b4;
  --player-line: rgba(255, 255, 255, 0.1);
}

.player-shell {
  width: 100%;
  max-width: 1440px;
  margin-inline: auto;
  padding-block: clamp(1rem, 3.5vw, 3rem);
  padding-inline: clamp(0.75rem, 3.5vw, 3rem);
}

.player-card {
  /* The card's inset. The nav bar in its head and the section rhythm below both
     read this, so the head stays flush with the body at every width. No
     background/border/shadow of its own any more -- the page ground shows
     straight through, and each section (the hero, the tiles, the game
     cards, ...) carries its own surface already.

     The inherited colour is the dark ink the pale ground wants. Anything
     left with no colour of its own (a calendar's day numbers, a section's
     plain text) reads against that ground; the one block that does not is
     the hero, which sets its own white on its own forest green. */
  --pad: clamp(1rem, 3.5vw, 2.25rem);
  --dash-w: 1020px;
  width: 100%;
  padding: var(--pad);
  color: #091810;
  font-family: var(--font);
}

/* The card stays on the light --player-* ladder even when the site theme is
   dark, so its sections keep reading as light shapes on the green page
   ground instead of turning dark and blending into it. */
:root[data-theme="dark"] .player-card {
  --player-surface: #eef1f5;
  --player-surface-2: #ffffff;
  --player-bar: #e3e7ee;
  --player-ink: #1a1f2b;
  --player-ink-soft: #5b6472;
  --player-line: rgba(26, 31, 43, 0.12);
}

.player-card h1,
.player-card h2,
.player-card h3,
.player-card h4 {
  font-family: var(--font-title);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--player-ink);
}

/* Sentence-case, unspaced buttons. The site's display caps suit the marketing
   pages; a form a player fills in should read as a form, not a poster. */
.player-card .btn,
.notice-dialog .btn {
  font-family: var(--font);
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0.005em;
}

/* Panels sitting on the card get the raised surface, so the two are told apart
   by a step in tone rather than by a border alone. */
.player-card .card {
  background: var(--player-surface-2);
  border: 1px solid var(--player-line);
  border-radius: 12px;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
  color: var(--player-ink);
  padding: clamp(1rem, 3vw, 1.75rem);
}

/* --- The nav bar in the card's head ---
   Scoped to .dashboard-desktop-nav (x-player-nav's own wrapper) rather than
   plain body.player-page .site-header: the phone header further up this
   file (.dashboard-phone-header .site-header, the brand-only bar that
   replaced x-player-nav on phone) matched this same selector too, so
   position:relative and the negative margins meant to bleed x-player-nav to
   the card's own edges were pulling the phone header in from the screen's
   real edges instead -- it never reached them, because it was being laid
   out as if it were the in-card bar this rule was written for. */
/* A card of its own now, not a bar bled to the edges of the white panel
   that used to sit behind it -- that panel is gone (see .player-card), so
   the negative margins this carried were pulling it out past the page's
   own gutter and off the right edge of the screen. */
body.player-page .dashboard-desktop-nav .site-header {
  position: relative;
  background: var(--dash-card);
  margin: 0 0 1.5rem;
  padding: 0.7rem 1.1rem;
  border-radius: 16px;
  border: 1px solid var(--dash-line);
  box-shadow: 0 6px 18px rgba(9, 24, 16, 0.07);
}

body.player-page .dashboard-desktop-nav .site-header .wrap {
  height: auto;
  max-width: none;
  width: 100%;
  margin-inline: 0;
  padding-inline: 0;
  padding-block: 0;
  gap: 0.75rem;
}

body.player-page .dashboard-desktop-nav .site-header .page-title {
  font-family: var(--font-title);
  font-size: clamp(0.9rem, 1.6vw, 1.1rem);
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--dash-ink);
  margin: 0 auto 0 0.9rem;
}

/* The nav pill and its current item take the dashboard's own green
   (--dash-accent) rather than the brand green tint (--brand-soft) they
   wear on the other pages. */
body.player-page .nav--player {
  background: #e7f6ec;
}

body.player-page .nav--player .nav-link {
  color: var(--dash-ink-soft);
}

body.player-page .nav--player .nav-link.is-current {
  background: var(--dash-accent);
  color: #fff;
}

body.player-page .nav--player .nav-link:hover:not(.is-current) {
  background: rgba(21, 149, 74, 0.1);
  color: var(--dash-accent);
}

/* The phone header (.dashboard-phone-header .site-header, brand only) also
   matches the generic @media(max-width:880px) .site-header rule further up,
   which paints a near-black bar for headers floating over a public-page
   photo hero. Player pages have no photo there -- it's the pale ground --
   so that bar reads as a stray black strip instead of blending in. The
   brand comes with it: .site-header .brand is white for that same photo
   hero, which is invisible here. */
@media (max-width: 880px) {
  body.player-page .dashboard-phone-header .site-header {
    background: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  body.player-page .dashboard-phone-header .brand {
    color: var(--dash-ink, #091810);
  }

  body.player-page .dashboard-phone-header .brand__tagline {
    color: var(--dash-ink-soft, #56675c);
  }

  /* .site-header is position:absolute, so it takes up no room of its own
     and whatever follows it starts at the top of the page, behind it. The
     dashboard hides this behind its own date block; the other three player
     pages had their first card sliding under the wordmark. */
  body.player-page .dashboard-phone-header {
    height: 58px;
  }
}

body.player-page .brand__logo-box img {
  height: clamp(34px, 7vw, 46px);
}

/* Phone: the brand and the nav pill keep the top row to themselves and the page
   title drops to a line of its own underneath. Squeezing all three onto one row
   is what pushed the nav off the right edge. */
@media (max-width: 760px) {
  body.player-page .site-header .wrap {
    flex-wrap: wrap;
    row-gap: 0.6rem;
  }

  body.player-page .site-header .page-title {
    order: 3;
    flex: 1 0 100%;
    margin: 0;
  }

  .nav--player {
    margin-left: auto;
    padding: 0.3rem;
  }

  .nav--player .nav-link {
    min-width: 38px;
    height: 38px;
  }

  .nav--player .nav-link .nav-ico {
    width: 38px;
  }

  .nav--player .nav-dot {
    left: 17px;
  }
}

/* --- Section rhythm --- */
.player-section {
  margin-block: clamp(1.75rem, 4vw, 2.75rem);
}

.player-section:first-of-type {
  margin-top: 1.5rem;
}

/* #notifSection sits between .quick-filters and Game Invitations in the
   DOM whether or not it has anything in it -- toggled .hidden rather than
   removed -- so :first-of-type (which only ever matches the very first
   .player-section, hidden or not) never reached Game Invitations, the
   section a player actually sees first on most visits. It kept the full
   clamp() gap above it as if nothing had been trimmed. Both get the
   tighter top margin so whichever one is visible first closes up the
   same way. */
.quick-filters + .player-section,
.quick-filters + .player-section + .player-section,
.quick-filters + .player-section + .player-section + .player-section {
  margin-top: 0.6rem;
}

.quick-filters + .player-section + .player-section + .player-section {
  margin-top: 0.2rem;
}

.section-head {
  margin-bottom: 1.15rem;
  max-width: 68ch;
}

.section-head__title {
  font-family: var(--font-title);
  font-size: clamp(1.15rem, 2.4vw, 1.45rem);
  font-weight: 700;
  letter-spacing: -0.012em;
  color: var(--player-ink);
  margin: 0 0 0.35rem;
}

/* The lead used to carry white-space:nowrap inline, which held one long line of
   copy rigid and pushed the whole page sideways on a phone. */
.section-head__lead {
  margin: 0;
  font-size: 0.92rem;
  line-height: 1.65;
  color: var(--player-ink-soft);
  white-space: normal;
}

/* .player-section sits straight on the page ground now that .player-card
   carries no surface of its own, so its headings take the dashboard's own
   ink rather than the --player-* ladder, which is tuned for type on the
   white cards inside it. */
.player-section .section-head__title {
  color: var(--dash-ink, #091810);
}

.player-section .section-head__lead {
  color: var(--dash-ink-soft, #56675c);
}

/* "Upcoming events" (the calendar's own heading) a size down from the
   default -- the same scale the game invitations heading just above it
   just moved to, so the two read as the same weight of heading rather
   than one looking like a bigger deal than the other. */
#upcoming-events .section-head__title {
  font-size: 1.05rem;
}

#upcoming-events .section-head__lead {
  font-size: 0.76rem;
}

/* "Upcoming events" closer to the game card above it on a phone. Its own
   0.2rem top margin (see "Section rhythm") never showed: it collapses into
   the section above's clamp()ed bottom margin, the larger of the two, so
   that bottom margin is the one to take in. */
@media (max-width: 880px) {
  .player-section:has(+ #upcoming-events) {
    margin-bottom: 1rem;
  }
}

/* Settings' own two headings ("Change name", "Change password") and
   their lead lines, a size down the same way -- phone-only.
   .section-head.section-head to outscore .section-head .section-head__title
   further down (var(--player-heading), 20px here), which shares this
   rule's specificity but wins on being later in the file. */
@media (max-width: 880px) {
  .settings-forms .section-head.section-head .section-head__title {
    font-size: 0.92rem;
  }

  .settings-forms .section-head__lead {
    font-size: 0.72rem;
  }
}

/* --- Dashboard greeting --- */
.greeting-block {
  margin-block: 0 1.75rem;
}

.greeting-block__name {
  font-family: var(--font-title);
  font-size: clamp(1.25rem, 3vw, 1.6rem);
  font-weight: 700;
  letter-spacing: -0.015em;
  margin: 0 0 0.2rem;
}

.greeting-block__id {
  margin: 0;
  font-size: 0.88rem;
  color: var(--player-ink-soft);
}

.greeting-block__id strong {
  color: var(--brand);
  letter-spacing: 0.04em;
}

/* --- Profile identity header --- */
.player-identity {
  padding: clamp(1rem, 3vw, 1.5rem) 0 1.25rem;
  border-bottom: 1px solid var(--player-line);
  margin-bottom: 1.75rem;
}

/* The row holding Settings' two forms -- gap moved here from an inline
   style so a phone width can bring it down without !important. */
.settings-forms {
  gap: 1.5rem;
}

/* .player-shell's own padding-block plus .player-card's --pad stack to
   32px above the phone header -- generic layout insets tuned for the
   page as a whole, not for this one header. Pulled back with a negative
   margin here rather than trimmed at the source, so nothing else that
   reads either token moves. */
@media (max-width: 880px) {
  /* Close under the brand, and the line and the card below drawn in too:
     at -1rem and 1.25rem/1.1rem the name sat 34px under the tagline, with
     25px and 20px either side of the line. */
  .player-identity {
    padding-top: 0;
    padding-bottom: 0.45rem;
    margin-top: -2.1rem;
    margin-bottom: 0.5rem;
  }

  /* Settings has no .player-identity of its own -- the same 32px gap sits
     above its first form instead, pulled back the same way. gap comes
     down too: at this width the two forms stack (flex-wrap), so it reads
     as the space between Change name and Change password, not a side
     gutter between two columns. */
  .settings-forms {
    margin-top: -1rem;
    gap: 0.75rem;
  }

  /* Each form is a .player-section, which carries a clamp()ed margin-block
     of its own. Stacked, that margin lands on top of the flex gap above
     Change password, so the two headings sat much further apart than the
     gap alone says. */
  .settings-forms .player-section {
    margin-block: 0;
  }

  /* The name/role pair and the panel title below them are all a size or
     two down from the desktop scale here -- the clamp()s they use
     elsewhere floor out well above what a phone header actually needs. */
  .player-identity__name {
    font-size: 1.15rem;
    margin-bottom: 0.1rem;
  }

  .player-identity__role {
    font-size: 0.7rem;
  }
}

.player-identity__name {
  font-family: var(--font-title);
  font-size: clamp(1.35rem, 3.2vw, 1.85rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  margin: 0 0 0.2rem;
}

.player-identity__role {
  margin: 0;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--player-ink-soft);
}

.player-panel {
  max-width: var(--dash-w);
  margin-bottom: 1.5rem;
}

@media (max-width: 880px) {
  .player-panel {
    margin-bottom: 0.5rem;
  }
}

.player-panel--form {
  max-width: 34rem;
}

.player-panel__title {
  font-size: 1.05rem;
  margin: 0 0 1.35rem;
}

@media (max-width: 880px) {
  .player-panel__title {
    font-size: 0.95rem;
    margin-bottom: 1rem;
  }
}

/* --- Label / value pairs, used by the game cards and the invitation dialog ---
   A grid rather than a run of <br>s: the label column collapses under its value
   on a narrow screen instead of the pair wrapping mid-phrase. */
.meta-list {
  display: grid;
  gap: 0.6rem;
  margin: 0 0 1.15rem;
}

.meta-list--boxed {
  border: 1px solid var(--player-line);
  border-radius: 12px;
  background: var(--player-surface);
  padding: clamp(0.9rem, 3vw, 1.15rem);
}

.meta-row {
  display: grid;
  grid-template-columns: 9rem minmax(0, 1fr);
  gap: 0.85rem;
  align-items: baseline;
}

.meta-row dt {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--player-ink-soft);
}

.meta-row dd {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--player-ink);
  overflow-wrap: anywhere;
}

@media (max-width: 560px) {
  .meta-row {
    grid-template-columns: 1fr;
    gap: 0.1rem;
  }
}

/* --- My games --- */
.game-card {
  display: grid;
  grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
  gap: 1rem;
  margin-bottom: 1.25rem;
  border-radius: 12px;
  outline: 3px solid transparent;
  outline-offset: 4px;
  transition: outline-color 0.4s ease;
}

/* Landed on this exact game from the account-credentials email's calendar
   (dashboard.js's openLinkedGame()) -- an outline rather than a border so
   the card's own grid/padding never shifts when it fades in and back out. */
.game-card--linked {
  outline-color: var(--brand);
}

@media (max-width: 880px) {
  .game-card {
    grid-template-columns: 1fr;
  }
}

.game-panel {
  min-width: 0;
  border: 1px solid var(--player-line);
  border-radius: 12px;
  background: var(--player-surface-2);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
  overflow: hidden;
}

.game-panel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.8rem clamp(0.9rem, 3vw, 1.15rem);
  background: var(--player-bar);
  border-bottom: 1px solid var(--player-line);
  /* Keeps both panels' bodies starting at the same line even when one head
     wraps to two lines (a long sport name plus its badge) and the other
     doesn't. */
  min-height: 3.4rem;
}

.game-panel__title {
  font-size: 0.98rem;
  margin: 0;
}

.game-panel__body {
  padding: clamp(0.9rem, 3vw, 1.15rem);
}

/* The detail popup's date/time line -- calendar-icon-date, then
   clock-icon-time trailing on the same line, both the same weight and
   full ink colour so neither reads as the lesser fact. nowrap keeps the
   pair on one line: left to wrap, the time dropped to a second line on
   the narrower phone popup. Replaces the old DATE-labelled meta-row:
   the calendar icon already says what this line is, so the label was
   just repeating it. */
.game-panel__datetime {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  margin: 0 0 1rem;
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--player-ink);
}

.game-panel__datetime .icon {
  flex: none;
  color: var(--player-ink-soft);
}

.game-panel__datetime > span {
  white-space: nowrap;
}

.game-panel__datetime small {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  margin-left: 0.35rem;
  font: inherit;
  color: inherit;
  white-space: nowrap;
}

.game-panel__note {
  margin: 0 0 1rem;
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--player-ink-soft);
}

.game-panel__count {
  margin: 0 0 1rem;
  font-size: 0.9rem;
  color: var(--player-ink-soft);
}

.game-panel__count strong {
  font-size: 1.15rem;
  color: var(--player-ink);
}

@media (max-width: 520px) {
  .game-panel__actions .btn,
  .notice-dialog__actions .btn {
    flex: 1 1 100%;
  }
}

/* --- Invitation / announcement dialog --- */
.modal-card--notice {
  width: 100%;
  max-width: 560px;
  padding: clamp(1.15rem, 4vw, 2rem);
  max-height: min(88vh, 900px);
  overflow-y: auto;
  background: var(--player-surface-2);
  border: 1px solid var(--player-line);
  color: var(--player-ink);
}

.modal-card--notice .modal-close-btn {
  top: clamp(0.65rem, 2.5vw, 1rem);
  right: clamp(0.65rem, 2.5vw, 1rem);
  background: var(--player-surface);
  border-color: var(--player-line);
  color: var(--player-ink-soft);
}

.modal-card--notice .modal-close-btn:hover {
  background: var(--player-bar);
  color: var(--player-ink);
}

.notice-dialog {
  font-family: var(--font);
  color: var(--player-ink);
}

.notice-dialog__head {
  padding-right: 2.5rem;
  margin-bottom: 1.25rem;
}

.notice-dialog__eyebrow {
  margin: 0 0 0.35rem;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brand);
}

.notice-dialog__title {
  font-family: var(--font-title);
  font-size: clamp(1.2rem, 3.5vw, 1.5rem);
  font-weight: 700;
  letter-spacing: -0.015em;
  margin: 0 0 0.3rem;
  color: var(--player-ink);
}

.notice-dialog__sub {
  margin: 0;
  font-size: 0.9rem;
  color: var(--player-ink-soft);
}

.notice-dialog__note {
  margin-bottom: 1.25rem;
  font-size: 0.9rem;
  line-height: 1.65;
  color: var(--player-ink-soft);
}

.notice-dialog__note p {
  margin: 0 0 0.5rem;
}

.notice-dialog__ask {
  margin: 0 0 1rem;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--player-ink);
}

.notice-dialog__status {
  margin: 0.9rem 0 0;
  font-size: 0.85rem;
  color: var(--player-ink-soft);
}

.notice-dialog__status strong {
  color: var(--player-ink);
}

/* The dialog can outgrow a short phone viewport, so the backdrop scrolls and the
   panel starts at the top instead of being centred and clipped at both ends. */
.modal-backdrop {
  padding: clamp(0.75rem, 3vw, 1.5rem);
  overflow-y: auto;
}

@media (max-height: 720px) {
  .modal-backdrop {
    align-items: flex-start;
  }
}

/* --- Forms on the player pages --- */
.player-card .field label {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--player-ink-soft);
}

.player-card input[type="text"],
.player-card input[type="email"],
.player-card input[type="tel"],
.player-card input[type="password"] {
  width: 100%;
  max-width: 100%;
  background: var(--player-surface);
  border: 1px solid var(--player-line);
  color: var(--player-ink);
  border-radius: 8px;
}

@media (max-width: 880px) {
  .player-card input[type="text"],
  .player-card input[type="email"],
  .player-card input[type="tel"],
  .player-card input[type="password"] {
    padding: 0.7rem 0.85rem;
    font-size: 0.86rem;
  }
}

.player-card input[readonly] {
  color: var(--player-ink-soft);
  cursor: default;
}

.player-card .grid-2 {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: 1.15rem;
}

@media (max-width: 880px) {
  .player-card .grid-2 {
    gap: 0.85rem;
  }
}

/* --- Overflow guards. Long venue names, wide tables and pasted email addresses
   are what actually make a page scroll sideways on a phone. --- */
.player-card .table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.player-card img {
  max-width: 100%;
  height: auto;
}

.player-shell,
.player-card,
.game-panel__body,
.notice-dialog {
  overflow-wrap: break-word;
}

/* Notification cards sit on the player card's own surface, so they take the
   raised tone the panels beside them use rather than the near-identical grey
   the site-wide .notif-card carries. */
.player-card .notif-card {
  background: var(--player-surface-2);
  border-color: var(--player-line);
  border-left-color: var(--player-line);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
  color: var(--player-ink);
  padding: clamp(0.9rem, 3vw, 1.25rem) clamp(1rem, 3vw, 1.5rem);
}

/* The accent on an unanswered notification, and the button that answers
   it, take the dashboard's accent (--dash-accent) rather than the brand
   green the public pages use, and these sit on the player pages' own
   palette.
   body.player-page, not .player-card alone: the admin console's own main
   column carries .player-card too (see admin.blade.php), and the console
   keeps the brand green. */
body.player-page .notif-card--new {
  border-left-color: var(--dash-accent);
}

body.player-page .btn--primary {
  background: var(--dash-accent);
  border-color: var(--dash-accent);
  color: #fff;
}

body.player-page .btn--primary:hover:not(:disabled) {
  background: var(--dash-accent-deep);
  border-color: var(--dash-accent-deep);
}

/* Text links (organizer/dashboard/settings on Profile, and every other
   plain link on these pages) and a focused input's border/ring both
   carry the brand green -- this page's own accent is --dash-accent, so
   both take it here rather than falling through to the site-wide
   green default. Not .phone-tabs__item -- those are <a> tags too, but
   already have their own deliberate white/accent colouring for the
   dark floating bar, not the plain-link treatment this is for. */
body.player-page a:not(.phone-tabs__item) {
  color: var(--dash-accent);
}

body.player-page a:not(.phone-tabs__item):hover {
  color: var(--dash-accent-deep);
}

body.player-page input:focus,
body.player-page select:focus,
body.player-page textarea:focus {
  border-color: var(--dash-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--dash-accent) 35%, transparent);
}

body.player-page .btn--ghost:hover:not(:disabled) {
  border-color: var(--dash-accent);
  color: var(--dash-accent);
}

.player-card .notice-head h3 {
  font-size: 1rem;
  margin: 0 0 0.25rem;
}

/* The status pill under a notification's heading used to be centred by an inline
   style; left-aligned it lines up with the heading and the date above it. */
.player-card .notice-meta .badge {
  margin-top: 0.4rem;
}

.player-card .roster-row {
  border-top-color: var(--player-line);
}

/* The design runs the logged-in pages on a pale green ground with the hero,
   the tiles and the game cards floating on it. Fixed rather than scrolling
   and set in both themes on purpose: the dashboard's own palette is a
   fixed one (see the .player-page token block further down), the same way
   the admin console's is, so a player toggling the site theme does not end
   up with white type on this ground. */
body.player-page {
  background:
    radial-gradient(90% 60% at 82% 4%, rgba(47, 215, 95, 0.2) 0%, rgba(47, 215, 95, 0) 62%),
    radial-gradient(80% 55% at 6% 96%, rgba(34, 197, 94, 0.18) 0%, rgba(34, 197, 94, 0) 60%),
    #eef6f1;
  background-attachment: fixed;
}

/* ==========================================================================
   TYPOGRAPHY POLISH + LINE ICONS IN THE PLAYER UI
   Inter carries a set of alternates that make it read as an interface face
   rather than a web default: a straight-tailed l, a disambiguated 1/I, and
   real tabular figures. They are off unless asked for, so they are asked for
   here once, at the root.
   ========================================================================== */

body {
  font-feature-settings: "cv05" 1, "cv11" 1, "ss03" 1;
  text-rendering: optimizeLegibility;
}

/* Anything a reader compares down a column -- IDs, counts, scores, times, table
   cells -- gets figures of equal width so the digits line up. */
.mono,
.meta-row dd,
.game-panel__count,
.match-score,
.greeting-block__id,
table td,
table th {
  font-variant-numeric: tabular-nums;
}

/* Most .mono text (a Player ID, "1234") is short enough that this never
   engages -- but a long value dropped in here (an email address on the View
   Account card, say) has no spaces for the browser's default wrapping to
   break on, so it ran past the edge of its card instead of onto a second
   line, dragging the whole page into a horizontal scroll on phone width. */
.mono {
  font-size: 0.94em;
  letter-spacing: 0.01em;
  overflow-wrap: break-word;
  word-break: break-word;
}

/* Headings set at display sizes still read better with their line breaks
   balanced rather than left to wrap wherever they run out of room. */
.section-head__title,
.notice-dialog__title,
.player-identity__name,
.greeting-block__name {
  text-wrap: balance;
}

.section-head__lead,
.game-panel__note {
  text-wrap: pretty;
}

/* --- Icon primitives --- */
.icon {
  flex: none;
  display: inline-block;
  vertical-align: -0.15em;
}

.btn .icon {
  flex: none;
  margin-inline-end: -0.1em;
}

/* An icon and its text kept on one line, used for the short meta strips under a
   match card where a full label column would be too much furniture. */
.inline-meta {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.inline-meta + .inline-meta {
  margin-inline-start: 0.9rem;
}

/* --- Icons in the label column of a details list --- */
.meta-row {
  grid-template-columns: 10.5rem minmax(0, 1fr);
}

.meta-row dt {
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

.meta-row__ico {
  color: var(--player-ink-soft);
  opacity: 0.85;
}

@media (max-width: 560px) {
  .meta-row {
    grid-template-columns: 1fr;
  }
}

/* --- The sport's mark, on a game panel's heading --- */
.sport-mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 9px;
  background: var(--brand-soft);
  color: var(--brand);
}

.sport-mark .svg-sport {
  width: 18px;
  height: 18px;
  vertical-align: 0;
  stroke: var(--brand);
}

.sport-mark .emoji-fallback {
  font-size: 1rem;
  line-height: 1;
}

.game-panel__title {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-width: 0;
}

.game-panel__title > span {
  min-width: 0;
  overflow-wrap: anywhere;
}

.game-panel__ico {
  color: var(--player-ink-soft);
}

/* --- The dialog's heading block --- */
.notice-dialog__head {
  display: flex;
  align-items: flex-start;
  gap: 0.9rem;
}

.notice-dialog__mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--brand-soft);
  color: var(--brand);
}

.notice-dialog__mark .svg-sport {
  width: 24px;
  height: 24px;
  vertical-align: 0;
  stroke: var(--brand);
}

.notice-dialog__mark .emoji-fallback {
  font-size: 1.35rem;
  line-height: 1;
}

.notice-dialog__headings {
  min-width: 0;
}

/* --- The dialog's note block: icon in a gutter beside the prose --- */
.notice-dialog__note {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  padding: 0.85rem 1rem;
  border-radius: 10px;
  background: var(--player-surface);
  border: 1px solid var(--player-line);
}

.notice-dialog__note p:last-child {
  margin-bottom: 0;
}

.notice-dialog__note-ico {
  margin-top: 0.15rem;
  color: var(--brand);
}

@media (max-width: 420px) {
  .notice-dialog__head {
    gap: 0.7rem;
  }

  .notice-dialog__mark {
    width: 38px;
    height: 38px;
    border-radius: 10px;
  }
}

/* Badges and micro-labels on the player pages sit inside running interface
   text, so their weight and tracking are set explicitly here rather than
   left to whatever a badge elsewhere on the site happens to use. */
.player-card .badge,
.player-card .label,
.notice-dialog .badge {
  font-family: var(--font);
  font-weight: 700;
  letter-spacing: 0.07em;
}

/* .section-head h2 -- the marketing sections' large italic-caps heading. It is
   a bare element selector inside .section-head, so it outranked
   .section-head__title on the player pages and dressed "My games" as a hero
   banner. Matched here on specificity and reset outright, rather than by
   piling !important on each property. Size comes from the shared dial below
   so a section title and the greeting above it stay the same size for good. */
:root {
  --player-heading: clamp(1.25rem, 3vw, 1.6rem);
}

.section-head .section-head__title {
  font-family: var(--font-title);
  font-size: var(--player-heading);
  font-weight: 700;
  font-style: normal;
  text-transform: none;
  letter-spacing: -0.012em;
  color: var(--player-ink);
}

/* Same story one line down: .section-head p forces the site's muted grey, which
   is tuned for the dark marketing surface, not for a light card. */
.section-head .section-head__lead {
  color: var(--player-ink-soft);
  font-size: 0.92rem;
}

.greeting-block__name,
.player-identity__name {
  font-size: var(--player-heading);
}

/* ==========================================================================
   ADMIN CONSOLE â€” RAIL, GREETING, STAT CARDS
   The console used to be one light card on the page with a tab strip in its
   head. This is the shape asked for instead: a dark green rail down the left,
   a greeting across the top, and the four counts as tinted cards with a donut
   of the same numbers beside them. Phone swaps the rail for a bottom bar.
   ========================================================================== */
.admin-layout {
  display: flex;
  min-height: 100vh;
  background: #f6f4ee;
}

/* --- The rail --- */
.admin-side {
  flex: none;
  width: 240px;
  padding: 1.1rem 0.85rem 1.5rem;
  background: #0f3a2a;
  color: #fff;
}

.admin-side__brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.35rem 0.5rem 1.4rem;
  text-decoration: none;
  color: #fff;
}

.admin-side__logo {
  display: grid;
  place-items: center;
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.1);
}

.admin-side__logo img {
  width: 22px;
  height: 22px;
  object-fit: contain;
}

.admin-side__name {
  font-weight: 800;
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  line-height: 1.05;
}

/* White on the rail with SPORTS in the active item's lime, as the
   reference has it -- navy, the logo's own colour, all but vanished into
   the dark green. Set on the name rather than on .admin-side__brand, where
   body.player-page's link blue would win; the phone bar's copy of the
   name keeps its own white. */
.admin-side .admin-side__name {
  color: #fff;
}

.admin-side .admin-side__name small {
  color: #c9ef5d;
  opacity: 1;
}

/* The second word sits under the first, smaller and letterspaced, the way the
   public header's lockup reads. */
.admin-side__name small {
  display: block;
  font-size: 0.56rem;
  font-weight: 700;
  letter-spacing: 0.32em;
  opacity: 0.72;
}

.admin-side__nav {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

/* .tab carries the console's own strip styling -- uppercase, letterspaced, a
   bottom border for the active one. None of that belongs on a rail, so the
   parts that would show through are reset here rather than the rail's items
   being given a class outside .tab, which is what admin.js listens for. */
.admin-side__link {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  width: 100%;
  padding: 0.7rem 0.85rem;
  border: none;
  border-radius: 10px;
  background: none;
  color: rgba(255, 255, 255, 0.88);
  font-family: var(--font);
  font-size: 0.86rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.admin-side__link .icon {
  flex: none;
  opacity: 0.9;
}

.admin-side__link:hover {
  background: rgba(255, 255, 255, 0.07);
  color: #fff;
}

/* Lime, with the rail's own green for its words. */
.admin-side__link.is-active {
  background: #c9ef5d;
  color: #0f3a2a;
  border: none;
  box-shadow: none;
  font-weight: 700;
}

/* --- The page beside it --- */
/* .admin-main also carries .player-card; this takes that card's chrome off and
   leaves only the typography and component rules scoped to it. */
.admin-main.player-card {
  flex: 1;
  min-width: 0;
  padding: 1.5rem clamp(1rem, 2.4vw, 2rem) 2.5rem;
  border: none;
  border-radius: 0;
  box-shadow: none;
  background: transparent;
}

.admin-top {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 1.35rem;
}

.admin-top__greet {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  flex: 1 1 260px;
}

.admin-top__wave {
  font-size: 1.5rem;
  line-height: 1.1;
}

.admin-top__greet h1 {
  margin: 0;
  font-size: clamp(1.1rem, 2vw, 1.4rem);
  font-weight: 800;
  color: #0f172a;
}

.admin-top__greet p {
  margin: 0.15rem 0 0;
  font-size: 0.82rem;
  color: #64748b;
}

.admin-top__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.admin-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.7rem 0.3rem 0.3rem;
  border-radius: 999px;
  background: #e7f3ec;
  color: #0f3d2e;
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.08em;
}

.admin-chip__av {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--brand);
  color: #fff;
  font-size: 0.66rem;
}

/* --- The pill strip --- */
.tabs.admin-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin: 0 0 1.35rem;
  padding: 0.35rem;
  border: none;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
}

.admin-tabs .tab {
  display: flex;
  align-items: center;
  gap: 0.42rem;
  padding: 0.5rem 0.85rem;
  border: none;
  border-radius: 9px;
  background: none;
  color: #64748b;
  font-family: var(--font);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
}

.admin-tabs .tab .icon {
  flex: none;
}

.admin-tabs .tab:hover {
  background: #f1f5f9;
  color: #0f172a;
}

.admin-tabs .tab.is-active {
  background: var(--brand);
  color: #fff;
  border: none;
}

/* --- A titled card inside a console panel --- */
/* One card per thing the panel does, each led by its own tinted icon,
   title and a line saying what it is for -- rather than a bare form with
   the explanation floating above it outside any card. */
.panel-card {
  max-width: 680px;
  margin-bottom: 1.25rem;
  padding: 1.15rem;
}

.panel-card__head {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  margin-bottom: 1.25rem;
}

.panel-card__ico {
  display: grid;
  place-items: center;
  flex: none;
  width: 38px;
  height: 38px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--brand) 14%, #fff);
  color: var(--brand);
}

.panel-card__head h3 {
  margin: 0 0 0.2rem;
  font-size: 1rem;
  font-weight: 800;
  color: #0f172a;
}

.panel-card__head p {
  margin: 0;
  font-size: 0.76rem;
  line-height: 1.4;
  color: var(--muted);
}

/* The search/filter pair on the Accounts card, pushed to the end of the
   head row rather than sitting under the title. */
.panel-card__head .filter-bar {
  margin-left: auto;
}

/* A step down from the site-wide label size: these sit close above their
   own input inside a card, so they read as its caption rather than as a
   heading of their own. */
.panel-card label {
  font-size: 0.62rem;
}

.panel-card .form-grid {
  gap: 0.65rem;
}

.panel-card .field {
  gap: 0.25rem;
}

/* An input with its own icon inside the box, and optionally a button at
   the other end. Padding on the input itself clears them, so the icons
   sit over the field rather than shrinking the space text can use. */
.input-wrap {
  position: relative;
  display: block;
}

/* The icon sits in a box of its own against the field's left edge, with
   a hairline between it and the text -- a compartment rather than a mark
   floating over the input. */
.input-wrap__ico {
  position: absolute;
  top: 50%;
  left: 0;
  translate: 0 -50%;
  display: grid;
  place-items: center;
  box-sizing: content-box;
  width: 20px;
  height: 20px;
  padding: 0.4rem 0.7rem;
  border-right: 1px solid var(--line-strong);
  color: var(--muted);
  pointer-events: none;
}

.input-wrap input {
  width: 100%;
  padding-left: 2.4rem;
}

.input-wrap:has(.input-wrap__peek) input {
  padding-right: 2.6rem;
}

.input-wrap__peek {
  position: absolute;
  top: 50%;
  right: 0.5rem;
  translate: 0 -50%;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.input-wrap__peek:hover {
  color: var(--text);
}

/* What a list shows when it has nothing in it -- a circled icon, a line
   saying so, and a quieter one saying what to do about it, rather than a
   bare sentence sitting where the rows would be. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 2rem 1rem;
  text-align: center;
}

.empty-state__ico {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--muted) 14%, transparent);
  color: var(--muted);
}

.empty-state strong {
  font-size: 0.9rem;
  color: var(--text);
}

.empty-state p {
  margin: 0;
  font-size: 0.78rem;
  color: var(--muted);
}

/* padding-block, not the padding shorthand: an .input-wrap input sets its
   own left and right padding to clear the icons sitting over it, and the
   shorthand here -- being later in the file at the same specificity --
   was resetting both, so the icon landed on top of the text. */
.panel-card input,
.panel-card select,
.panel-card textarea {
  font-size: 0.9rem;
  padding-block: 0.7rem;
  padding-inline: 0.9rem;
  border-radius: 10px;
}

.panel-card > p {
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--muted);
}

/* Clears the icon compartment on the left, and the eye on the right. */
.panel-card .input-wrap input {
  padding-left: 3.1rem;
}

.panel-card .input-wrap:has(.input-wrap__peek) input {
  padding-right: 3rem;
}

.panel-card__submit {
  width: 100%;
  margin-top: 1.5rem;
  padding-block: 0.95rem;
  border-radius: 10px;
  justify-content: center;
}

@media (max-width: 700px) {
  .panel-card__head {
    flex-wrap: wrap;
  }

  /* The icon and the title block stay side by side; only the filter pair
     drops to a row of its own. Without this the text block wrapped under
     the icon too, leaving the icon alone on the first line. */
  .panel-card__head > div:not(.filter-bar) {
    flex: 1 1 0;
    min-width: 0;
  }

  .panel-card__head .filter-bar {
    flex-basis: 100%;
    margin-left: 0;
    margin-top: -0.35rem;
  }
}

/* .filter-bar gives its controls a fixed 38px height and zero vertical
   padding; .panel-card's own input rule sets padding-block instead, and
   the two together pushed the select's text out of its box. Inside a
   card the filter controls follow the card's sizing. */
.panel-card .filter-bar input[type="text"],
.panel-card .filter-bar select {
  height: auto;
  padding-block: 0.7rem;
  font-size: 0.85rem;
}

/* --- The four counts --- */
/* Four across on a wide screen, with the ring and the month in a row of
   their own under them (.ov-pair). White cards on the cream page: each
   count tells itself apart by the tinted square its icon sits in and the
   colour of its percentage, not by a tint across the whole card. */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.9rem;
  margin-bottom: 0.9rem;
}

.stat {
  position: relative;
  overflow: hidden;
  padding: 1rem 1.1rem 1.05rem;
  border: 1px solid #e8e6e0;
  border-radius: 16px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(27, 31, 28, 0.04);
}

.stat__head {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin-bottom: 0.8rem;
}

.stat__ico {
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 10px;
}

.stat--info .stat__ico {
  background: #e9efe6;
  color: #45624b;
}

.stat--ok .stat__ico {
  background: #e6f2e3;
  color: #3d9a4f;
}

.stat--warn .stat__ico {
  background: #fbf0d6;
  color: #c98a14;
}

.stat--bad .stat__ico {
  background: #fbe4e1;
  color: #d5503f;
}

.stat__label {
  font-size: 0.64rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #7c817b;
}

.stat__num {
  display: inline-block;
  font-size: 2.3rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.01em;
  color: #1b1f1c;
}

/* On the count's own line here, in the card's colour; the phone moves it
   under the count and mutes the words after the figure. */
.stat__trend {
  display: inline-block;
  margin-left: 0.45rem;
  font-size: 0.72rem;
  font-weight: 700;
  color: #7c817b;
}

.stat__trend b {
  font-weight: 800;
}

.stat--info .stat__trend,
.stat--ok .stat__trend {
  color: #3d9a4f;
}

.stat--warn .stat__trend {
  color: #c98a14;
}

.stat--bad .stat__trend {
  color: #d5503f;
}

.stat .stat__trend.is-zero {
  color: #4b514b;
}

.stat__note {
  margin: 0.8rem 0 0;
  font-size: 0.72rem;
  color: #7c817b;
}

/* --- The ring and the month --- */
.ov-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: stretch;
  gap: 0.9rem;
  margin-bottom: 2rem;
}

/* --- User Management --- */
/* The Accounts table needs the panel's whole width: inside .panel-card's
   680px cap it ran past the card's edge, cutting Contact off and leaving
   Created and each row's buttons out of sight. The form above it takes
   the same width, so the two cards line up. */
[data-panel="users"] .panel-card {
  max-width: none;
}

/* Tighter cells, so the six columns fit a laptop screen: the Player ID
   sits under the name instead of in a column of its own, an email can
   break onto a second line, and a row's three buttons wrap under one
   another rather than holding the table at their combined width. */
[data-panel="users"] th,
[data-panel="users"] td,
[data-panel="players"] th,
[data-panel="players"] td {
  padding: 0.75rem 0.8rem;
}

/* Onto two lines only when the column is squeezed; a wide screen has the
   room to keep it on one. */
[data-panel="users"] td .badge {
  font-size: 0.62rem;
  line-height: 1.3;
  text-align: center;
  white-space: normal;
}

.users-id {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.78rem;
  color: var(--brand);
}

.users-contact {
  min-width: 7rem;
  overflow-wrap: anywhere;
}

.users-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.users-actions .btn {
  white-space: nowrap;
}

/* No floor under the table on a wider screen, so the columns give way
   before it runs past the card. The phone keeps the floor and scrolls the
   table sideways inside .table-wrap instead. Registrants' table takes the
   same, its sports on one line and its date wrapping when squeezed. */
@media (min-width: 701px) {
  [data-panel="users"] table,
  [data-panel="players"] table {
    min-width: 0;
  }
}

/* Where the rail still takes its 240px of a small laptop's width. */
@media (min-width: 701px) and (max-width: 1100px) {
  [data-panel="users"] th,
  [data-panel="users"] td,
  [data-panel="players"] th,
  [data-panel="players"] td {
    padding: 0.65rem 0.6rem;
  }
}

/* A .panel-card inside the pair, so it drops the card's own width cap and
   bottom margin -- the grid places it. */
.ov-pair .panel-card {
  max-width: none;
  margin-bottom: 0;
}

/* .player-card .card sets these, one class deeper than .event-overview. */
.player-card .event-overview {
  padding: 1.1rem 1.2rem 1.2rem;
  border: 1px solid #e8e6e0;
  border-radius: 18px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(27, 31, 28, 0.04);
}

.event-overview .panel-card__head {
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1.1rem;
}

.event-overview .panel-card__ico {
  width: 38px;
  height: 38px;
  border-radius: 11px;
  background: #eff0ec;
  color: #1b1f1c;
}

.event-overview .panel-card__head h3 {
  margin: 0 0 0.1rem;
  font-size: 1rem;
  color: #1b1f1c;
}

.event-overview .panel-card__head p {
  font-size: 0.72rem;
  color: #7c817b;
}

/* Opens the full Registrants list, every status. "View details" here,
   "Details" alone on the phone. */
.event-overview__more {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 0.3rem;
  margin-left: auto;
  padding: 0.55rem 0.8rem 0.55rem 1rem;
  border: none;
  border-radius: 999px;
  background: #1c3a2b;
  color: #fff;
  font-family: var(--font);
  font-size: 0.74rem;
  font-weight: 800;
  white-space: nowrap;
  cursor: pointer;
}

.event-overview__short {
  display: none;
}

.event-overview__row {
  display: flex;
  align-items: center;
  gap: 1.75rem;
}

.donut {
  position: relative;
  flex: none;
  width: 164px;
  height: 164px;
  border-radius: 50%;
  /* admin.js writes the real stops; this is what shows with nobody on the
     roster yet. */
  background: conic-gradient(#e2e8f0 0deg 360deg);
}

/* The count and "Players" under it; the "Total" above it is left out. */
.donut__hole {
  position: absolute;
  inset: 22px;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 0.15rem;
  border-radius: 50%;
  background: #fff;
  text-align: center;
}

.donut__hole small:first-child {
  display: none;
}

.donut__hole small {
  font-size: 0.58rem;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #7c817b;
}

.donut__hole strong {
  font-size: 2.2rem;
  font-weight: 800;
  line-height: 1;
  color: #1b1f1c;
}

.donut-legend {
  flex: 1 1 0;
  min-width: 0;
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
}

.donut-legend li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid #eeece6;
  font-size: 0.8rem;
  color: #1b1f1c;
}

.donut-legend i {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: currentColor;
}

/* Right-aligned in fixed-width columns so the counts and percentages
   line up down the list rather than sitting wherever their own width
   puts them. The percentage's is a floor and kept on one line: a share
   over 100% (more invitations answered than accounts) broke "150%" in
   two. */
.donut-legend b {
  margin-left: auto;
  width: 2.5ch;
  text-align: right;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: #1b1f1c;
}

.donut-legend span {
  min-width: 4.5ch;
  text-align: right;
  font-size: 0.68rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: #9a9e98;
}

/* --- Phone and tablet --- */
.admin-phonebar,
.admin-bottombar,
.admin-screen-title {
  display: none;
}

/* The phone Overview's own pieces (see "Overview on the phone" below):
   hidden anywhere wider, and the header buttons' tile wrappers dissolved
   so their icon, text and dot lay out as direct children of the button,
   the way they did before the wrappers were there. */
.admin-top__sub,
.admin-top__phone,
.stat__go {
  display: none;
}

.admin-top__ico,
.admin-top__txt,
.admin-top__lbl {
  display: contents;
}

/* Two counts a row, and the ring and the month one above the other: side
   by side here the ring's legend and the month's cells get too narrow. */
@media (max-width: 1100px) {
  .stat-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .ov-pair {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 880px) {
  /* The rail costs a third of a tablet's width and the pill strip already
     names every panel, so below this the strip is the navigation. */
  .admin-side {
    display: none;
  }
}

@media (max-width: 700px) {
  .admin-layout {
    /* Clears the fixed bar at the foot of the screen. */
    padding-bottom: 62px;
  }

  .admin-main.player-card {
    padding: 0 0.9rem 1.25rem;
  }

  .admin-phonebar {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    margin: 0 -0.9rem 0.9rem;
    padding: 0.6rem 0.9rem;
    background: linear-gradient(180deg, #10402f 0%, #0a2a1f 100%);
    color: #fff;
  }

  .admin-phonebar .admin-chip {
    margin-left: auto;
    background: rgba(255, 255, 255, 0.14);
    color: #fff;
  }

  .admin-top {
    gap: 0.6rem;
    margin-bottom: 0.6rem;
  }

  /* One row, each sized to its own label rather than to an equal share --
     "SMTP LIVE" and "Public site" are wider words than "Log out" and the
     row reads better letting them be. */
  .admin-top__actions {
    gap: 0.45rem;
    flex-wrap: nowrap;
    /* At their full size the three can just outrun a narrow screen --
       scrolling the row is better than shrinking the type to fit. */
    overflow-x: auto;
    scrollbar-width: none;
  }

  .admin-top__actions::-webkit-scrollbar {
    display: none;
  }

  .admin-top__actions .btn,
  .admin-top__actions .badge--lead {
    flex: 0 1 auto;
    min-width: 0;
    gap: 0.4rem;
    padding: 0.7rem 0.75rem;
    font-size: 0.8rem;
    white-space: nowrap;
  }

  .admin-top__greet h1 {
    font-size: 1rem;
  }

  .admin-top__greet p {
    font-size: 0.74rem;
  }

  .admin-chip--top {
    display: none;
  }

  .admin-screen-title {
    display: block;
    margin-bottom: 0.5rem;
    font-family: var(--font);
    font-size: 1.05rem;
    font-weight: 800;
    color: #0f172a;
  }

  /* The pill strip is replaced by the bar at the foot of the screen. */
  .tabs.admin-tabs {
    display: none;
  }

  .stat-grid {
    gap: 0.6rem;
    margin-bottom: 1rem;
  }

  .stat {
    padding: 0.75rem 0.8rem 0.85rem;
  }

  .stat__num {
    font-size: 1.5rem;
  }

  .stat__ico {
    width: 28px;
    height: 28px;
  }

  .stat__label {
    font-size: 0.58rem;
  }

  .stat__note,
  .stat__trend {
    font-size: 0.64rem;
  }

  .admin-bottombar {
    position: fixed;
    z-index: 60;
    left: 0;
    right: 0;
    bottom: 0;
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    background: #fff;
    border-top: 1px solid rgba(16, 24, 40, 0.1);
    box-shadow: 0 -2px 12px rgba(16, 24, 40, 0.08);
  }

  .admin-bottombar .tab {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    padding: 0.45rem 0.1rem 0.5rem;
    border: none;
    background: none;
    color: #94a3b8;
    font-family: var(--font);
    font-size: 0.55rem;
    font-weight: 700;
    letter-spacing: 0;
    text-transform: none;
    cursor: pointer;
  }

  .admin-bottombar .tab.is-active {
    border: none;
    color: var(--brand);
  }
}

/* --- The header and Overview's heading on a wider screen ---
   The greeting on the left and a row of pills on the right -- the mail
   status, the public site, log out and the admin chip -- then Overview's
   own heading with today's date. The phone makes the pills a row of
   tiles instead, in the block below. */
@media (min-width: 701px) {
  .admin-top {
    gap: 1rem 1.5rem;
    margin-bottom: 1.6rem;
  }

  /* The phone card's; the greeting here is words alone. */
  .admin-top__wave,
  .admin-top__bang {
    display: none;
  }

  .admin-top__greet h1 {
    font-size: clamp(1.5rem, 2.3vw, 1.95rem);
    line-height: 1.15;
    letter-spacing: -0.01em;
    color: #1b1f1c;
  }

  .admin-top__greet p {
    margin-top: 0.3rem;
    font-size: 0.8rem;
    color: #7c817b;
  }

  .admin-top__actions {
    gap: 0.55rem;
  }

  /* .badge.badge--*, not .badge--* alone: the light theme's own badge
     colours further up are two classes deep and would otherwise win. */
  .admin-top__actions .btn,
  .admin-top__actions .badge--lead,
  .admin-top__actions .badge.badge--ok,
  .admin-top__actions .badge.badge--warn {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    height: 36px;
    padding: 0 0.95rem;
    border: 1px solid #e3e1da;
    border-radius: 999px;
    background: #fff;
    color: #1b1f1c;
    box-shadow: none;
    font-family: var(--font);
    font-size: 0.76rem;
    font-weight: 700;
    letter-spacing: 0;
    text-transform: none;
  }

  .admin-top__actions .badge.badge--ok {
    border-color: #cfe8a4;
    background: #e4f4c9;
    color: #1f4d2e;
  }

  .admin-top__actions .badge.badge--warn {
    border-color: #f0dc9e;
    background: #fbf0d6;
    color: #8a5a06;
  }

  /* The mail pill is its light and its words -- the light first, and none
     of the tile's icon. .admin-top__lbl is display: contents here, so its
     dot is one of the pill's own flex items and order can move it. */
  .badge--lead .admin-top__ico {
    display: none;
  }

  .admin-top__lbl .badge__dot {
    order: -1;
  }

  .badge--ok .admin-top__lbl .badge__dot {
    background: #3d9a4f;
  }

  .badge--warn .admin-top__lbl .badge__dot {
    background: #c98a14;
  }

  /* No external-link arrow after "Public site", and the pill's words in
     ink rather than the page's link blue -- body.player-page's link rule
     is two classes and two elements deep, hence the body.player-page. */
  .admin-top__lbl .icon {
    display: none;
  }

  body.player-page .admin-top__actions .admin-top__site,
  body.player-page .admin-top__actions .admin-top__site:hover {
    color: #1b1f1c;
  }

  .admin-top__out .icon {
    color: #d5503f;
  }

  .admin-chip--top {
    height: 36px;
    gap: 0.5rem;
    padding: 0 1rem 0 0.35rem;
    background: #0f3a2a;
    color: #fff;
    font-size: 0.76rem;
    letter-spacing: 0;
  }

  .admin-chip--top .admin-chip__av {
    width: 26px;
    height: 26px;
    background: #c9ef5d;
    color: #0f3a2a;
    font-size: 0.72rem;
    font-weight: 800;
  }

  .ov-head {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin: 0 0 1rem;
  }

  .ov-head__text {
    flex: 1 1 auto;
    min-width: 0;
  }

  .ov-head .ov-head__text h2 {
    margin: 0;
    font-family: var(--font);
    font-size: 1.2rem;
    font-weight: 800;
    line-height: 1.2;
    letter-spacing: 0;
    text-transform: none;
    color: #1b1f1c;
  }

  .ov-head .ov-head__text p {
    margin: 0.1rem 0 0;
    font-size: 0.72rem;
    color: #7c817b;
  }

  .ov-head__date {
    display: inline-flex;
    flex: none;
    align-items: center;
    gap: 0.5rem;
    height: 36px;
    padding: 0 0.9rem;
    border: 1px solid #e3e1da;
    border-radius: 10px;
    background: #fff;
    color: #1b1f1c;
    font-family: var(--font);
    font-size: 0.76rem;
    font-weight: 700;
    white-space: nowrap;
    cursor: pointer;
  }
}

/* The rail names every panel on a wide screen, so the pill strip is left
   out there; under 881px the rail goes and the strip is the navigation. */
@media (min-width: 881px) {
  .tabs.admin-tabs {
    display: none;
  }
}

/* --- Overview on the phone ---
   The greeting as a card, the three header actions as a row of equal
   tiles, a heading of Overview's own with today's date, the four counts
   as tinted cards with a way into the list behind each, and the ring
   beside its legend rather than above it. Everything here is phone only;
   the pieces it shows are hidden in the block above. */
@media (max-width: 700px) {
  .admin-top {
    gap: 0.65rem;
    margin-bottom: 1rem;
  }

  /* The two bands rising across the right-hand side -- one pale, one a
     step bluer -- sit behind the balls in ::after below. */
  .admin-top__greet {
    position: relative;
    overflow: hidden;
    flex: 1 1 100%;
    align-items: flex-start;
    gap: 0.7rem;
    padding: 1.25rem 1rem 1.35rem;
    border: 1px solid #d9e6f8;
    border-radius: 16px;
    background:
      linear-gradient(120deg, transparent 63%, rgba(255, 255, 255, 0.55) 63% 70%,
        transparent 70% 73%, rgba(143, 178, 238, 0.28) 73% 81%, transparent 81%),
      linear-gradient(135deg, #e0ebfd 0%, #f2f7ff 100%);
  }

  /* The basketball, the football and the Stelsen S in the corner, drawn in
     admin-greeting-balls.svg; the football runs off the card's bottom
     edge, as in the reference. */
  .admin-top__greet::after {
    content: "";
    position: absolute;
    right: 0;
    bottom: -8px;
    width: 109px;
    height: 88px;
    background: url('../images/admin-greeting-balls.svg') no-repeat center / contain;
    pointer-events: none;
  }

  .admin-top__greet > div {
    position: relative;
    z-index: 1;
    min-width: 0;
  }

  .admin-top__wave {
    font-size: 1.75rem;
  }

  .admin-top__greet h1 {
    font-size: 1.15rem;
  }

  /* The line under the greeting stops short of the balls; the greeting
     itself sits above them and keeps the card's full width, so "Good
     afternoon, Admin!" stays on one line. */
  .admin-top__greet p {
    margin-top: 0.25rem;
    padding-right: 4.6rem;
    font-size: 0.74rem;
    line-height: 1.45;
    color: #52627a;
  }

  /* One row of three equal tiles, filling the width, where the block above
     let the row scroll. minmax(0, 1fr) rather than auto, so the three come
     out the same width whatever their words, and stretched so they come
     out the height of the tallest too. */
  .admin-top__actions {
    flex: 1 1 100%;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    align-items: stretch;
    gap: 0.55rem;
    overflow: visible;
  }

  /* White tiles, each telling itself apart by the square its icon sits in
     rather than by a tint across the whole tile: icon on top, label and
     sub-line under it. .badge.badge--*, not .badge--* alone: the light
     theme's own badge colours further up are two classes deep and would
     otherwise win. */
  .admin-top__actions .btn,
  .admin-top__actions .badge--lead,
  .admin-top__actions .badge.badge--ok,
  .admin-top__actions .badge.badge--warn,
  .admin-top__actions .admin-top__site {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 0.65rem;
    min-width: 0;
    padding: 0.75rem 0.7rem 0.8rem;
    border: 1px solid #e8e6e0;
    border-radius: 16px;
    background: #fff;
    color: #1b1f1c;
    box-shadow: 0 1px 2px rgba(27, 31, 28, 0.04);
    font-family: var(--font);
    font-size: 0.8rem;
    font-weight: 800;
    letter-spacing: 0;
    text-transform: none;
    text-align: left;
    white-space: normal;
  }

  .admin-top__ico {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    border-radius: 9px;
    background: #eff0ec;
    color: #1b1f1c;
  }

  .admin-top__ico .icon {
    width: 17px;
    height: 17px;
  }

  .badge--ok .admin-top__ico {
    background: #e6f2e3;
    color: #3d9a4f;
  }

  .badge--warn .admin-top__ico {
    background: #fbf0d6;
    color: #c98a14;
  }

  .admin-top__out .admin-top__ico {
    background: #fbe4e1;
    color: #d5503f;
  }

  .admin-top__txt {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
  }

  /* "Public Site", "Log Out": capitalised here rather than in the markup,
     so the wider layouts keep the words as they were. The status light
     moves down to the sub-line, and the external-link arrow goes. */
  .admin-top__lbl {
    display: block;
    font-size: 0.8rem;
    font-weight: 800;
    line-height: 1.2;
    text-transform: capitalize;
    color: #1b1f1c;
  }

  .admin-top__lbl .icon,
  .admin-top__lbl .badge__dot,
  .admin-top__desk {
    display: none;
  }

  .admin-top__phone {
    display: inline;
  }

  .admin-top__sub {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.64rem;
    font-weight: 500;
    line-height: 1.3;
    letter-spacing: 0;
    text-transform: none;
    color: #7c817b;
  }

  .admin-top__sub .badge__dot {
    flex: none;
    width: 6px;
    height: 6px;
  }

  .badge--ok .admin-top__sub .badge__dot {
    background: #3d9a4f;
  }

  .badge--warn .admin-top__sub .badge__dot {
    background: #c98a14;
  }

  /* Overview names itself in .ov-head below. */
  .admin-main[data-tab="overview"] .admin-screen-title {
    display: none;
  }

  .ov-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0.55rem 0 0.9rem;
  }

  .ov-head__text {
    flex: 1 1 auto;
    min-width: 0;
  }

  .ov-head .ov-head__text h2 {
    margin: 0;
    font-family: var(--font);
    font-size: 1.35rem;
    font-weight: 800;
    line-height: 1.15;
    letter-spacing: -0.01em;
    text-transform: none;
    color: #1b1f1c;
  }

  .ov-head .ov-head__text p {
    margin: 0.1rem 0 0;
    font-size: 0.74rem;
    line-height: 1.35;
    color: #7c817b;
  }

  .ov-head__date {
    display: inline-flex;
    flex: none;
    align-items: center;
    gap: 0.45rem;
    padding: 0.5rem 0.75rem;
    border: 1px solid #e8e6e0;
    border-radius: 12px;
    background: #fff;
    box-shadow: 0 1px 2px rgba(27, 31, 28, 0.04);
    color: #1b1f1c;
    font-family: var(--font);
    font-size: 0.8rem;
    font-weight: 800;
    white-space: nowrap;
    cursor: pointer;
  }

  .stat-grid {
    gap: 0.65rem;
  }

  .ov-pair {
    gap: 0.65rem;
    margin-bottom: 1.25rem;
  }

  /* The cards' white and their tinted squares are the wide screen's too
     (see "The four counts"); only the sizes and the layout inside change
     here -- the label across from the square, and the percentage under
     the count rather than beside it. */
  .stat {
    padding: 0.85rem 0.9rem 0.9rem;
    border-radius: 18px;
  }

  .stat__head {
    gap: 0.5rem;
    margin-bottom: 0.85rem;
  }

  .stat__ico {
    width: 32px;
    height: 32px;
    border-radius: 9px;
  }

  .stat__label {
    margin-left: auto;
    font-size: 0.62rem;
    line-height: 1.2;
    text-align: right;
  }

  /* No look of its own: laid over the whole card, so the card is what
     answers the tap. */
  .stat__go {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 1;
    padding: 0;
    border: none;
    border-radius: 18px;
    background: none;
    cursor: pointer;
  }

  .stat__go .icon {
    display: none;
  }

  .stat__num {
    display: block;
    font-size: 2.1rem;
  }

  /* The figure in the card's colour (grey when it is nought), the words
     after it muted. What the card counts is already in its label, so the
     note under it goes. */
  .stat .stat__trend,
  .stat .stat__trend.is-zero {
    display: block;
    margin: 0.55rem 0 0;
    font-size: 0.68rem;
    font-weight: 500;
    line-height: 1.3;
    color: #7c817b;
  }

  .stat--info .stat__trend b,
  .stat--ok .stat__trend b {
    color: #3d9a4f;
  }

  .stat--warn .stat__trend b {
    color: #c98a14;
  }

  .stat--bad .stat__trend b {
    color: #d5503f;
  }

  .stat .stat__trend.is-zero b {
    color: #7c817b;
  }

  .stat__note {
    display: none;
  }

  /* .player-card .card sets this, one class deeper than .event-overview. */
  .player-card .event-overview {
    padding: 1rem 0.95rem 0.75rem;
  }

  .event-overview .panel-card__head {
    flex-wrap: nowrap;
    gap: 0.7rem;
    margin-bottom: 1rem;
  }

  .event-overview .panel-card__ico .icon {
    width: 18px;
    height: 18px;
  }

  .event-overview .panel-card__head p {
    font-size: 0.7rem;
  }

  .event-overview__more {
    padding: 0.5rem 0.75rem 0.5rem 0.95rem;
    font-size: 0.76rem;
  }

  .event-overview__long {
    display: none;
  }

  .event-overview__short {
    display: inline;
  }

  /* A smaller ring, and the legend beside it without the lines between
     its rows. */
  .event-overview__row {
    gap: 1.1rem;
  }

  .event-overview .donut {
    width: 124px;
    height: 124px;
  }

  .event-overview .donut__hole {
    inset: 17px;
  }

  .event-overview .donut__hole strong {
    font-size: 1.7rem;
  }

  .event-overview .donut__hole small {
    font-size: 0.56rem;
  }

  .event-overview .donut-legend li {
    gap: 0.55rem;
    padding: 0.4rem 0;
    border-bottom: none;
    font-size: 0.78rem;
  }

  .event-overview .donut-legend b {
    width: 2ch;
    font-size: 0.85rem;
  }

  .event-overview .donut-legend span {
    font-size: 0.66rem;
  }
}

/* Under the common 390px phone the greeting's corner picture comes down a
   size, and the counts' squares and labels tighten so the label keeps one
   line beside its square. */
@media (max-width: 389px) {
  /* A size smaller, so the S stays clear of the end of the greeting. */
  .admin-top__greet::after {
    width: 98px;
    height: 79px;
  }

  .admin-top__greet p {
    padding-right: 4.1rem;
  }

  .stat__head {
    gap: 0.35rem;
  }

  .stat__ico {
    width: 28px;
    height: 28px;
  }

  .stat__label {
    letter-spacing: 0.06em;
  }
}

/* Too narrow for the ring and its legend side by side: back to stacked.
   The tiles' and the counts' type comes down a step so their words keep
   to the width they have. */
@media (max-width: 359px) {
  .admin-top__actions .btn,
  .admin-top__actions .badge--lead,
  .admin-top__actions .badge.badge--ok,
  .admin-top__actions .badge.badge--warn,
  .admin-top__actions .admin-top__site {
    padding: 0.65rem 0.6rem 0.7rem;
  }

  .admin-top__lbl {
    font-size: 0.74rem;
  }

  .event-overview__row {
    flex-direction: column;
  }

  .event-overview__row .donut-legend {
    flex: none;
    width: 100%;
  }

  .stat__head {
    gap: 0.3rem;
  }

  .stat__ico {
    width: 26px;
    height: 26px;
  }

  .stat__label {
    letter-spacing: 0.03em;
  }
}

/* --- The Active switch in Game Management --- */
/* A button, not a checkbox: the row already reports its own state through
   aria-checked, and the press has to go to the server before the switch is
   allowed to move -- a checkbox would flip itself first and then have to be
   put back if the save failed. */
.switch {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: #cbd5e1;
  cursor: pointer;
  transition: background 0.15s;
}

.switch i {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(16, 24, 40, 0.3);
  transition: transform 0.15s;
}

.switch.is-on {
  background: var(--brand);
}

.switch.is-on i {
  transform: translateX(18px);
}

.switch:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* ==========================================================================
   GAME MANAGEMENT CARDS
   One card per game: the sport's photo across the top, the when and where
   under it, the number of players as an editable box, and the switch that
   decides whether any of it reaches a player.
   ========================================================================== */
.game-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 270px), 1fr));
  gap: 1rem;
}

.game-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: 14px;
  background: #fff;
  border: 1px solid rgba(16, 24, 40, 0.09);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.06);
  transition: opacity 0.15s, box-shadow 0.15s;
}

/* A game that is switched off is still here to be worked on, so it is faded
   rather than hidden -- and the fade stops at the footer, because the switch
   that turns it back on has to stay as legible as any other. */
.game-card.is-off .game-card__photo,
.game-card.is-off .game-card__body {
  opacity: 0.55;
}

.game-card__photo {
  position: relative;
  padding: 0.9rem 1rem;
  min-height: 104px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  /* The sport's own colour, under whichever photo the rules below put on top.
     A sport added later has no photo and keeps this. */
  background: var(--sport-color, var(--brand));
  background-size: cover;
  background-position: center;
}

/* Dark enough under the type to read over any part of a photograph. */
.game-card__photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(8, 12, 18, 0.15) 0%, rgba(8, 12, 18, 0.72) 100%);
}

.game-card__photo > * {
  position: relative;
  z-index: 2;
}

.game-card__mark {
  position: absolute;
  top: -0.1rem;
  right: 0;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.18);
  color: #fff;
}

.game-card__mark .svg-sport {
  width: 18px;
  height: 18px;
  stroke: #fff;
}

.game-card__photo h3 {
  margin: 0;
  font-family: var(--font);
  font-size: 1rem;
  font-weight: 800;
  color: #fff;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.5);
}

.game-card__type {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.82);
}

.game-card__body {
  flex: 1;
  padding: 0.85rem 1rem 0.9rem;
}

.game-card__line {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0 0 0.35rem;
  font-size: 0.78rem;
  color: #334155;
}

.game-card__ico {
  display: flex;
  flex: none;
  color: #94a3b8;
}

.game-card__players {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin-top: 0.7rem;
  font-size: 0.78rem;
  color: #334155;
}

/* Sized to four digits and no more: it sits on one line with its label, and a
   full-width box would read as the card's main field rather than one number
   on it. */
.game-card__players input {
  width: 74px;
  margin-left: auto;
  padding: 0.35rem 0.5rem;
  font-size: 0.82rem;
  text-align: center;
}

.game-card__answers {
  margin: 0.7rem 0 0;
  padding-top: 0.6rem;
  border-top: 1px solid rgba(16, 24, 40, 0.08);
  font-size: 0.72rem;
  color: #64748b;
}

.game-card__answers strong {
  color: #15803d;
}

/* Wraps: a card is about 290px and a switched-on game carries five controls --
   the switch, its label, Send invitation, Edit and Delete. Held on one line
   they ran off the end and Delete was cut in half. */
.game-card__foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  row-gap: 0.5rem;
  padding: 0.7rem 0.85rem;
  border-top: 1px solid rgba(16, 24, 40, 0.08);
  background: #f8fafc;
}

.game-card__state {
  margin-right: auto;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #64748b;
}

.game-card:not(.is-off) .game-card__state {
  color: var(--brand-dim);
}

/* The photos, keyed by the slug on the sports table rather than by the
   .division-* classes the homepage uses: three of those slugs differ from
   these (running/funrun, lawn-tennis/lawntennis, shooting-range/shooting), and
   keying off the real slug is what lets the card name its own class. */
.gm-photo--badminton {
  background-image: url('../images/badminton.webp');
}

.gm-photo--basketball {
  background-image: url('../images/basketball.webp');
}

.gm-photo--running {
  background-image: url('../images/funrun.webp');
}

.gm-photo--golf {
  background-image: url('../images/golf.webp');
}

.gm-photo--lawn-tennis {
  background-image: url('../images/lawn-tennis.webp');
}

.gm-photo--pickleball {
  background-image: url('../images/pickleball.webp');
}

.gm-photo--shooting-range {
  background-image: url('../images/firerange.webp');
}

.gm-photo--volleyball {
  background-image: url('../images/volleyball.webp');
}

/* --- Game Management's details dialog --- */
/* The dialog is 560px at most and gets narrower than that on a phone, so its
   rows are auto-fit rather than a fixed column count: three across where they
   fit, two, then one, with no row ever wider than the card it sits in. */
.ge-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 140px), 1fr));
  gap: 0.85rem;
}

.ge-grid--two {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
}

/* The dialog scrolls down, never sideways: a form that has to be dragged left
   and right to be read is the thing this replaced. */
#gameEditModal .modal-card {
  overflow-x: hidden;
}

#gameEditModal input,
#gameEditModal select,
#gameEditModal textarea {
  max-width: 100%;
}

.ge-photo {
  display: flex;
  align-items: flex-start;
  gap: 0.9rem;
}

.ge-photo__preview {
  flex: none;
  width: 96px;
  height: 68px;
  border-radius: 10px;
  background: #e2e8f0 center / cover no-repeat;
  border: 1px solid rgba(16, 24, 40, 0.12);
}

.ge-photo .label {
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--player-ink-soft, #5b6472);
}

/* --- The Google Calendar connection card, at the top of Settings --- */
/* The date-square in place of a line icon, in Google Calendar's own blue
   on its own pale ground -- so the card reads as being about that
   service and not about this one. */
.panel-card__ico.gcal__mark {
  background: #e8f0fe;
  color: #1a73e8;
  font-weight: 800;
  font-size: 0.9rem;
}

.gcal__actions:not(:empty) {
  margin-top: 1rem;
}

/* ==========================================================================
   PLAYER DASHBOARD
   In the home page hero's greens. The dashboard is the one page a player
   lives in rather than passes through, and it runs on a pale green ground
   with a forest green hero, green shortcut tiles and white
   game cards. Everything here is scoped to the dashboard's own classes, so
   the public pages, the admin console and the emails keep the brand green
   they have always had -- only this page changes colour.
   ========================================================================== */
.player-page {
  /* The home page hero's greens, so the player's pages read as the same
     app: the ink is the hero's forest green (--ph-surface-1), the accent
     its button's (#22c55e to #15954a, the deep end here so it holds up as
     text on white). The bottom tab bar is the same component the public
     pages run and reads --ph-panel/-panel-line/-accent, which :root
     already sets to the hero's own -- so nothing here overrides them. */
  --dash-ink: #091810;
  --dash-ink-soft: #56675c;
  --dash-accent: #15954a;
  --dash-accent-deep: #0f6e37;
  --dash-line: rgba(9, 24, 16, 0.09);
  --dash-card: #ffffff;
}

/* One reading column for the whole dashboard. Without it the hero and the
   game cards run the full 1440px the shell allows, which leaves a card's
   two short facts stranded at the left of a very wide white band. */
.player-page .dashboard-today,
.player-page .welcome-card,
.player-page .quick-filters,
.player-page .player-section,
.player-page .dashboard-desktop-nav .site-header {
  max-width: 1080px;
  margin-inline: auto;
}

/* On phone, .player-shell's own padding-inline plus .player-card's --pad
   inside it left the hero, the tiles and every section (Upcoming Games'
   cards, the calendar, Notifications' own list) inset further than the
   floating tab bar below them (0.8rem/12.8px a side), so none of them
   lined up edge to edge with it. Pulling them out by both and back in by
   the tab bar's own inset matches them all exactly.

   .player-panel not .player-section .player-panel: on Settings the panel
   sits inside a .player-section that already gets this same pull, and
   applying it twice (once on the section, again on the panel nested
   inside it) pulled the card out twice as far, off the left edge of the
   screen. Profile's own panel has no .player-section wrapper around it,
   so it still needs the rule directly. */
@media (max-width: 880px) {
  .player-page .dashboard-today,
  .player-page .welcome-card,
  .player-page .quick-filters,
  .player-page .player-section,
  .player-page #notifList,
  .player-page .player-panel:not(.player-section .player-panel) {
    margin-inline: calc(0.8rem - var(--pad) - clamp(0.75rem, 3.5vw, 3rem));
  }
}

/* --- Today's date, up beside the brand --- */
/* Pulled up into the space .dashboard-phone-header reserves: the wordmark
   it holds is left-aligned and two lines tall, so the date sits beside it
   rather than under it, which is where the design has it. The top margin
   lines its first line up with STELSEN SPORTS rather than with the header
   box's own top edge, and it is a size down from the name below it -- it
   is the quietest thing on the screen, not a heading. */
.dashboard-today {
  margin: -4.9rem 0 0.65rem;
  text-align: right;
  line-height: 1.25;
}

@media (min-width: 881px) {
  .dashboard-today {
    margin-top: 0;
  }
}

.dashboard-today strong {
  display: block;
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--dash-ink);
}

.dashboard-today span {
  font-size: 0.64rem;
  color: var(--dash-ink-soft);
}

/* --- The hero greeting --- */
/* A forest green panel rather than a white card: it is the one block on
   the page that is about the player rather than about a game, and the
   design leads with it. The court photo sits under a dark green wash (the
   home page hero's own) so the white text on
   top stays readable at any crop. */
.welcome-card {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: 1rem;
  margin: 0.95rem 0 0.9rem;
  padding: 0.65rem 1.5rem;
  border-radius: 18px;
  background:
    radial-gradient(120% 140% at 88% 18%, rgba(47, 215, 95, 0.35) 0%, rgba(47, 215, 95, 0) 58%),
    linear-gradient(90deg, rgba(9, 24, 16, 0.9) 0%, rgba(11, 30, 20, 0.7) 45%, rgba(11, 30, 20, 0.5) 100%),
    url('../images/welcome-court.webp') center / cover no-repeat;
  color: #fff;
  box-shadow: 0 14px 34px rgba(9, 24, 16, 0.28);
}

/* The court's own sweep of light, behind the type. */
.welcome-card::after {
  content: "";
  position: absolute;
  inset: auto -20% -60% 30%;
  height: 150%;
  background: radial-gradient(50% 50% at 50% 50%, rgba(74, 222, 128, 0.22) 0%, rgba(74, 222, 128, 0) 70%);
  pointer-events: none;
}

.welcome-card__greet {
  position: relative;
  z-index: 1;
  flex: 1 1 auto;
  min-width: 0;
}

/* "Good morning," quiet on its own line, the name loud under it -- the
   design's own split, and it keeps a long name off the greeting's line
   instead of squeezing both onto one. */
/* .player-card in the selector only to outscore `.player-card h2`, which
   paints every heading on these pages with the dark --player-ink meant
   for type on their white cards. This one sits on the dark green hero. */
.player-card .welcome-card__name {
  margin: 0;
  font-family: var(--font);
  font-size: 1.02rem;
  font-weight: 600;
  line-height: 1.25;
  /* Not an alpha over the hero: the wash behind this line is at its
     lightest right here, so a translucent white came out barely darker
     than the panel. Opaque, one clear step below the name under it. */
  color: #c9efd6;
}

.welcome-card__name strong {
  display: block;
  margin-top: 0.05rem;
  font-size: 1.5rem;
  font-weight: 800;
  letter-spacing: -0.015em;
  color: #fff;
}

/* The short rule under the name, as in the design. */
.welcome-card__lede {
  position: relative;
  margin: 0.55rem 0 0;
  padding-top: 0.55rem;
  font-size: 0.82rem;
  line-height: 1.35;
  color: rgba(255, 255, 255, 0.82);
}

.welcome-card__lede::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 46px;
  height: 3px;
  border-radius: 2px;
  background: #2fd75f;
}

/* The handwritten tagline in the hero's corner. No script face is loaded
   (the site runs on system fonts by design), so it leans on the system
   italic and its own slant instead of pretending to be a signature. */
.welcome-card__script {
  position: relative;
  z-index: 1;
  flex: none;
  align-self: flex-end;
  font-size: 0.86rem;
  font-style: italic;
  font-weight: 600;
  line-height: 1.35;
  text-align: right;
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 1px 6px rgba(6, 8, 12, 0.55);
  transform: rotate(-6deg);
}

/* --- The two shortcut tiles --- */
/* Side by side at every width, each a gradient card in its own colour:
   its icon in a frosted square and a frosted round arrow across the top,
   the label and a line saying what it opens under them, and two faint
   circles in the right-hand corners for texture. */
.quick-filters {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.8rem;
  margin-bottom: 0.9rem;
}

/* isolation, so the circles' z-index -1 lands above the gradient and
   under the tile's own content. */
.quick-filter {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  padding: 0.7rem 0.9rem 0.8rem;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 18px;
  background: linear-gradient(135deg, var(--tile-a), var(--tile-b));
  color: #fff;
  font-family: var(--font);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 10px 24px var(--tile-shadow);
  transition: transform 0.15s, box-shadow 0.15s;
}

.quick-filter::before,
.quick-filter::after {
  content: "";
  position: absolute;
  z-index: -1;
  width: 110px;
  height: 110px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  pointer-events: none;
}

.quick-filter::before {
  top: -32px;
  right: -40px;
}

.quick-filter::after {
  right: -34px;
  bottom: -66px;
  background: rgba(255, 255, 255, 0.07);
}

.quick-filter:hover {
  transform: translateY(-2px);
  box-shadow: 0 14px 28px var(--tile-shadow);
}

/* Each tile its own colour, the way the design has them. */
.quick-filter[data-tile="games"] {
  --tile-a: #22c55e;
  --tile-b: #15954a;
  --tile-shadow: rgba(31, 174, 94, 0.3);
}

.quick-filter[data-tile="teams"] {
  --tile-a: #166c3b;
  --tile-b: #0b1e14;
  --tile-shadow: rgba(11, 30, 20, 0.3);
}

.quick-filter__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
}

.quick-filter__ico {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.2);
}

.quick-filter__ico .icon {
  width: 18px;
  height: 18px;
}

.quick-filter__go {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.22);
}

.quick-filter__label {
  display: block;
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

.quick-filter__sub {
  display: block;
  margin-top: 0.1rem;
  font-size: 0.76rem;
  font-weight: 500;
  line-height: 1.3;
  color: rgba(255, 255, 255, 0.82);
}

/* The tile actually filtering the list right now. The gradient is already
   the tile's loudest state, so "on" is said with a ring and a lift rather
   than by making it louder still. */
.quick-filter.is-active {
  box-shadow: 0 0 0 3px #fff, 0 0 0 5px var(--tile-a), 0 10px 24px var(--tile-shadow);
}

.quick-filter__badge {
  position: absolute;
  top: 0.55rem;
  right: 0.6rem;
  min-width: 18px;
  height: 18px;
  padding-inline: 4px;
  border-radius: 999px;
  background: #e5484d;
  color: #fff;
  font-size: 0.64rem;
  font-weight: 800;
  line-height: 18px;
  text-align: center;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.55);
}

/* --- The section heading --- */
/* A two-row grid rather than one flex line: the count belongs beside the
   title, and the lead runs the full width under both. On one flex line the
   count sat against the middle of a two-line heading block and squeezed
   the lead into wrapping early underneath it. */
.invites-head {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 0.2rem 0.75rem;
  margin-bottom: 0.6rem;
}

.invites-head__text {
  display: contents;
}

/* The accent bar is the title's own ::before, so the heading and its lead
   stay one block rather than needing a wrapper to hold a bar beside them.
   .player-card in the selector to outscore `.player-card h2`, as above. */
.player-card .invites-head__title {
  position: relative;
  margin: 0;
  padding-left: 0.85rem;
  font-family: var(--font);
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: -0.015em;
  color: var(--dash-ink);
}

.player-card .invites-head__title::before {
  content: "";
  position: absolute;
  top: 0.15em;
  bottom: 0.15em;
  left: 0;
  width: 4px;
  border-radius: 3px;
  background: linear-gradient(180deg, #22c55e, #15954a);
}

/* Second row, spanning both columns -- .invites-head__text is display:
   contents, so this is a grid item of .invites-head itself. */
.invites-head__lead {
  grid-column: 1 / -1;
  margin: 0 0 0 0.85rem;
  font-size: 0.76rem;
  color: var(--dash-ink-soft);
}

.invites-head__count {
  grid-row: 1;
  grid-column: 2;
  padding: 0.4rem 0.8rem;
  border-radius: 999px;
  background: #e7f6ec;
  color: #0f6e37;
  font-size: 0.74rem;
  font-weight: 700;
  white-space: nowrap;
}

.invites-head__count strong {
  font-weight: 800;
}

/* --- One game --- */
.invite-card {
  position: relative;
  overflow: hidden;
  margin-bottom: 0.6rem;
  padding: 1.1rem 1.15rem 1.15rem 1.35rem;
  border-radius: 16px;
  background: var(--dash-card);
  border: 1px solid var(--dash-line);
  box-shadow: 0 6px 18px rgba(9, 24, 16, 0.07);
}

/* The sport's colour down the left edge -- a bar drawn by the card itself
   rather than a border, so the radius stays round at both ends. */
.invite-card::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 5px;
  background: var(--sport-color);
}

.invite-card__head {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
}

/* The sport's own drawing, tinted with its colour. */
.invite-card__mark {
  display: grid;
  place-items: center;
  flex: none;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--sport-color) 16%, #fff);
  color: var(--sport-color);
}

.invite-card__mark svg {
  width: 26px;
  height: 26px;
}

.invite-card__lead {
  flex: 1 1 auto;
  min-width: 0;
}

/* "PRACTICE" over the title, in the sport's own colour on its own tint. */
.invite-card__type {
  display: inline-block;
  margin: 0 0 0.35rem;
  padding: 0.2rem 0.5rem;
  border-radius: 6px;
  background: color-mix(in srgb, var(--sport-color) 14%, #fff);
  color: color-mix(in srgb, var(--sport-color) 78%, #000);
  font-size: 0.64rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* .player-card in the selector to outscore `.player-card h3`, as above. */
.player-card .invite-card__title {
  margin: 0 0 0.5rem;
  font-family: var(--font);
  font-size: 1.05rem;
  font-weight: 800;
  letter-spacing: -0.015em;
  color: var(--dash-ink);
}

/* Date, time and venue each their own line with their own icon, rather
   than one run-on line the phone then wraps mid-sentence. They sit
   below the head rather than in the column beside the sport mark, so
   each line has the card's full width and none of the three has to
   wrap or truncate. */
.invite-card__facts {
  margin-top: 0.6rem;
}

.invite-card__meta {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 0 0 0.35rem;
  font-size: 0.87rem;
  line-height: 1.35;
  color: var(--dash-ink);
}

.invite-card__meta > span {
  min-width: 0;
}

.invite-card__meta .icon {
  flex: none;
  margin-top: 0.1rem;
  color: var(--sport-color);
}

.invite-card__tail {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
}

/* Unanswered, and saying so before the card is read. */
.invite-card__new {
  padding: 0.2rem 0.45rem;
  border-radius: 6px;
  background: #e5484d;
  color: #fff;
  font-size: 0.6rem;
  font-weight: 800;
  letter-spacing: 0.06em;
}

.invite-card__more {
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  border: 1px solid var(--dash-line);
  background: #fff;
  color: var(--dash-ink-soft);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.invite-card__more:hover {
  background: #eaf6ee;
  color: var(--dash-accent);
}

/* --- Who is in, and the answer already given --- */
.invite-card__foot {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-top: 0.9rem;
}

.invite-state {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.35rem 0.7rem;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 700;
}

.is-waiting .invite-state {
  background: #fff4e0;
  color: #9a6208;
}

.is-in .invite-state {
  background: #e6f6ec;
  color: #17683c;
}

.is-out .invite-state {
  background: #fdeaea;
  color: #b4262b;
}

/* --- The two answers --- */
.invite-card__actions {
  display: flex;
  gap: 0.6rem;
  flex-wrap: nowrap;
  margin-top: 0.9rem;
}

.invite-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  flex: 1 1 0;
  min-width: 0;
  padding: 0.8rem 0.6rem;
  border-radius: 11px;
  border: 1px solid transparent;
  font-family: var(--font);
  font-size: 0.87rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: filter 0.15s, background 0.15s;
}

/* The home page hero's button green, not the sport's own colour -- that one already
   drives the spine, the icon disc and the type chip, so a card was
   carrying its sport's colour four times over. One consistent action
   colour across every card reads as the app's own "press this" signal. */
.invite-btn--yes,
.invite-btn--view {
  background: linear-gradient(135deg, #22c55e, #15954a);
  color: #fff;
  box-shadow: 0 6px 16px rgba(31, 174, 94, 0.3);
}

.invite-btn--yes:hover,
.invite-btn--view:hover {
  filter: brightness(1.06);
}

/* --view is the answer already given, not an action -- it only reopens
   the popup. On a declined game it sat next to I'll Join in the same
   green, so the card showed two identical buttons and neither read as
   the current state. Declined now reads the same way an accepted one
   does: the state button carries the colour (red here, green there) and
   the button that would flip it is the quiet outlined one. */
.is-out .invite-btn--view {
  background: linear-gradient(135deg, #e5484d, #c1272d);
  box-shadow: 0 6px 16px rgba(229, 72, 77, 0.28);
}

.is-out .invite-btn--yes {
  background: #fff;
  border-color: var(--dash-line);
  color: var(--dash-ink-soft);
  box-shadow: none;
}

.is-out .invite-btn--yes:hover {
  background: #f3f8f5;
  color: var(--dash-ink);
  filter: none;
}

.invite-btn--no {
  background: #fff;
  border-color: var(--dash-line);
  color: var(--dash-ink-soft);
}

.invite-btn--no:hover {
  background: #f3f8f5;
  color: var(--dash-ink);
}

/* --- Participants (Profile): who else is in each game --- */
/* Same shape as an .invite-card -- the sport's colour down the edge, the
   same icon disc, title and meta lines -- since this is the same fact
   (which game, when, where) with a roster under it instead of an answer. */
.participants-card {
  position: relative;
  overflow: hidden;
  margin-bottom: 0.85rem;
  padding: 1.1rem 1.15rem 1.15rem 1.35rem;
  border-radius: 16px;
  background: var(--dash-card);
  border: 1px solid var(--dash-line);
  box-shadow: 0 6px 18px rgba(9, 24, 16, 0.07);
}

.participants-card::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 5px;
  background: var(--sport-color);
}

.participants-card__head {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
}

/* This card's title carries the game type too ("Basketball — Practice
   game"), longer than .invite-card's own (sport name alone), so it wraps
   to two lines at the shared 1.05rem size in the popup's narrower card. */
.participants-card__head .invite-card__title {
  font-size: 0.92rem;
}

.participants-card__roster {
  margin-top: 1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--dash-line);
}

.participants-card__roster h4 {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0 0 0.5rem;
  font-family: var(--font);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dash-ink-soft);
}

/* --- The month at the foot of the page --- */
/* .calendar and its parts are shared with the admin console, whose own
   copy is scoped to #console; these are the player's, in the dashboard's
   forest green and the home page hero's button green. The month and its game count sit centred between two
   round arrows; the days are bare numbers, Sundays in red, the days either
   side of the month greyed; today is outlined in green and a day with a
   game filled with it; a key under the grid names the two. */
.player-page .player-section .calendar {
  padding: 1rem 0.9rem 0.95rem;
  border: 1px solid var(--dash-line);
  border-radius: 18px;
  background: var(--dash-card);
  box-shadow: 0 6px 18px rgba(9, 24, 16, 0.07);
}

.player-page .player-section .calendar-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.9rem;
}

.player-cal__nav {
  display: grid;
  place-items: center;
  flex: none;
  width: 36px;
  height: 36px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: #eaf5ee;
  color: var(--dash-ink);
  cursor: pointer;
  transition: background 0.15s;
}

.player-cal__nav:hover {
  background: #dcefe2;
}

.player-cal__title {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  align-items: center;
  min-width: 0;
  text-align: center;
}

.player-cal__title strong {
  font-size: 1.1rem;
  font-weight: 800;
  line-height: 1.2;
  color: var(--dash-ink);
}

.player-cal__title span {
  margin-top: 0.1rem;
  font-size: 0.72rem;
  color: var(--dash-ink-soft);
}

.player-page .player-section .calendar-grid {
  gap: 4px 2px;
}

.player-page .player-section .calendar-dow {
  padding-bottom: 0.45rem;
  font-family: var(--font);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #859087;
}

/* Sunday's heading and its days in red. nth-child rather than a class:
   the seven headings come first and the grid never varies which day
   starts each row, so every 7n+1 child is a Sunday. */
.player-page .player-section .calendar-dow:first-child,
.player-page .player-section .calendar-day:nth-child(7n + 1) .calendar-date {
  color: #d0543f;
}

/* Bare numbers: the mark for today or a game sits on the number itself,
   a square of its own centred in the cell, so it keeps its shape however
   wide the column comes out. The day is still clickable (see
   #playerCalGrid's click handler in dashboard.js) for what is on. */
.player-page .player-section .calendar-day {
  display: grid;
  place-items: center;
  min-width: 0;
  min-height: 0;
  padding: 0.1rem 0;
  border: none;
  background: none;
}

/* 38px square at most, and no wider than its column on the narrowest
   phones, where a seventh of the card comes out under that. */
.player-page .player-section .calendar-date {
  display: grid;
  place-items: center;
  width: 100%;
  max-width: 38px;
  aspect-ratio: 1;
  border: 1.5px solid transparent;
  border-radius: 11px;
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--dash-ink);
}

/* After the Sunday rule, so a Sunday from the month before is grey too. */
.player-page .player-section .calendar-day.is-outside {
  cursor: default;
}

.player-page .player-section .calendar-day.is-outside .calendar-date {
  font-weight: 500;
  color: #c4cbc6;
}

.player-page .player-section .calendar-day.is-today .calendar-date {
  border-color: var(--dash-accent);
  background: #fff;
  font-weight: 800;
  color: var(--dash-accent);
}

.player-page .player-section .calendar-date.has-games {
  border-color: transparent;
  background: linear-gradient(180deg, #22c55e 0%, #15954a 100%);
  box-shadow: 0 6px 14px rgba(31, 174, 94, 0.45);
  font-weight: 800;
  color: #fff;
}

.player-cal__legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.1rem;
  margin-top: 0.9rem;
  font-size: 0.74rem;
  color: var(--dash-ink-soft);
}

.player-cal__legend span {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.player-cal__key {
  width: 12px;
  height: 12px;
  border: 1.5px solid var(--dash-accent);
  border-radius: 3px;
}

.player-cal__key--game {
  border-color: transparent;
  background: linear-gradient(180deg, #22c55e 0%, #15954a 100%);
}

@media (max-width: 400px) {
  .welcome-card {
    padding: 1.35rem 1.15rem;
  }

  .welcome-card__name strong {
    font-size: 1.6rem;
  }

  .welcome-card__script {
    font-size: 0.76rem;
  }

  .quick-filter {
    padding: 0.7rem 0.8rem 0.8rem;
  }

  .quick-filter__label {
    font-size: 0.96rem;
  }

  .invite-card__actions .invite-btn {
    padding-inline: 0.45rem;
    font-size: 0.8rem;
  }
}

/* --- The facts and the roster --- */
.invite-card__body {
  margin-top: 1rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--player-line);
}

.invite-card__roster {
  margin-top: 0.9rem;
}

.invite-card__roster h4 {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0 0 0.5rem;
  font-family: var(--font);
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--player-ink-soft);
}

@media (max-width: 560px) {
  .invites-head__count {
    margin-left: 0;
  }

  /* Stay side by side even on a narrow phone -- a smaller label buys back
     the room a 100%-wide stack used to need. */
  .invite-card__actions .invite-btn {
    padding-inline: 0.45rem;
    font-size: 0.76rem;
  }
}

/* ==========================================================================
   RSVP LANDING â€” where a one-click answer from an invitation email arrives
   ========================================================================== */
.rsvp-card {
  max-width: 520px;
  padding: 2.5rem 2.25rem 2.25rem;
  border-radius: 20px;
  text-align: center;
}

/* The disc reads before the heading does, and is the only part that differs
   between the three answers -- everything else is the same card. */
.rsvp-card__mark {
  display: grid;
  place-items: center;
  width: 76px;
  height: 76px;
  margin: 1.5rem auto 1.25rem;
  border-radius: 50%;
  color: #fff;
}

.rsvp-card--yes .rsvp-card__mark {
  background: var(--brand);
  box-shadow: 0 0 0 10px rgba(31, 174, 94, 0.14);
}

.rsvp-card--no .rsvp-card__mark {
  background: #94a3b8;
  box-shadow: 0 0 0 10px rgba(148, 163, 184, 0.16);
}

.rsvp-card--error .rsvp-card__mark {
  background: #e0a818;
  box-shadow: 0 0 0 10px rgba(224, 168, 24, 0.16);
}

.rsvp-card h1 {
  margin: 0 0 0.6rem;
  font-family: var(--font);
  font-size: clamp(1.5rem, 4vw, 1.9rem);
  font-weight: 800;
  letter-spacing: -0.02em;
}

.rsvp-card p {
  margin: 0 auto 1.75rem;
  max-width: 40ch;
  font-size: 0.95rem;
  line-height: 1.65;
}

/* Centred, and each button wide enough to be a target on a phone rather than
   two small ones squeezed onto one line. */
.rsvp-card .row-actions {
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.rsvp-card .row-actions .btn {
  min-width: 168px;
  justify-content: center;
}

/* --- The card's right-hand end: the NEW flag and the chevron --- */
.invite-card__tail {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
}

/* Opens the popup with the game's details. A button, not the whole card: the
   card already carries two buttons of its own, and a tap target wrapped around
   them would fire on the way to either. */
.invite-card__more {
  display: grid;
  place-items: center;
  flex: none;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid rgba(16, 24, 40, 0.12);
  border-radius: 50%;
  background: #fff;
  color: #64748b;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.invite-card__more:hover {
  background: #f1f5f9;
  color: #0f172a;
}

/* Not inside the My Games modal, where the popup it opens would be a
   second dialog stacked on this one. The dashboard card keeps it --
   nothing sits above that. The NEW flag beside it stays either way:
   it is the only thing marking an unanswered game in the list. */
#myGamesModal .invite-card__more {
  display: none;
}

/* --- The popup itself --- */
.invite-detail__head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1rem;
  padding-bottom: 0.9rem;
  border-bottom: 1px solid var(--player-line);
}

.invite-detail .meta-list {
  margin-bottom: 0.9rem;
}

/* The roster can run longer than the popup: it scrolls inside rather than
   pushing the card past the top and bottom of the screen. */
.invite-detail .invite-card__roster {
  max-height: 40vh;
  overflow-y: auto;
}

/* =========================================================================
   PHONE HOME -- the reference design (GAME ON eyebrow, PLAY/MEET/IMPROVE,
   header tagline, bottom tabs).

   Kept last in the file on purpose: these rules restyle .ready-card and its
   parts, which are declared further up, and a media query carries no
   specificity of its own -- from any earlier position the older rules win.
   ========================================================================= */

/* Phone only -- switched on in the block below. On desktop the wordmark
   carries the header by itself. */
.brand__tagline {
  display: none;
  font-size: 0.52rem;
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.62);
}

.brand__stack {
  display: inline-flex;
  flex-direction: column;
  gap: 0.1rem;
}

/* The bottom tab bar, phone only. */
.phone-tabs {
  display: none;
}

@media (max-width: 880px) {
  .brand__tagline {
    display: block;
    letter-spacing: 0.1em;
    word-spacing: 0.15em;
    white-space: nowrap;
  }

  /* No panel: the type sits straight on the photo, left-aligned from the same
     gutter as the rest of the page. The hero's own left scrim keeps it
     readable. */
  .ready-card {
    max-width: none;
    margin-inline: 0;
    padding: 0;
    background: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
    text-align: left;
  }

  /* "GAME ON" with the rule running off to its right. */
  .ready-card__eyebrow {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-weight: 700;
    font-size: 0.68rem;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.85);
    margin-bottom: 0.5rem;
    text-shadow: 0 2px 8px rgba(0, 0, 0, 0.55);
  }

  .ready-card__eyebrow span {
    width: 42px;
    height: 2px;
    background: var(--brand);
    flex: none;
  }

  .ready-card__title {
    margin: 0 0 0.75rem;
    font-size: clamp(2.2rem, 12vw, 3.4rem);
    letter-spacing: -0.02em;
    text-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  }

  /* The second line is the loud one: italic, a size up, and green. */
  .ready-card__title-accent {
    font-style: italic;
    font-size: 1.12em;
    background: none;
    -webkit-background-clip: border-box;
    background-clip: border-box;
    color: #22c55e;
  }

  .ready-card__subtitle {
    font-size: clamp(0.85rem, 3.6vw, 1.05rem);
    font-weight: 800;
    letter-spacing: 0.02em;
    line-height: 1.45;
    margin-bottom: 1.35rem;
    text-shadow: 0 2px 10px rgba(0, 0, 0, 0.55);
  }

  .ready-card__subtitle-accent {
    color: #22c55e;
  }

  /* Three one-word claims, split by hairlines. Sized to their own content and
     pushed left rather than spread across the width -- spread out they drifted
     under the golfer on the right of the photo. */
  .ready-card__features {
    display: flex;
    align-items: stretch;
    justify-content: flex-start;
    gap: 1.15rem;
    margin: 0 0 1.5rem;
    padding: 0;
    border-top: none;
  }

  .ready-card__feature {
    flex: none;
    gap: 0.3rem;
    color: #22c55e;
  }

  .ready-card__feature + .ready-card__feature {
    border-left: 1px solid rgba(255, 255, 255, 0.22);
    padding-left: 1.15rem;
  }

  .ready-card__feature strong {
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    text-shadow: 0 2px 8px rgba(0, 0, 0, 0.55);
  }

  /* A full-width pill, not the angled desktop button -- the clipped corners
     read as a rendering slip at phone size. Icon hard left, arrow hard right,
     label centred between them by the two auto margins. */
  .ready-card__btn {
    gap: 0.7rem;
    padding: 1.05rem 1.25rem;
    border-radius: 999px;
    font-size: 1rem;
    letter-spacing: 0.04em;
    background: linear-gradient(180deg, #22c55e 0%, #15954a 100%);
    box-shadow: 0 12px 28px rgba(31, 174, 94, 0.45);
  }

  .ready-card__btn-ico {
    margin-inline-end: auto;
  }

  .ready-card__btn .btn-arrow {
    margin-inline-start: auto;
    font-size: 1.15em;
  }

  /* Two lines now (a bold label over the caption), so it needs to stack. */
  .sports-dash__text {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    line-height: 1.35;
  }

  .sports-dash__text strong {
    font-size: 0.88rem;
    font-weight: 800;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: #fff;
  }

  /* Bottom tabs removed -- they weren't needed. */
}

/* =========================================================================
   PHONE HOME -- one dark surface, top to bottom.

   The reference design runs black with a green glow from the header all the
   way to the tab bar; the sports section was the one light panel breaking
   that. Phone only -- the desktop page keeps its light section.
   ========================================================================= */
@media (max-width: 880px) {
  /* The announcement strip: dropped on the phone home. The marquee and its
     stacked phone twin both go -- the same wording is already on the page in
     the places that matter, and it was the one bright band across an
     otherwise dark screen. Other pages and the desktop keep theirs. */
  .page-home .blue-announcement-banner {
    display: none;
  }

  /* Was #f5f7f9. The glow is the same green the hero uses, thrown from the
     top of the section so it reads as one continuous surface with the photo
     above rather than a panel sitting on top of it. A deep forest green
     rather than plain black, so the whole page reads as one green-tinted
     surface instead of a black one with a glow floating on it. */
  .page-home .divisions-section {
    background:
      radial-gradient(640px 360px at 50% -6%, rgba(31, 174, 94, 0.35), transparent 58%),
      linear-gradient(180deg, #0f3d28 0%, #0b2f1f 45%, #072018 100%);
  }

  /* The masthead panel loses its own fill for the same reason -- on a dark
     section a separate dark box just reads as a seam. */
  .page-home .sports-intro {
    background: none;
    border: none;
    box-shadow: none;
    padding-inline: 0;
  }

  .page-home .sports-intro__title {
    color: #fff;
  }

  .page-home .sports-intro__lede {
    color: rgba(255, 255, 255, 0.72);
  }

  .page-home .sports-intro__eyebrow {
    color: rgba(255, 255, 255, 0.7);
  }

  /* The tiles were white cards on a light panel; on the dark surface they go
     back to reading as photos with a dark scrim, like the design. */
  .page-home .division-ticket {
    background: #0d1411;
    border-color: rgba(255, 255, 255, 0.1);
  }

  .page-home .division-ticket .ticket__body h3 {
    color: #fff;
  }

  .page-home .division-ticket__blurb {
    color: rgba(255, 255, 255, 0.72);
  }

  /* The footer is the last thing above the tab bar, so it has to carry the
     same surface or the page ends on a pale band. */
  .page-home .site-footer {
    background: #072018;
    border-top: 1px solid rgba(255, 255, 255, 0.07);
  }
}

/* The three rules the light phone section left behind, restated at matching
   specificity so they land on the dark surface above. Each mirrors a rule in
   the max-width:880px block further up: the card scrim, the name, and the
   line under it were all tuned for a white card. */
@media (max-width: 880px) {
  /* Black under the copy, clear over the picture -- the same vertical scrim
     as before, inverted. */
  .page-home .division-ticket .ticket__body::after {
    background: linear-gradient(0deg,
        rgba(6, 10, 8, 0.97) 0%,
        rgba(6, 10, 8, 0.92) 42%,
        rgba(6, 10, 8, 0.45) 66%,
        rgba(6, 10, 8, 0) 88%);
  }

  .page-home .division-ticket .ticket__body h3 {
    color: #fff;
  }

  .page-home .division-ticket .division-ticket__blurb {
    color: rgba(255, 255, 255, 0.74);
  }
}

/* The footer's own light-theme fill is set by a :root[data-theme="light"]
   selector, which outscores anything written without one -- hence the match
   here. The phone home ends on the dark surface, light theme or not, the
   same way the hero photo above it does. */
@media (max-width: 880px) {
  :root[data-theme="light"] .page-home .site-footer,
  .page-home .site-footer {
    background: #072018;
    border-top: 1px solid rgba(255, 255, 255, 0.05);
  }

  /* The brand wordmark and the address line beside it are dark ink on the
     light theme; on this surface they need to come back to white. */
  :root[data-theme="light"] .page-home .site-footer .brand,
  :root[data-theme="light"] .page-home .site-footer .brand__text {
    color: #fff;
  }

  :root[data-theme="light"] .page-home .footer-bottom {
    color: rgba(255, 255, 255, 0.6);
  }
}

/* =========================================================================
   PHONE HOME -- the reference design's proportions.

   The parts above put the right pieces on the page; this sizes them the way
   the design does. The hero fills the screen and its headline is the biggest
   thing on it; the cards are tall photo tiles, not short strips. Last in the
   file for the usual reason: these restate rules declared further up.
   ========================================================================= */
@media (max-width: 880px) {
  /* Full screen, with the type sitting low over the photo -- the golfer needs
     the upper half and the headline reads against the grass below. */
  /* Sized to its own content with a floor, not to vh. A viewport unit here
     measures whatever box the page is in, which on a tall screen (or inside a
     preview frame) stretched the photo to several times the height of the
     type sitting on it. */
  .page-home .hero {
    height: auto;
    min-height: 320px;
    padding-top: 36px;
  }

  .page-home .hero-content {
    height: auto;
    min-height: 0;
    width: min(1200px, 100% - 2.5rem);
    padding: 0.8rem 0 0.8rem;
    gap: 0;
    justify-content: flex-end;
  }

  /* The headline is the loudest thing on the screen in the design; it was
     sized like body copy before. */
  .page-home .ready-card__title {
    font-size: clamp(2.6rem, 15vw, 4.2rem);
    margin: 0 0 0.85rem;
    line-height: 1.02;
  }

  .page-home .ready-card__subtitle {
    font-size: clamp(0.95rem, 4.2vw, 1.18rem);
    margin-bottom: 1.7rem;
  }

  .page-home .ready-card__eyebrow {
    font-size: 0.74rem;
    letter-spacing: 0.26em;
    margin-bottom: 0.7rem;
  }

  .page-home .ready-card__eyebrow span {
    width: 58px;
  }

  /* Compact spacing to fit one screen. */
  .page-home .ready-card__features {
    gap: 1rem;
    margin-bottom: 1.2rem;
  }

  .page-home .ready-card__feature + .ready-card__feature {
    padding-left: 1rem;
  }

  .page-home .ready-card__feature strong {
    font-size: 0.72rem;
  }

  .page-home .ready-card__btn {
    padding: 0.9rem 1.2rem;
    font-size: 0.95rem;
  }

  /* Compact tiles to fit one screen. */
  .page-home .division-ticket {
    aspect-ratio: 1 / 0.8;
  }

  .page-home .division-ticket .ticket__body {
    padding: 0.65rem;
  }

  .page-home .division-ticket .ticket__body h3 {
    font-size: 0.9rem;
  }

  .page-home .division-ticket .division-ticket__blurb {
    font-size: 0.68rem;
    padding-right: 1.8rem;
  }

  /* The icon badge is .ticket__stub, already pinned to the tile's top-left
     further up -- this only sizes it to the design. */
  .page-home .division-ticket .ticket__stub {
    width: 46px;
    height: 46px;
  }

  /* The circled chevron in the corner of every tile. The card is a real link
     now (see the <a> in home.blade.php), so this points at something. */
  .page-home .division-ticket .ticket__body::before {
    content: "\203A";
    position: absolute;
    right: 0.85rem;
    bottom: 0.85rem;
    z-index: 3;
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.5);
    background: rgba(6, 10, 8, 0.5);
    color: #fff;
    font-size: 1.15rem;
    line-height: 1;
    padding-bottom: 3px;
  }

  /* --- Section masthead --- */
  .page-home .sports-intro__title {
    font-size: clamp(1.6rem, 7.6vw, 2.1rem);
  }

  .page-home .sports-intro__lede {
    font-size: 0.92rem;
  }

  /* The two quiet lines to the right of the heading in the design. Sits
     opposite the title, so the masthead becomes a two-column block. */
  .page-home .sports-intro__text {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "eyebrow ."
      "title   aside"
      "lede    aside";
    column-gap: 0.75rem;
    align-items: end;
  }

  .page-home .sports-intro__eyebrow { grid-area: eyebrow; }
  .page-home .sports-intro__title   { grid-area: title; }
  .page-home .sports-intro__lede    { grid-area: lede; }

  .page-home .sports-intro__aside {
    grid-area: aside;
    align-self: end;
    text-align: right;
    font-size: 0.8rem;
    line-height: 1.35;
    color: rgba(255, 255, 255, 0.5);
    padding-bottom: 0.1rem;
  }

  /* The rule under the eyebrow is drawn by ::after on the text block, which
     the grid would otherwise place in a cell of its own. */
  .page-home .sports-intro__text::after {
    display: none;
  }

  .page-home .sports-intro__eyebrow {
    padding-bottom: 0.4rem;
    border-bottom: 3px solid var(--brand);
    justify-self: start;
  }

  /* --- Upcoming events --- */
  .page-home .sports-dash {
    padding: 0.95rem 1rem;
    gap: 0.85rem;
  }

  .page-home .sports-dash__ico {
    width: 46px;
    height: 46px;
  }

  .page-home .sports-dash__text strong {
    font-size: 0.95rem;
  }

  .page-home .sports-dash__text {
    font-size: 0.78rem;
  }
}

/* Phone only -- switched on by the grid in the block above. */
.sports-intro__aside {
  display: none;
}

/* The tile is an <a> now, so it needs the anchor colour and underline off;
   .ticket styles it in every other respect. */
.division-ticket,
.division-ticket:hover {
  text-decoration: none;
  color: inherit;
}

/* The headline grew to the design's size and started running across Sully's
   face. Anchoring the photo to its right edge slides him clear of the type
   without changing the vertical framing -- the 12% above is what keeps his
   head in shot on a short window, so that stays put. */
@media (max-width: 880px) {
  .page-home .hero::before {
    background-position: 88% 12%;
    /* Keep the natural grass green of the photo — don't tint it. The
       sports section below is the deep forest green; the photo sits
       above it as natural, bright grass. */
    filter: brightness(0.7) contrast(1.02) saturate(1.05);
  }

  /* Headline and subtitle stop short of the right edge so they never reach
     him even at the largest clamp size. */
  .page-home .ready-card__title,
  .page-home .ready-card__subtitle {
    max-width: 8.5ch;
  }

  .page-home .ready-card__subtitle {
    max-width: 20ch;
  }
}

/* Two corrections to the block above.

   1. The aside stayed hidden: the phone rule that positions it in the grid
      never turned it back on, so the blanket display:none kept winning.
   2. Sully sits low and right in the photo and `cover` on a tall, narrow
      window crops most of the width away, so shifting background-position
      barely moved him. Shrinking the headline a step is what actually keeps
      the type off his face. */
@media (max-width: 880px) {
  .page-home .sports-intro__aside {
    display: block;
  }

  .page-home .ready-card__title {
    font-size: clamp(2.3rem, 12.5vw, 3.4rem);
    max-width: 9ch;
  }
}

/* The aside squeezed the title column until "Choose your game" broke across
   two lines. It moves under the heading block instead of beside it -- the
   design's side-by-side needs more width than a phone has once the title is
   at full size. */
@media (max-width: 880px) {
  .page-home .sports-intro__text {
    display: block;
  }

  .page-home .sports-intro__aside {
    margin: 0.5rem 0 0;
    text-align: left;
    font-size: 0.78rem;
  }

  /* The eyebrow's rule was drawn by ::after on the text block before the grid
     took it out; with the grid gone it comes back. */
  .page-home .sports-intro__eyebrow {
    display: inline-block;
  }
}

/* The tile got its height from aspect-ratio, but .ticket__body kept the
   106px min-height it had as a short card and simply sat at the top, leaving
   the rest of the tile empty below the copy. Pinning the body to all four
   sides makes the photo fill the tile and the copy sit at its foot, which is
   what the design shows. */
@media (max-width: 880px) {
  .page-home .division-ticket .ticket__body {
    position: absolute;
    inset: 0;
    min-height: 0;
    justify-content: flex-end;
  }
}

/* Phone home header gets a scrim of its own so the wordmark reads over the
   photo. Green-black rather than plain black: the reference carries the same
   green through the top of the screen as the rest of the page, and a black
   scrim here left the one neutral grey band on an otherwise green surface. */
@media (max-width: 880px) {
  .page-home .site-header {
    background: linear-gradient(180deg, rgba(7, 24, 16, 0.88) 0%, rgba(7, 24, 16, 0.34) 100%);
  }
}

/* =========================================================================
   PHONE HOME -- second pass matching the reference screenshot exactly.
   Everything above sized the sections; this shrinks type and cards further
   so the whole page (hero + sports section + one row of Upcoming Events)
   sits inside one 844px screen without scrolling.
   ========================================================================= */
@media (max-width: 880px) {
  .page-home .hero {
    min-height: 260px;
    padding-top: 28px;
  }

  .page-home .hero-content {
    padding: 0.6rem 0 0.6rem;
  }

  .page-home .ready-card__eyebrow {
    font-size: 0.68rem;
    margin-bottom: 0.5rem;
  }

  .page-home .ready-card__title {
    font-size: clamp(1.9rem, 10vw, 2.6rem);
    margin: 0 0 0.5rem;
  }

  .page-home .ready-card__subtitle {
    font-size: clamp(0.78rem, 3.4vw, 0.92rem);
    margin-bottom: 1rem;
  }

  .page-home .ready-card__features {
    gap: 0.8rem;
    margin-bottom: 0.85rem;
  }

  .page-home .ready-card__feature + .ready-card__feature {
    padding-left: 0.8rem;
  }

  .page-home .ready-card__feature strong {
    font-size: 0.62rem;
  }

  .page-home .ready-card__feature .icon {
    width: 16px;
    height: 16px;
  }

  .page-home .ready-card__btn {
    padding: 0.7rem 1.1rem;
    font-size: 0.85rem;
  }

  /* --- Sports section masthead: tighter type, less air above the grid --- */
  .page-home .divisions-section {
    padding-top: 1rem;
  }

  .page-home .sports-intro {
    margin-bottom: 0.75rem;
  }

  .page-home .sports-intro__eyebrow {
    font-size: 0.68rem;
    padding-bottom: 0.3rem;
  }

  .page-home .sports-intro__title {
    font-size: clamp(1.3rem, 6vw, 1.6rem);
  }

  .page-home .sports-intro__lede {
    font-size: 0.78rem;
  }

  .page-home .sports-intro__blurb {
    display: none;
  }

  .page-home .sports-intro__aside {
    font-size: 0.7rem;
    margin-top: 0.3rem;
  }

  /* --- Smaller, tighter tiles matching the reference's compact grid --- */
  .page-home .divisions-grid.tickets-grid {
    gap: 0.6rem;
  }

  .page-home .division-ticket {
    aspect-ratio: 1 / 0.68;
    border-radius: 10px;
  }

  .page-home .division-ticket .ticket__body {
    padding: 0.5rem;
  }

  .page-home .division-ticket .ticket__body h3 {
    font-size: 0.78rem;
  }

  .page-home .division-ticket .division-ticket__blurb {
    font-size: 0.6rem;
    padding-right: 1.4rem;
  }

  .page-home .division-ticket .ticket__stub {
    width: 34px;
    height: 34px;
    top: 0.5rem;
    left: 0.5rem;
  }

  .page-home .division-ticket .ticket__stub .svg-sport {
    width: 16px;
    height: 16px;
  }

  .page-home .division-ticket .ticket__body::before {
    width: 22px;
    height: 22px;
    right: 0.5rem;
    bottom: 0.5rem;
    font-size: 0.9rem;
    padding-bottom: 2px;
  }

  /* --- Upcoming events: shorter row so it clears the fold --- */
  .page-home .sports-dash {
    margin-top: 0.75rem;
    padding: 0.7rem 0.8rem;
    gap: 0.6rem;
  }

  .page-home .sports-dash__ico {
    width: 38px;
    height: 38px;
  }

  .page-home .sports-dash__text strong {
    font-size: 0.82rem;
  }

  .page-home .sports-dash__text {
    font-size: 0.68rem;
  }
}

/* =========================================================================
   PHONE HOME -- the reference screenshot's colour.

   The passes above put the right pieces on the screen and sized them; this
   is the colour pass. The reference runs a near-black forest green from
   under the hero photo to the foot of the page, uses one bright green for
   every accent on it, gives the registration strip and the upcoming-events
   row the same bordered panel, and takes each sport badge's colour from the
   sport itself -- golf is green and the fun run is blue there, where the old
   chip palette had them blue and cyan.

   Phone only: every rule is inside max-width:880px and scoped to .page-home,
   so the desktop page keeps the colours it already has. Last in the file for
   the reason the blocks above give -- these restate rules declared earlier
   and a media query carries no specificity of its own.
   ========================================================================= */

/* The reference's palette in one place, so the rules below read as names
   rather than as a run of hex codes. On :root because custom properties
   resolve where they are used and every use below is already phone-scoped. */
:root {
  /* The surface, top to bottom: deep forest green under the hero, fading to
     near-black at the foot of the page. */
  --ph-surface-1: #091810;
  --ph-surface-2: #07130d;
  --ph-surface-3: #060f0a;
  /* The single bright accent: the READY? headline, the section rules, and the
     two calls to action. */
  --ph-accent: #2fd75f;
  --ph-accent-soft: #4ade80;
  /* The two panels that sit on the surface: a shade above it inside a green
     hairline, with a soft green bloom around them. */
  --ph-panel: #0b1e14;
  --ph-panel-line: rgba(47, 215, 95, 0.5);
}

/* Phone only -- switched on in the block below. The desktop page has no
   equivalent strip; its announcement marquee is parked (see home.blade.php). */
.reg-banner {
  display: none;
}

@media (max-width: 880px) {
  /* --- The surface --- */
  /* Was a mid forest green under a strong glow, which sat several steps
     lighter than the reference. Same two layers, both taken down: the glow
     is now a hint of green under the photo rather than a lamp on the
     section. */
  .page-home .divisions-section {
    background:
      radial-gradient(520px 300px at 50% -8%, rgba(47, 215, 95, 0.16), transparent 62%),
      linear-gradient(180deg,
        var(--ph-surface-1) 0%,
        var(--ph-surface-2) 46%,
        var(--ph-surface-3) 100%);
  }

  /* The page ends on the darkest step of the same surface. The light theme's
     own footer fill is set by a :root[data-theme="light"] selector, which
     outscores anything written without one -- hence the pair. */
  :root[data-theme="light"] .page-home .site-footer,
  .page-home .site-footer {
    background: var(--ph-surface-3);
    border-top: 1px solid rgba(255, 255, 255, 0.05);
  }

  /* --- The accent --- */
  /* Every green that is not a surface or a button: the second headline line,
     the word under it, the three claims beside it, and the "View all" link at
     the foot of the sports section. */
  .page-home .ready-card__title-accent,
  .page-home .ready-card__subtitle-accent,
  .page-home .ready-card__feature,
  .page-home .sports-dash__cta {
    color: var(--ph-accent);
  }

  /* The two rules drawn as bars rather than text: the one running off the
     right of GAME ON, and the one under SPORTS SECTION. */
  .page-home .ready-card__eyebrow span {
    background: var(--ph-accent);
  }

  .page-home .sports-intro__eyebrow {
    border-bottom-color: var(--ph-accent);
  }

  /* The button is the one green that is deliberately below the accent -- on
     the reference it reads as a surface you press, not as a highlight. */
  .page-home .ready-card__btn {
    background: linear-gradient(180deg, #23b955 0%, #0f8c3c 100%);
    box-shadow: 0 10px 26px rgba(15, 140, 60, 0.4);
  }

  /* --- Clearance under the header --- */
  /* The header sits on top of the hero, and at 28px of padding the GAME ON
     eyebrow was running underneath it. 68px clears the 48px header with the
     gap the reference leaves above the eyebrow. */
  .page-home .hero {
    padding-top: 68px;
  }

  /* --- The sport badges --- */
  /* The reference colours these by sport rather than from the old chip
     palette --sport-color carries: golf takes the green of a fairway and the
     fun run the blue the cyan used to hold. Keyed off data-sport on the tile
     (see home.blade.php) so the inline --sport-color the desktop card still
     reads is left alone. */
  .page-home .division-ticket[data-sport="golf"] .ticket__stub {
    background: #16a34a;
  }

  .page-home .division-ticket[data-sport="shooting"] .ticket__stub {
    background: #ef4444;
  }

  .page-home .division-ticket[data-sport="basketball"] .ticket__stub {
    background: #f5a524;
  }

  .page-home .division-ticket[data-sport="funrun"] .ticket__stub {
    background: #2f86e8;
  }

  /* The tile itself is the surface a step up, not the near-black it was. */
  .page-home .division-ticket {
    background: #08150e;
    border-color: rgba(255, 255, 255, 0.06);
  }

  /* --- The two panels --- */
  /* The registration strip under the hero and the upcoming-events row at the
     foot of the section are the same object in the reference: a panel a shade
     above the surface, inside a green hairline, with a soft bloom around it. */
  .page-home .reg-banner,
  .page-home .sports-dash {
    background: var(--ph-panel);
    border: 1px solid var(--ph-panel-line);
    box-shadow: 0 0 22px rgba(47, 215, 95, 0.12);
  }

  .page-home .reg-banner {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    width: min(1420px, 100vw - 2rem);
    margin: 0.9rem auto 0;
    padding: 0.7rem 0.75rem;
    border-radius: 12px;
    color: #fff;
    text-decoration: none;
  }

  /* White disc, green megaphone -- the one light shape on the screen, which
     is what makes the strip the first thing the eye lands on below the hero.
     The icon is drawn with stroke:currentColor, so the colour here is what
     draws it. */
  .page-home .reg-banner__ico {
    display: grid;
    place-items: center;
    flex: none;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: #fff;
    color: #12873c;
  }

  .page-home .reg-banner__text {
    display: flex;
    flex: 1;
    min-width: 0;
    flex-direction: column;
    gap: 0.08rem;
    line-height: 1.3;
  }

  .page-home .reg-banner__text strong {
    font-size: 0.8rem;
    font-weight: 800;
    letter-spacing: 0.02em;
    text-transform: uppercase;
  }

  .page-home .reg-banner__text span {
    font-size: 0.64rem;
    color: rgba(255, 255, 255, 0.72);
  }

  /* The one filled pill on the page that is not the hero's button: lighter
     green with dark ink, so it reads as the brighter of the two. */
  .page-home .reg-banner__cta {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    flex: none;
    padding: 0.45rem 0.7rem;
    border-radius: 999px;
    background: var(--ph-accent-soft);
    color: #06210f;
    font-size: 0.66rem;
    font-weight: 800;
    white-space: nowrap;
  }

  /* The calendar disc is the mirror of the megaphone one: green fill, white
     drawing. */
  .page-home .sports-dash__ico {
    background: #1b8f47;
    color: #fff;
  }
}

/* Three corrections to the block above.

   1. The registration strip sits between .hero and .divisions-section, which
      are the two elements painting the surface -- so it was standing on the
      body's own fill, a pale band across an otherwise dark screen (and paler
      still under the light theme, whose --ink is a light grey). The page
      itself carries the surface here so there is nothing left to show
      through. Scoped to the phone home, so no other page moves.
   2. The strip's headline wrapped: the disc and the pill either side of it
      left the middle column about 30px short of "Registration is open!" on a
      360px screen. Both come in a size, which is enough to hold it on one
      line down to 360px.
   3. The two quiet lines go back beside the heading, where the reference has
      them. They were moved underneath when the title was a size larger and
      the two columns fought; at the size the pass above leaves it, "Choose
      your game" and the aside sit side by side with room to spare. */
@media (max-width: 880px) {
  :root[data-theme="light"] body.page-home,
  body.page-home {
    background: var(--ph-surface-1);
  }

  .page-home .reg-banner {
    gap: 0.6rem;
    padding: 0.65rem 0.7rem;
  }

  .page-home .reg-banner__ico {
    width: 34px;
    height: 34px;
  }

  .page-home .reg-banner__text strong {
    font-size: 0.76rem;
    white-space: nowrap;
  }

  .page-home .reg-banner__cta {
    padding: 0.4rem 0.6rem;
    font-size: 0.62rem;
  }

  /* Back to the two-column masthead the earlier pass set up: heading and lede
     down the left, the aside held against the right edge. Every property here
     mirrors one that pass declared -- the block that stacked them only
     switched the grid off and re-aligned the aside, so this switches it back
     on and re-aligns it the other way. */
  .page-home .sports-intro__text {
    display: grid;
  }

  .page-home .sports-intro__aside {
    margin: 0;
    text-align: right;
  }

  .page-home .sports-intro__eyebrow {
    display: block;
  }
}

/* Two last steps toward the reference's own light level.

   1. The glow under the hero was still reading as a lamp pointed at the top
      of the sports section; on the reference it is a hint that the photo
      above carries on behind the section, and the surface under SPORTS
      SECTION is nearly black.
   2. The hero photo was a stop darker than the reference, whose grass is lit
      and whose trees still have their colour. Brightness comes up and the
      saturation with it; the scrim down the left of the hero is what keeps
      the headline readable, so the type does not lose contrast for it. */
@media (max-width: 880px) {
  .page-home .divisions-section {
    background:
      radial-gradient(480px 260px at 50% -10%, rgba(47, 215, 95, 0.055), transparent 60%),
      linear-gradient(180deg,
        rgba(9, 24, 16, 0.88) 0%,
        rgba(7, 19, 13, 0.84) 50%,
        rgba(6, 15, 10, 0.93) 100%),
      url('../images/bg.webp?v=2') center / cover no-repeat,
      var(--ph-surface-3);
  }

  .page-home .hero::before {
    filter: brightness(0.82) contrast(1.02) saturate(1.14);
  }

  .page-home .reg-banner__text span {
    font-size: 0.6rem;
  }
}

/* One last correction to the colour pass above: the palette was still a step
   and a half lighter than the reference, and greener with it. Sampling the
   rendered page against the design put the surface under the sports section
   at rgb(10,36,22) where the design has about rgb(9,24,17) -- the same hue,
   two thirds of the light. Every stop comes down by that ratio, the glow
   under the hero drops with them, and the header's scrim turns from black to
   the same green-black so the colour runs the whole page rather than
   starting below the hero. Values are edited in place above rather than
   restated here; this note is what they were changed from. */

/* The golf tile's photo, phone only. The reference shows a ball on the tee
   with the club behind it on lit, vivid grass -- which is hero_golf1, already
   in the project (served as webp; the .jpg master is 3648x5472 and 1.2MB). The tile was running golf.webp, a player mid-swing
   at golden hour, which reads amber-brown at tile size and is most of why the
   grid looked warmer than the design. Phone only, like the rest of this pass:
   the desktop card keeps the photo it has. */
@media (max-width: 880px) {
  .page-home .division-ticket .ticket__photo.division-golf {
    background-image: url('../images/hero_golf1.webp');
  }
}

/* The tile scrim, lightened. It ran 0.97 opaque at the foot and was still
   0.92 at two fifths of the way up -- on a 118px tile that is most of the
   picture painted out, which is the other half of why the grid read darker
   and greyer than the design. The reference darkens the copy's corner enough
   to read and leaves the grass visible through it, so the top of each stop
   comes down and the fade starts lower. */
@media (max-width: 880px) {
  .page-home .division-ticket .ticket__body::after {
    background: linear-gradient(0deg,
        rgba(5, 14, 9, 0.88) 0%,
        rgba(5, 14, 9, 0.72) 36%,
        rgba(5, 14, 9, 0.26) 62%,
        rgba(5, 14, 9, 0) 84%);
  }
}

/* =========================================================================
   HOME -- the reference's surface at every width, not just on the phone.

   Everything above was scoped to max-width:880px, so on a desktop window the
   lower half of the page never moved: the sports section was still #f5f7f9
   and the footer still the light theme's grey. This lifts the same three
   surface colours out of the phone block and onto the page at any width, so
   the colour runs top to bottom on a laptop the same way it does on a phone.

   Only the surface moves. The desktop's own layout -- the full-height hero,
   all eight sport tickets with their schedule and venue rows, the footer's
   link columns -- is untouched, and so is the phone sizing above.

   The fill is translucent over bg.webp -- the collage of players the sign-in
   page runs on (.centered-page uses the same photo behind the same scrim).
   Flattening it to an opaque gradient is what lost the picture; the green
   now tints the collage instead of covering it.

   The hero is deliberately not part of this. It went to every width once,
   with the desktop running the phone's portrait photograph as a masked panel,
   and that was the piece that read as wrong on a wide screen; it has been
   taken back out and the desktop hero is the full-bleed photo it always was.
   The colour below it is what stays.

   The paired theme selectors are not decoration: the fills being replaced are
   written as :root[data-theme="…"] .thing, which outscores a plain
   .page-home .thing, so a rule without them loses no matter where it sits.
   ========================================================================= */

:root[data-theme="dark"] body.page-home,
:root[data-theme="light"] body.page-home,
body.page-home {
  background: var(--ph-surface-1);
}

:root[data-theme="dark"] .page-home .divisions-section,
:root[data-theme="light"] .page-home .divisions-section,
.page-home .divisions-section {
  background:
    radial-gradient(900px 420px at 50% -8%, rgba(47, 215, 95, 0.1), transparent 60%),
    linear-gradient(180deg,
      rgba(9, 24, 16, 0.86) 0%,
      rgba(7, 19, 13, 0.80) 50%,
      rgba(6, 15, 10, 0.92) 100%),
    url('../images/bg.webp?v=2') center / cover no-repeat,
    var(--ph-surface-3);
}

/* The glow above is sized for a desktop window; the phone keeps the tighter
   one the pass further up tuned for a 390px screen. */
@media (max-width: 880px) {
  :root[data-theme="dark"] .page-home .divisions-section,
  :root[data-theme="light"] .page-home .divisions-section,
  .page-home .divisions-section {
    background:
      radial-gradient(480px 260px at 50% -10%, rgba(47, 215, 95, 0.055), transparent 60%),
      linear-gradient(180deg,
        rgba(9, 24, 16, 0.88) 0%,
        rgba(7, 19, 13, 0.84) 50%,
        rgba(6, 15, 10, 0.93) 100%),
      url('../images/bg.webp?v=2') center / cover no-repeat,
      var(--ph-surface-3);
  }
}

/* The page ends on the darkest step of the same surface, in both themes. */
:root[data-theme="dark"] .page-home .site-footer,
:root[data-theme="light"] .page-home .site-footer,
.page-home .site-footer {
  background: var(--ph-surface-3);
  border-top: 1px solid rgba(255, 255, 255, 0.05);
  color: rgba(255, 255, 255, 0.72);
}

/* The footer's ink was written for the light theme's grey panel -- dark type
   on a light fill. On the green it has to come back to white, the same way
   the dark theme's footer already reads. */
:root[data-theme="light"] .page-home .site-footer .brand,
:root[data-theme="light"] .page-home .site-footer .brand__text,
.page-home .site-footer .brand,
.page-home .site-footer .brand__text {
  color: #fff;
}

:root[data-theme="light"] .page-home .site-footer h4,
.page-home .site-footer h4 {
  color: #fff;
}

:root[data-theme="light"] .page-home .site-footer a,
.page-home .site-footer a {
  color: rgba(255, 255, 255, 0.74);
}

:root[data-theme="light"] .page-home .site-footer a:hover,
.page-home .site-footer a:hover {
  color: var(--ph-accent);
}

:root[data-theme="light"] .page-home .footer-bottom,
.page-home .footer-bottom {
  color: rgba(255, 255, 255, 0.6);
  border-top-color: rgba(255, 255, 255, 0.1);
}

/* =========================================================================
   HOME HERO -- the design's own photograph, on the phone.

   The reference's hero is Sully on a lit, midday course: dark teal sky, a
   wall of green trees, a vivid striped fairway. hero_golf_cartoon.png is the
   same character at golden hour -- amber sky, sparse trees -- and no filter
   reaches from one to the other (pushing it green enough turns Sully green
   too), so on the phone the picture itself changes.

   Served as webp: the source PNG is 3.3MB, a lot to hand a phone for a
   background. The same frame at 84% webp is 136KB, and the .png stays beside
   it as the master.

   Phone only, and the reason is the shape. The photograph is portrait
   (1200x1310) against a hero barely wider than it is tall, so cover scales to
   the width: the whole picture is across and the crop is top and bottom. The
   desktop hero is twice as wide as it is tall, where cover throws away half
   the frame and fills the screen with Sully's head -- so it keeps
   hero_golf_cartoon.png, full bleed, framed the way it already was.
   ========================================================================= */
@media (max-width: 880px) {
  .page-home .hero::before {
    background-image: url('../images/hero_golf_green.webp');
    background-repeat: no-repeat;
    background-size: cover;
    /* Only the vertical value does anything here -- there is no horizontal
       overflow to slide. This stands Sully on the grass with his shoes just
       clear of the button, the way the design has him. Lower and the button
       crosses his shins; higher and his cap runs under the header. */
    background-position: 50% 55%;
    /* The old grade was built to lift a dark, hazy photo. This one arrives
       lit and saturated, so it only needs taking down enough for white type
       to hold over it -- the scrim down the left of the hero does the rest. */
    filter: brightness(0.92) contrast(1.02) saturate(1.06);
  }
}

/* Three changes asked for after the design landed on a real phone.

   1. The button sits lower on the grass. The gap above it was 0.85rem, which
      put it closer to PLAY / MEET / IMPROVE than to the foot of the photo;
      this drops it toward the bottom edge the way the design has it.
   2. The registration strip is parked (see home.blade.php) -- there are no
      sign-ups to announce. Its rules stay here for when there are.
   3. The footer comes off the phone home. It had collapsed to a wordmark and
      an address on one dark bar, which read as a leftover strip under the
      page rather than a footer; the same links are on every other page and
      the desktop home keeps its full footer. Phone home only. */
@media (max-width: 880px) {
  .page-home .ready-card__features {
    margin-bottom: 1.45rem;
  }

  .page-home .site-footer {
    display: none;
  }
}

/* The sports section's masthead comes down to its eyebrow on the phone.
   "Choose your game", the line under it and the two quiet lines opposite were
   three pieces of copy standing between the hero and the first card, and the
   cards name their own sports. SPORTS SECTION and its rule stay as the label.
   The section lifts toward the hero by what the copy was occupying, plus a
   little off the padding above it. Phone only -- the desktop masthead keeps
   its panel, its photo and all three lines. */
@media (max-width: 880px) {
  .page-home .sports-intro__title,
  .page-home .sports-intro__lede,
  .page-home .sports-intro__aside {
    display: none;
  }

  .page-home .divisions-section {
    padding-top: 0.75rem;
  }

  .page-home .sports-intro {
    margin-bottom: 0.6rem;
  }
}

/* The upcoming-events row, brought the rest of the way to the design. Three
   things were off: the icon was a disc where the design draws a rounded
   square, "View all" was being uppercased and letter-spaced into a label when
   the design has it as plain words, and the rule between the sentence and the
   call to action is not in the design at all. The badge also carries a slight
   top-down lift rather than sitting flat. Phone only, like the row itself. */
@media (max-width: 880px) {
  .page-home .sports-dash__ico {
    border-radius: 14px;
    background: linear-gradient(160deg, #23b658 0%, #12833f 100%);
  }

  .page-home .sports-dash__cta {
    border-left: none;
    padding-left: 0;
    text-transform: none;
    letter-spacing: normal;
    font-size: 0.8rem;
    font-weight: 700;
    gap: 0.35rem;
  }
}

/* The upcoming-events row's colour, sampled off the design rather than judged
   by eye. Every value here is a measurement:

     fill        rgb(2,19,21)    #021315   (1072 of the sampled pixels)
     icon top    rgb(2,177,86)   #02b156
     icon body   rgb(4,127,57)   #047f39
     "View All"  rgb(0,229,118)  #00e576   (brightest green in the frame)

   The fill was #0b1e14 -- rgb(11,30,20), half a step lighter and green where
   the design is a near-black that leans cool, blue just ahead of green. The
   badge was mixed with red in it (#23b658) where the design's is close to
   pure, and the call to action was the page's accent rather than this
   brighter one.

   The badge also throws a glow the flat one did not. Ten pixels out from it
   the design reads rgb(1,55,37) against that rgb(2,19,21) fill, which solves
   to rgba(0,200,100,0.2) over the fill -- the same alpha from the green and
   the blue channel independently, so it is a glow and not a lighter panel.

   The border is left alone: the crop stops inside the card on all four sides,
   so there was nothing of it to measure. */
@media (max-width: 880px) {
  .page-home .sports-dash {
    background: #021315;
  }

  .page-home .sports-dash__ico {
    background: linear-gradient(180deg, #02b156 0%, #017f3c 100%);
    box-shadow: 0 0 16px rgba(0, 200, 100, 0.55);
  }

  .page-home .sports-dash__cta {
    color: #00e576;
  }
}

/* The hero's button comes down a size and the gap under it closes up, to buy
   the sports section room to move up the screen. The button was 0.7rem/1.1rem
   of padding at 0.85rem of type; the hero content block was carrying 0.6rem
   of padding under it on top of that. Both are trimmed -- the button still
   clears the 44px tap target at 0.58rem of padding on a 0.78rem label. */
@media (max-width: 880px) {
  .page-home .ready-card__btn {
    padding: 0.58rem 1rem;
    font-size: 0.78rem;
  }

  .page-home .hero-content {
    padding: 0.6rem 0 0.15rem;
  }

  .page-home .hero {
    min-height: 0;
  }
}

/* All eight sports on the phone, not four. The tile comes down from a card
   with a blurb to a name over a photo -- 174x73 rather than 174x118 -- so
   four rows of two fit where two rows used to, and the badge, the name and
   the chevron shrink with it. The hero gives up a little more of its padding
   above to help; 56px still clears the 48px header.

   Measured: the page ends at 808px with all eight, against 745px with four. */
@media (max-width: 880px) {
  .page-home .hero {
    padding-top: 56px;
  }

  .page-home .division-ticket {
    aspect-ratio: 1 / 0.42;
  }

  /* The name carries the tile on its own at this height; there is no room for
     a line of copy under it. */
  .page-home .division-ticket .division-ticket__blurb,
  .page-home .division-ticket .division-ticket__blurb--short {
    display: none;
  }

  .page-home .division-ticket .ticket__body {
    padding: 0.4rem;
  }

  .page-home .division-ticket .ticket__body h3 {
    font-size: 0.72rem;
  }

  .page-home .division-ticket .ticket__stub {
    width: 26px;
    height: 26px;
    top: 0.35rem;
    left: 0.35rem;
  }

  .page-home .division-ticket .ticket__stub .svg-sport {
    width: 13px;
    height: 13px;
  }

  .page-home .division-ticket .ticket__body::before {
    width: 18px;
    height: 18px;
    right: 0.35rem;
    bottom: 0.35rem;
    font-size: 0.72rem;
    padding-bottom: 1px;
  }
}

/* The chevron was dropped when the grid held all eight sports and the tile
   was down to 174x73 -- too short for badge, name and arrow together. Back to
   four sports the tile has room again (see the sizing pass further down), so
   the chevron comes back on; only the spacing it freed up stays. */
@media (max-width: 880px) {
  .page-home .divisions-section {
    padding-top: 0.4rem;
  }

  .page-home .sports-intro {
    margin-bottom: 0.4rem;
  }

  .page-home .sports-intro__eyebrow {
    padding-bottom: 0.25rem;
  }
}

/* The 20px of dead photo under the button was the hero's own padding-bottom,
   not the centring it looked like: the phone hero computes to
   padding 56px 0 20px, so the content stopped 20px short of the bottom edge
   however it was aligned. Dropping it puts the button on the grass and lifts
   the sports section by the same 20px. flex-end goes with it so the block
   still sits at the foot of the photo now that there is no padding holding
   it there; padding-top keeps the eyebrow clear of the header. */
@media (max-width: 880px) {
  .page-home .hero {
    align-items: flex-end;
    padding-bottom: 0;
  }
}

/* =========================================================================
   PHONE HOME -- back to four sports, and the tile that fits them.

   Golf, Pickleball, Shooting Range and Basketball are the four shown now
   ('phone' => true in home.blade.php); the tile below is sized for four
   again rather than the eight-in-two-columns squeeze a few passes back. With
   half as many tiles the grid has the room to grow: the blurb comes back,
   the badge and chevron return to their larger size, and the whole card
   reads like a tile again rather than a strip with a name on it.

   Last in the file, restating the 174x73 rules further up for the usual
   reason: a later media query with equal specificity wins outright, and this
   one needs to.
   ========================================================================= */
@media (max-width: 880px) {
  .page-home .division-ticket {
    aspect-ratio: 1 / 0.68;
  }

  .page-home .division-ticket .division-ticket__blurb,
  .page-home .division-ticket .division-ticket__blurb--short {
    display: block;
    font-size: 0.62rem;
    padding-right: 1.4rem;
  }

  /* Only one blurb shows at a time -- .division-ticket__blurb--short is the
     phone-length copy; the long .division-ticket__blurb is still the one
     .division-ticket__meta and this pair hide together further up. */
  .page-home .division-ticket .division-ticket__blurb:not(.division-ticket__blurb--short) {
    display: none;
  }

  .page-home .division-ticket .ticket__body h3 {
    font-size: 0.8rem;
  }

  .page-home .division-ticket .ticket__stub {
    width: 34px;
    height: 34px;
    top: 0.5rem;
    left: 0.5rem;
  }

  .page-home .division-ticket .ticket__stub .svg-sport {
    width: 16px;
    height: 16px;
  }

  .page-home .division-ticket .ticket__body::before {
    width: 22px;
    height: 22px;
    right: 0.5rem;
    bottom: 0.5rem;
    font-size: 0.9rem;
    padding-bottom: 2px;
  }
}

/* =========================================================================
   PHONE HOME -- the bottom tab bar, back on the page.

   .phone-tabs already existed (declared display:none, further up, so
   nothing renders on desktop) from an earlier pass that was never finished
   on phone; this is that pass, styled with the tokens the rest of this file
   now uses (--ph-panel, --ph-accent) rather than the flat dark panel it
   started with, so it reads as the same object as the registration strip and
   the upcoming-events row.

   Fixed to the viewport, so the page needs bottom padding equal to the bar's
   own height or the last card sits behind it -- added to .page-home below,
   with the iOS home-indicator safe area folded in.
   ========================================================================= */
@media (max-width: 880px) {
  /* Full width and flush to the bottom edge now, rather than a pill floating
     with a gap on all four sides -- only the top corners are rounded, and
     the safe-area inset is padding inside the bar (so its own background
     still reaches the true bottom of the screen) instead of a gap under it. */
  /* width:100vw + left:0 rather than left:0;right:0;width:100% -- on a page
     tall enough to need a scroll (every one this bar appears on), a classic
     scrollbar takes up part of the browser's own width, and right:0/100%
     both resolve against the space that leaves rather than the true edge of
     the screen -- the bar stopped short of it by however wide the scrollbar
     was, which is exactly what showed up testing this in a desktop browser
     window rather than on an actual phone (no reserved scrollbar space
     there). 100vw is measured including the scrollbar, so it reaches the
     real edge either way. */
  .phone-tabs {
    position: fixed;
    left: 0;
    bottom: 0;
    z-index: 60;
    display: flex;
    width: 100vw;
    gap: 0.15rem;
    /* Cut down from 0.5rem: at the icon+label size these tabs run, that much
       air top and bottom left the card visibly taller than its own content --
       this sizes the card to the tabs rather than the other way around. */
    padding: 0.3rem 0.3rem calc(0.3rem + env(safe-area-inset-bottom, 0px));
    border-radius: 16px 16px 0 0;
    background: var(--ph-panel);
    border: 1px solid var(--ph-panel-line);
    border-bottom: none;
    /* The glow takes the accent rather than a fixed green, so the player
       pages' blue bar is not ringed in the public pages' colour. */
    box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.35),
      0 0 22px color-mix(in srgb, var(--ph-accent) 14%, transparent);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
  }

  .phone-tabs__item {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.2rem;
    padding: 0.2rem 0.1rem 0.15rem;
    border-radius: 10px;
    text-decoration: none;
    color: rgba(255, 255, 255, 0.6);
    font-size: 0.64rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    /* Every tab so far has been an <a>; Log out on the dashboard is a
       <button> (it ends the session rather than going anywhere), and
       without these a browser's own button chrome -- its border, background
       and font -- showed through instead of matching its neighbours. */
    border: none;
    background: none;
    font-family: inherit;
    cursor: pointer;
  }

  /* The tab you're on: just the accent colour on the icon and label. The
     underline bar this carried is out -- asked for a shorter bar, and that
     bar plus the gap above it was what the other tabs' extra stretched
     height (align-items:stretch matches every tab to the tallest one) came
     from in the first place. */
  .phone-tabs__item.is-current {
    color: var(--ph-accent);
  }

  /* Clears the fixed bar so the last card (Upcoming Events) is not left
     sitting behind it. */
  .page-home {
    padding-bottom: calc(4.6rem + env(safe-area-inset-bottom, 0px));
  }
}

/* The masthead panel tried above (a bordered green card with the tennis
   photo behind SPORTS SECTION) is out -- asked for plain again, no card, just
   the eyebrow line the way it was before that pass. The pre-existing phone
   rules further up (.sports-intro background:none, .sports-intro__photo and
   ::after display:none) take back over now that nothing here overrides them. */


/* =========================================================================
   ALL-SPORTS PAGE -- the wide ticket, kept wide at every width.

   .division-ticket already gives the home page's showcase tiles a full-
   opacity photo, a readable scrim and a few other touches over the base
   .ticket look -- but it also carries the home page's own phone-only
   compacting (.division-ticket at max-width:700px turns into the small
   two-across tile with the blurb hidden), which is wrong here: this page's
   whole point is the wide ticket, one to a row, at any width. Rather than
   fight that cascade property by property, .sport-ticket repeats just the
   handful of .division-ticket rules this page wants, under its own class,
   so nothing elsewhere can compact it later by accident.
   ========================================================================= */
.sport-ticket::before,
.sport-ticket::after {
  display: none;
}

.sport-ticket .ticket__stub {
  border-right: none;
}

.sport-ticket {
  box-shadow: none;
}

/* Sized to the longest name in the set ("SHOOTING RANGE"), not the shortest.
   writing-mode:vertical-rl means the label's own text LENGTH is what sets its
   height, and .ticket is a two-column grid with no explicit height -- when
   the stub's vertical text needed more room than the card's 100px, the grid
   row grew to fit it and every ticket for a long-named sport grew with it
   (measured: Shooting Range at 134px against Golf's 100px). Small enough here
   that even the longest name fits inside the 100px every ticket is meant to
   share. */
.sport-ticket .ticket__serial {
  font-family: var(--font);
  font-size: 0.38rem;
  font-weight: 800;
  letter-spacing: 0.05em;
}

.sport-ticket .ticket__body h3 {
  font-style: normal;
  font-weight: 800;
}

.sport-ticket .ticket__photo,
.sport-ticket:hover .ticket__photo {
  opacity: 1;
}

.sport-ticket .ticket__body::after {
  background: linear-gradient(100deg,
      rgba(9, 12, 17, 0.95) 0%,
      rgba(9, 12, 17, 0.9) 34%,
      rgba(9, 12, 17, 0.46) 60%,
      rgba(9, 12, 17, 0.06) 100%);
}

.sport-ticket .division-ticket__blurb {
  color: #d7dee7;
}

/* A size down from the base .ticket -- asked to shrink the cards once the
   masthead above them was gone and they were carrying the whole page, then
   asked again after this first pass still read as close to full size (350x132
   measured on a 390px phone). Every value below is the same property
   .ticket/.ticket__stub/.ticket__body/etc. already set, just smaller, so the
   card keeps its proportions rather than just losing padding. */
.sport-ticket {
  --stub: 46px;
  min-height: 100px;
}

.sport-ticket .ticket__stub {
  padding: 0.4rem 0.25rem;
  gap: 0.25rem;
}

.sport-ticket .ticket__stub .svg-sport {
  width: 16px;
  height: 16px;
}

.sport-ticket .ticket__stub .emoji-fallback {
  font-size: 0.9rem;
}

.sport-ticket .ticket__body {
  padding: 0.6rem 0.7rem;
}

.sport-ticket .ticket__body h3 {
  font-size: 0.76rem;
}

.sport-ticket .division-ticket__blurb {
  font-size: 0.64rem;
  line-height: 1.3;
}

.sport-ticket .ticket__meta {
  margin-top: 0.3rem;
  gap: 0.15rem;
}

.sport-ticket .ticket__meta div {
  font-size: 0.58rem;
}

.sport-ticket .ticket__meta dt {
  min-width: 52px;
  font-size: 0.46rem;
}

/* Fixed tab bar needs the page to reserve its own height, same as the home
   page (see .page-home below). The footer comes off for the same reason it
   did there: it's a light-grey, non-photo-backed fill sitting under a page
   that's now all green, and between it and the fixed tab bar's own reserved
   space, the tickets ended a long way above the bottom of the screen with a
   flat, mismatched band showing beneath them. Every other page keeps its
   footer -- this is the second page (after home) built for the phone tab
   bar, so it drops the footer the same way. */
@media (max-width: 880px) {
  .page-sports-list {
    padding-bottom: calc(4.6rem + env(safe-area-inset-bottom, 0px));
  }

  .page-sports-list .site-footer {
    display: none;
  }
}

/* The sepia photo collage is out -- this page runs on the same near-black
   forest green as the home page now, the same tokens (--ph-surface-1/2/3,
   --ph-accent) from that colour pass, so the two feel like one app rather
   than a green screen and a brown one a tap apart. Both classes together, so
   About/Contact/Terms (.page-sports-bg alone) keep their photo backdrop. The
   masthead this page had is gone too, so there's no heading left for the old
   light-theme contrast fix here to do anything to. */
:root[data-theme="light"] .page-sports-bg.page-sports-list,
:root[data-theme="dark"] .page-sports-bg.page-sports-list,
.page-sports-bg.page-sports-list {
  background:
    radial-gradient(900px 420px at 50% -8%, rgba(47, 215, 95, 0.1), transparent 60%),
    linear-gradient(180deg, var(--ph-surface-1) 0%, var(--ph-surface-2) 40%, var(--ph-surface-3) 100%);
}

/* One correction to the block above: 0 padding-bottom put the button flush
   with the very bottom edge of the hero photo, which was fine for the old
   photo's empty grass there but this one has Sully's shoe landing in that
   exact spot -- the button's top edge was touching it. A little padding
   comes back, just enough to clear the shoe; nowhere near the original 20px
   this page's own history removed for being dead space. */
@media (max-width: 880px) {
  .page-home .hero {
    padding-bottom: 14px;
  }
}

/* main.wrap's own padding-top:120px !important (a site-wide guard so every
   content page clears the absolutely-positioned header regardless of its own
   inline padding) was winning over this page's inline style outright --
   !important beats a plain inline declaration no matter the value, so the
   "lift the tickets" pass further up never actually took effect: the real
   top padding stayed 120px throughout. Overridden here at higher specificity
   (element + two classes, still !important) rather than lowering the guard
   itself, which every other page still needs. */
.page-sports-list main.wrap {
  padding: 68px 0 20px !important;
}

/* Same bug as the CHOOSE YOUR GAME heading fix, one component over: the
   header's wordmark and its LOGIN link assume a plain light page in light
   theme (dark ink, and the wordmark's shadow stripped since a dark shadow on
   dark ink was pointless there) -- correct for most content pages, wrong for
   this one, whose header sits on a dark photo in both themes the same way
   home's does. Mirrors home's own fix for the wordmark
   (:root[data-theme="light"] .page-home .site-header .brand { color:#fff })
   plus restoring the shadow that rule strips, and adds the one home never
   needed: LOGIN, which home's header never shows. */
:root[data-theme="light"] body.page-sports-list .site-header .brand {
  color: #fff;
  text-shadow: 0 1px 3px rgba(6, 8, 12, 0.55);
}

:root[data-theme="light"] body.page-sports-list .site-header .nav a:not(.btn) {
  color: #fff;
}

/* =========================================================================
   ADMIN CALENDAR -- the month on Overview, a white card beside Event
   Overview in the same palette: the month's days in pale sage boxes,
   Sundays unboxed with the date in red, today boxed in the dark green,
   a day with a game filled with that game's colour, and the days either
   side of the month greyed in rather than left blank.

   Scoped to #console throughout: .calendar is shared with the player
   dashboard (dashboard.js's own, separate renderPlayerCalendar()), which
   keeps the site's light/dark toggle -- this is a fixed palette, the same
   way the stat cards beside it are.
   ========================================================================= */
#console .calendar {
  padding: 1.1rem 1.2rem 1.2rem;
  border: 1px solid #e8e6e0;
  border-radius: 18px;
  background: #fff;
  box-shadow: 0 1px 2px rgba(27, 31, 28, 0.04);
}

/* Title and month on the left, the two arrows together on the right. */
#console .calendar-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

#console .calendar-head__text {
  flex: 1 1 auto;
  min-width: 0;
}

#console .calendar-head__title {
  margin: 0;
  font-family: var(--font);
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: 0;
  text-transform: none;
  color: #1b1f1c;
}

#console .calendar-head__month {
  margin: 0.1rem 0 0;
  font-size: 0.72rem;
  color: #7c817b;
}

#console .calendar-nav-btn {
  display: grid;
  place-items: center;
  flex: none;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid #e8e6e0;
  border-radius: 50%;
  background: #f6f4ee;
  color: #1b1f1c;
  cursor: pointer;
  transition: border-color 0.15s;
}

#console .calendar-nav-btn:hover {
  border-color: #1c3a2b;
}

#console .calendar-grid {
  gap: 6px;
}

#console .calendar-dow {
  font-family: var(--font);
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  color: #4b514b;
}

/* Compact: a cell holds nothing but its date -- what is on that day is
   read in #dayModal, which a tap anywhere on the cell opens. */
#console .calendar-day {
  display: grid;
  place-items: center;
  min-height: 40px;
  padding: 0.3rem;
  border: 1px solid transparent;
  border-radius: 9px;
  background: #e9efe7;
}

#console .calendar-date {
  font-size: 0.78rem;
  font-weight: 700;
  color: #1f4a33;
}

/* Sundays: no box, the date in red. nth-child rather than a class: the
   seven weekday headings come first and the grid never varies which day
   starts each row, so every 7n+1 cell is a Sunday. */
#console .calendar-day:nth-child(7n + 1) {
  background: none;
}

#console .calendar-day:nth-child(7n + 1) .calendar-date {
  color: #d5503f;
}

/* The days either side of the month: present so the grid always fills
   full rows, but flat and inert. After the Sunday rule, so a Sunday from
   the month before is grey rather than red. */
#console .calendar-day.is-outside {
  background: none;
  cursor: default;
}

#console .calendar-day.is-outside .calendar-date {
  font-weight: 500;
  color: #b9bcb5;
}

#console .calendar-day.is-today {
  background: #fff;
  border-color: #1c3a2b;
}

#console .calendar-day.is-today .calendar-date {
  font-weight: 800;
  color: #1b1f1c;
}

/* --day-color comes off the day's own first game (renderCalendar() in
   admin.js), so a Basketball day fills orange, a Golf day green; that
   function falls back to green itself for a game with no sport colour. */
#console .calendar-day.has-games {
  background: var(--day-color);
}

#console .calendar-day.has-games .calendar-date {
  font-weight: 800;
  color: #fff;
}

/* =========================================================================
   PLAYER DASHBOARD -- the top pill nav moves to the phone tab bar.

   .nav--player's own unread-badge styling (.nav-dot) is scoped to that
   parent class, so it does not reach #navNotifDot in its new home inside
   .phone-tabs__item; this is that same badge, repositioned over the bell
   icon in a column-flex tab instead of a row of icon+label pills.
   ========================================================================= */
@media (max-width: 880px) {
  .player-page {
    padding-bottom: calc(4.6rem + env(safe-area-inset-bottom, 0px));
  }

  .phone-tabs__item .nav-dot {
    position: absolute;
    top: -2px;
    right: 18px;
    min-width: 15px;
    height: 15px;
    padding-inline: 3px;
    border-radius: 999px;
    background: var(--bad);
    color: #fff;
    font-size: 0.58rem;
    font-weight: 800;
    line-height: 15px;
    text-align: center;
    overflow: hidden;
    white-space: nowrap;
  }

  .phone-tabs__item .nav-dot-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  /* The design floats this bar clear of the screen's edges rather than
     bleeding it to them the way the public pages do -- a rounded panel with
     a gap on all four sides, bigger type and icons, and the tab you are on
     picked out by a lit box behind it rather than by colour alone. The
     public pages keep the flush-to-the-edge bar (see .phone-tabs further
     up); this is the player pages' own. */
  .player-page .phone-tabs {
    left: 50%;
    width: min(100vw - 1.6rem, 560px);
    translate: -50% 0;
    bottom: calc(0.7rem + env(safe-area-inset-bottom, 0px));
    gap: 0.25rem;
    padding: 0.35rem 0.5rem;
    border-radius: 20px;
    box-shadow: 0 10px 30px rgba(9, 24, 16, 0.3),
      0 0 0 1px color-mix(in srgb, var(--ph-accent) 18%, transparent);
  }

  .player-page .phone-tabs__item {
    gap: 0.2rem;
    padding: 0.4rem 0.15rem;
    border-radius: 13px;
    font-size: 0.68rem;
    font-weight: 600;
  }

  .player-page .phone-tabs__item .icon {
    width: 21px;
    height: 21px;
  }

  /* The lit box marks the current tab on its own; no dot under the label. */
  .player-page .phone-tabs__item.is-current {
    background: color-mix(in srgb, var(--ph-accent) 16%, transparent);
    font-weight: 700;
  }

  /* The bar is taller and no longer sits against the bottom edge, so the
     last card needs more room under it than the flush version wanted. */
  .player-page {
    padding-bottom: calc(5.5rem + env(safe-area-inset-bottom, 0px));
  }
}

/* The dashboard's header sits on the page's own pale surface, not a dark
   photo the way home/sports do, so .brand__tagline's always-white colour
   (built for that photo header) is invisible here. The page ground is the
   same pale green in both themes now, so this is set for both rather than
   for light theme alone the way it was when dark theme still had its own
   near-black surface to read against. */
:root body.player-page .brand,
:root body.player-page .brand__text {
  color: var(--dash-ink, #091810);
}

:root body.player-page .brand__tagline {
  color: var(--dash-ink-soft, #56675c);
}

/* "SPORTS" is the brand green everywhere else; on this page it takes the
   dashboard's accent so the wordmark belongs to what is under it. */
:root body.player-page .brand__text span {
  color: var(--dash-accent, #15954a);
}

:root body.player-page .brand__logo-box {
  background: var(--dash-accent, #15954a);
}

/* Two headers on the dashboard -- the brand-only phone one and
   x-player-nav's own pills for desktop -- shown one at a time. Desktop is
   the default (nothing hides it above 880px); the phone media query is what
   swaps them. */
.dashboard-phone-header {
  display: none;
}

@media (max-width: 880px) {
  .dashboard-phone-header {
    display: block;
  }

  .dashboard-desktop-nav {
    display: none;
  }
}
