/* =========================================================================
   Design tokens -- the "sporty" system, shared with the Speedup online
   registration site so the two read as one product: Speedup green +
   pickleball yellow, white cards on a cool-grey canvas, bold condensed
   italic display type (Barlow Condensed) for titles and numbers, uppercase
   muted micro-labels, pill badges and generously rounded controls.

   New components (page hero bands, the sidebar brand block, login, stat
   tiles, the public hero) live in sporty.css, loaded right after this file.
   ========================================================================= */
:root {
  --primary: #187339;
  --primary-600: #135f2f;
  --primary-700: #0d4a23;
  --primary-50: #eef5f0;
  --primary-100: #d9eadf;
  --primary-dark: #0d4a23;
  --primary-light: #18733914;
  --primary-ring: rgba(24, 115, 57, 0.18);

  /* Pickleball yellow, straight from the Speedup logo shield. Used
     sparingly: hero highlight/CTA, the active-nav marker, title accents. */
  --accent: #f5cf1e;
  --accent-600: #e6bd00;
  --accent-ink: #0b3d1c;

  --success: #1fae71;
  --success-light: #e7f8f0;
  --danger: #e6483f;
  --danger-light: #fdedec;
  --warning: #f5a623;
  --warning-light: #fef6e7;
  --bronze: #b0703a;
  --bronze-light: #f7ece2;

  --ink: #29314d;
  --muted: #6b7590;
  --subtle: #9aa3b8;
  --border: #e7eaf3;
  --border-strong: #d6dbe8;
  --surface: #ffffff;
  --canvas: #f5f7fb;

  /* The brand-green hero band shared by the admin scoresheet modal
     (.receipt-hero), the referee's full-screen scoring modal
     (.referee-scoring-modal-header), the page hero bands and the login
     panel -- one definition so they can't drift apart. The -striped
     variant adds a very faint diagonal "court line" texture. */
  --brand-hero:
    radial-gradient(120% 140% at 100% 0%, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0) 55%),
    linear-gradient(135deg, var(--primary) 0%, var(--primary-700) 100%);
  --brand-hero-striped:
    repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0 10px, rgba(255, 255, 255, 0) 10px 20px),
    var(--brand-hero);

  --radius-card: 16px;
  --radius-control: 10px;
  --radius-pill: 999px;
  --shadow-card:
    0 1px 2px rgba(23, 32, 63, 0.04), 0 2px 14px rgba(23, 32, 63, 0.05);
  --shadow-pop:
    0 18px 44px -14px rgba(23, 32, 63, 0.3), 0 2px 8px rgba(23, 32, 63, 0.06);
  --shadow-cta: 0 10px 22px -10px rgba(24, 115, 57, 0.75);

  --font-display:
    "Barlow Condensed", "Arial Narrow", "Roboto Condensed", "Inter", sans-serif;
  --ease: cubic-bezier(0.2, 0.8, 0.2, 1);
}

body {
  background-color: var(--canvas);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}
a {
  text-underline-offset: 3px;
}
:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}
::selection {
  background: var(--primary-100);
}
/* Form controls draw their own green ring (box-shadow) on focus -- don't
   stack the generic outline on top of it. */
.form-control:focus-visible,
.form-select:focus-visible,
.form-check-input:focus-visible {
  outline: none;
}

/* ---------------------------- Top navbar (logged-out pages only) ---------------------------- */
.navbar-brand-custom {
  background-color: var(--surface);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-card);
}
.navbar-brand-custom .navbar-brand {
  color: var(--ink) !important;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.navbar-brand-custom .nav-link {
  color: var(--muted) !important;
  font-weight: 500;
  border-radius: var(--radius-control);
  padding: 0.4rem 0.75rem !important;
  transition:
    background-color 0.15s ease,
    color 0.15s ease;
}
.navbar-brand-custom .nav-link:hover {
  color: var(--primary) !important;
  background-color: var(--primary-light);
}
.navbar-brand-custom .nav-link.active {
  color: var(--primary) !important;
  background-color: var(--primary-light);
  font-weight: 600;
}
.navbar-brand-custom .navbar-text {
  color: var(--muted) !important;
}
.navbar-brand-custom .navbar-toggler {
  border-color: var(--border);
}

/* ------------------------------- Sidebar layout (logged-in pages) ------------------------------- */
.app-shell {
  min-height: 100vh;
  align-items: stretch;
}
.sidebar {
  width: 260px;
  flex-shrink: 0;
  background-color: var(--surface);
  border-right: 1px solid var(--border);
}

@media (min-width: 992px) {
  .sidebar {
    position: sticky;
    top: 0;
    height: 100vh;
  }
}

/* NEW: make sure offcanvas-body fills the sidebar height and is a flex column */
.sidebar .offcanvas-body {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  padding: 0; /* if you weren't already using p-0 utility */
}

/* ------------------------------- Flash messages ------------------------------- */
/* Floats on top of the page (position: fixed) instead of sitting in normal
   flow, so it doesn't reserve space -- the content underneath never shifts
   up when the message auto-dismisses or is closed. */
#flash-messages {
  position: fixed;
  top: 1rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1090;
  width: min(600px, 92vw);
  pointer-events: none; /* clicks pass through the empty parts of the wrapper */
}
#flash-messages:empty {
  display: none;
}
#flash-messages .alert {
  pointer-events: auto;
}
@media (min-width: 992px) {
  /* Center over the content area to the right of the fixed sidebar,
     rather than the whole viewport. */
  #flash-messages {
    left: calc(50% + 130px);
  }
}

/* ---- "Notice" component: shared visual treatment for flash alerts
   (#flash-messages, above) and JS toasts (#toast-container, see
   showToast() in live_poll.js) -- a white card with a tone-colored left
   accent bar and icon badge, instead of Bootstrap's flat solid-color
   bar. Both surfaces keep Bootstrap's own .alert/.toast classes (for
   their dismiss/auto-hide JS behavior) and layer .notice + .notice-<tone>
   on top for the look, so this one set of rules covers both. */
.notice {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-left-width: 4px;
  border-radius: var(--radius-control);
  padding: 0.65rem 0.75rem;
  box-shadow: 0 8px 24px rgba(23, 32, 63, 0.12);
}
.notice-icon {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.notice-icon svg {
  width: 15px;
  height: 15px;
}
.notice-body {
  flex: 1 1 auto;
  min-width: 0;
}
.notice-label {
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  line-height: 1.2;
}
.notice-msg {
  display: block;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.3;
}
.notice-success {
  border-left-color: var(--success);
}
.notice-success .notice-icon {
  background-color: var(--success-light);
}
.notice-success .notice-label {
  color: var(--success);
}
.notice-danger {
  border-left-color: var(--danger);
}
.notice-danger .notice-icon {
  background-color: var(--danger-light);
  color: var(--danger);
}
.notice-danger .notice-label {
  color: var(--danger);
}
.notice-warning {
  border-left-color: var(--warning);
}
.notice-warning .notice-icon {
  background-color: var(--warning-light);
  color: var(--warning);
}
.notice-warning .notice-label {
  color: var(--warning);
}
.notice-info {
  border-left-color: var(--primary);
}
.notice-info .notice-icon {
  background-color: var(--primary-light);
  color: var(--primary);
}
.notice-info .notice-label {
  color: var(--primary);
}
/* Bootstrap's own alert-* classes still carry the dismiss-fade animation
   (see confirm_modal.js/base.html's auto-dismiss script) -- neutralize
   the flat background/border/color they'd otherwise paint underneath
   .notice's own look. */
#flash-messages .alert.notice {
  background-color: var(--surface);
  color: var(--ink);
}
.toast.notice {
  background-color: var(--surface);
  max-width: 380px;
}

/* Brand block: logo on the left, the "Speedup" wordmark over a tiny
   spaced-out product label on the right -- same lock-up as the online
   registration admin. */
.sidebar-brand,
.sidebar-brand-inline {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  min-width: 0;
  padding: 1.05rem 1.15rem 0.95rem;
  color: inherit;
  border-bottom: 1px solid var(--border);
  width: 100%;
}
.sidebar .offcanvas-header {
  padding: 0.8rem 1rem;
  border-bottom: 1px solid var(--border);
}
.sidebar-brand-inline {
  width: auto;
  padding: 0;
  border-bottom: 0;
}
/* Also used un-scoped by the login logo and the splash screen, which set
   their own sizes -- only the real sidebar gets the fixed logo height. */
.sidebar-brand-icon {
  border-radius: 6px;
  object-fit: contain;
  flex-shrink: 0;
}
.sidebar .sidebar-brand-icon {
  display: block;
  width: auto;
  height: 56px;
}
.sidebar .sidebar-brand-inline .sidebar-brand-icon {
  height: 44px;
}
.sidebar-brand-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1;
}
.sidebar-brand-name {
  font-size: 1.7rem;
}
.sidebar-brand-sub {
  margin-top: 0.3rem;
  color: var(--muted);
  font-size: 0.56rem;
  font-weight: 800;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  white-space: nowrap;
}
.sidebar-nav {
  padding: 0.6rem 0.75rem 0.75rem;
  overflow-y: auto;
}

/* Section label: condensed display caps with a little yellow slash before it. */
.sidebar-section-label {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 1.05rem 0.7rem 0.4rem;
  color: var(--muted);
  font-family: var(--font-display);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  line-height: 1.2;
  text-transform: uppercase;
}
.sidebar-section-label::before {
  content: "";
  flex: 0 0 auto;
  width: 5px;
  height: 12px;
  border-radius: 1px;
  background: var(--accent);
  transform: skewX(-18deg);
}
.sidebar-nav .sidebar-section-label:first-child {
  padding-top: 0.4rem;
}
.sidebar-link {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.62rem 0.8rem;
  margin-bottom: 0.15rem;
  border-radius: var(--radius-control);
  color: #3b4463;
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9rem;
  transition:
    background-color 0.15s var(--ease),
    color 0.15s var(--ease);
}
.sidebar-link .bi {
  flex: 0 0 auto;
  width: 1.2rem;
  font-size: 1.08rem;
  line-height: 1;
  color: var(--subtle);
  transition: color 0.15s var(--ease);
}
.sidebar-link:hover {
  background-color: var(--canvas);
  color: var(--ink);
}
.sidebar-link:hover .bi {
  color: var(--primary);
}
.sidebar-link.active {
  background-color: var(--primary-50);
  color: var(--primary);
  font-weight: 700;
}
.sidebar-link.active .bi {
  color: var(--primary);
}
/* Active marker: a slanted pickleball-yellow bar on the link's left edge. */
.sidebar-link.active::before {
  content: "";
  position: absolute;
  left: -0.75rem;
  top: 50%;
  width: 5px;
  height: 22px;
  border-radius: 0 3px 3px 0;
  background: var(--accent);
  transform: translateY(-50%);
}

/* Signed-in user card, pinned to the bottom of the sidebar. */
.sidebar-user {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.85rem 0.95rem;
  border-top: 1px solid var(--border);
  /* pin to bottom, never shrink/get squeezed by long nav lists */
  margin-top: auto;
  flex-shrink: 0;
}
.sidebar-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--primary-50);
  color: var(--primary);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}
.sidebar-user-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}
.sidebar-user-name {
  color: var(--ink);
  font-size: 0.88rem;
  font-weight: 700;
}
.sidebar-user-role {
  color: var(--muted);
  font-size: 0.75rem;
}
.sidebar-logout {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  margin-left: auto;
  flex: 0 0 auto;
  border-radius: var(--radius-control);
  color: var(--muted);
  font-size: 1.1rem;
  text-decoration: none;
  transition:
    background-color 0.15s var(--ease),
    color 0.15s var(--ease);
}
.sidebar-logout:hover,
.sidebar-logout:focus-visible {
  background-color: var(--danger-light);
  color: var(--danger);
}

.main-content {
  min-width: 0; /* prevents flex children (tables) from overflowing the layout */
}
.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  padding: 0.55rem 1rem;
  border-bottom: 1px solid var(--border);
  background-color: rgba(255, 255, 255, 0.92);
  -webkit-backdrop-filter: saturate(1.4) blur(12px);
  backdrop-filter: saturate(1.4) blur(12px);
}
.topbar-menu-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1.5px solid var(--border);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--ink);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  transition:
    border-color 0.15s var(--ease),
    color 0.15s var(--ease);
}
.topbar-menu-btn:hover {
  border-color: var(--primary);
  color: var(--primary);
}
.topbar-logo {
  width: auto;
  height: 30px;
}
.topbar-brand {
  font-size: 1.6rem;
}

/* ------------------------------- Cards ------------------------------- */
.card {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
}
.card-header {
  border-bottom: 1px solid var(--border);
  font-weight: 700;
  border-top-left-radius: var(--radius-card) !important;
  border-top-right-radius: var(--radius-card) !important;
}

/* Section headers (Pool Play / Bracket rounds / Bronze Match) -- solid
   primary-color band with bold uppercase text, rather than the previous
   soft understated tint, for a bolder "scoreboard" feel. */
.pool-header {
  background: var(--brand-hero-striped);
  color: #fff;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 1.1rem;
  line-height: 1.25;
}
/* NOT position:sticky, deliberately -- these live inside .bracket-scroll,
   which needs overflow-x: auto for the horizontal round-by-round scroll.
   Per the CSS overflow spec, setting overflow-x on an element forces its
   overflow-y to something other than `visible` too (you can't have one
   axis scroll and the other stay visible) -- and since ANY overflow
   value other than visible makes that element the sticky containing
   block for its descendants, .bracket-scroll (which never actually
   scrolls vertically itself; the page does) silently becomes that
   block instead of the viewport. A sticky descendant resolved against a
   container whose own scrollTop never moves never actually sticks --
   it just behaves as non-sticky while looking like it "should" work,
   which is exactly what caused the header to jump down and overlap the
   first match card instead of pinning in place. The round-jump nav
   above (.bracket-round-nav-wrap) doesn't have this problem -- it's a
   sibling BEFORE this scrolling container, not a descendant of it -- so
   its own sticky + auto-highlighting already answers "which round am I
   on" without needing this. */
.round-header {
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-600) 100%);
  color: var(--accent-ink);
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 1.1rem;
  line-height: 1.25;
}
.bronze-header {
  background: linear-gradient(135deg, #c98446 0%, var(--bronze) 100%);
  color: #fff;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 1.1rem;
  line-height: 1.25;
}

/* Round-jump nav (.bracket-round-nav-wrap) sticks to the very top on
   mobile, where a tall bracket means scrolling back up just to switch
   rounds is real friction -- the whole point of having the tabs. It's
   already mobile-only via d-md-none in the markup, so no media query is
   needed here to scope this. Unlike the round headers above, this one
   CAN be sticky -- it's a sibling BEFORE .bracket-scroll, not a
   descendant of it, so it isn't caught by that element's forced
   overflow-y. */
.bracket-round-nav-wrap {
  position: sticky;
  top: 0;
  z-index: 3;
  background-color: var(--canvas);
  padding-top: 0.5rem;
}

/* Stat-card labels, e.g. "GROSS REVENUE" style */
.stat-label {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Referee's "Select a Tournament to Score" picker (main/referee_index.html):
   the whole card is a tap target (.stretched-link on the CTA button), so a
   hover/focus lift is the only way to signal that on non-touch devices --
   the Score Matches button itself keeps its own visible affordance for
   touch. */
.referee-tournament-card {
  transition:
    box-shadow 0.15s ease,
    transform 0.15s ease,
    border-color 0.15s ease;
}
.referee-tournament-card:hover,
.referee-tournament-card:focus-within {
  box-shadow: 0 8px 24px rgba(23, 32, 63, 0.1);
  transform: translateY(-2px);
  border-color: var(--primary);
}
.referee-tournament-icon {
  width: 36px;
  height: 36px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  background-color: var(--primary-light);
  color: var(--primary);
}
.referee-tournament-icon-sm {
  width: 26px;
  height: 26px;
  border-radius: 8px;
}
.referee-tournament-name {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--ink);
}

/* ------------------------- Shared confirm modal (confirm_modal.js) ------------------------- */
.confirm-modal-content {
  border: none;
  border-radius: 20px;
  box-shadow: var(--shadow-pop);
  overflow: hidden; /* clips the header's solid tone color to the modal's rounded top corners */
}
.confirm-modal-header {
  padding: 0.85rem 1.1rem 0.75rem;
  border-bottom: none;
  background-color: var(--canvas);
  transition: background-color 0.1s ease;
}
.confirm-modal-header .modal-title {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 800;
  font-size: 1.25rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink);
}
/* Solid tone-colored band, set by confirm_modal.js adding tone-<variant>
   to .confirm-modal-content each time it opens (matches the toast/flash
   .notice-<tone> treatment elsewhere -- bold color up front instead of a
   plain white "are you sure?" bar). */
.confirm-modal-content.tone-danger .confirm-modal-header {
  background-color: var(--danger);
}
.confirm-modal-content.tone-warning .confirm-modal-header {
  background-color: var(--warning);
}
.confirm-modal-content.tone-success .confirm-modal-header {
  background-color: var(--success);
}
.confirm-modal-content.tone-primary .confirm-modal-header {
  background: var(--brand-hero-striped);
}
.confirm-modal-content[class*="tone-"] .confirm-modal-header .modal-title {
  color: #fff;
}
/* Hidden by default -- a data-confirm trigger with no icon (or before JS
   has run) falls back to the old plain "title only" header instead of an
   empty gap where a badge would be. Squircle rather than a circle, and a
   translucent white fill rather than its own solid tone color, now that
   it sits on the header's own solid tone-colored band instead of a plain
   white one. */
.confirm-modal-icon-badge {
  display: none;
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  border-radius: 10px;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.24);
  color: #fff;
}
.confirm-modal-icon-badge.show {
  display: inline-flex;
}
.confirm-modal-icon-badge svg {
  width: 17px;
  height: 17px;
}

.confirm-modal-body {
  color: var(--ink);
  padding: 0.7rem 1rem;
  font-size: 0.875rem;
  line-height: 1.3;
}
/* Plain-text mode (the common case -- "Delete this team?" etc.) keeps the
   line-break handling added for the check-in payment reminder's original
   single-paragraph fallback; the richer <template>-based layout below
   supplies its own spacing instead of relying on this. */
#confirmActionModalBody {
  white-space: pre-line;
}

/* Rich body, section 1: a plain (no card) "what happens next" lead-in --
   see checkin.html's payment-reminder <template> for the concrete usage.
   The icon badge sits in its own column to the left of ALL three lines
   (heading, greeting, lead text) rather than just inline with the
   heading -- otherwise the heading text would be indented past the icon
   while the greeting/lead text below it started flush at the left edge,
   leaving the three lines visually unaligned with each other. */
.confirm-modal-body .confirm-success-block {
  display: flex;
  align-items: flex-start;
  gap: 0.35rem;
}
/* Not a colored badge circle like the other icons in this modal -- the
   uploaded check_circle icon already bakes its own circle outline and
   green fill into the glyph, so it's shown directly rather than nested
   inside a second (redundant) colored circle. */
.confirm-modal-body .confirm-success-icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-top: 0.05rem;
}
.confirm-modal-body .confirm-success-text {
  min-width: 0;
}
.confirm-modal-body .confirm-success-heading {
  color: var(--success);
  font-weight: 700;
  font-size: 0.9rem;
  margin-bottom: 0.3rem;
}
.confirm-modal-body .confirm-greeting {
  font-weight: 600;
  margin-bottom: 0.1rem;
}
.confirm-modal-body .confirm-lead-text {
  color: var(--ink);
  margin-bottom: 0;
}

/* Rich body, section 2: one unified card holding several stacked rows,
   each separated by a hairline divider -- NOT a separate card per row.
   Mirrors a bank/payment-provider "reminder" card: a bold sub-heading row
   up top, plain informational rows below it, and a muted disclaimer row
   at the bottom, all inside one bordered container. */
.confirm-modal-body .confirm-highlight-card {
  background: var(--warning-light);
  border: 1px solid #f3dfb0;
  border-radius: var(--radius-control);
  margin: 0.5rem 0 0;
  overflow: hidden;
}
.confirm-modal-body .confirm-card-row {
  display: flex;
  gap: 0.5rem;
  align-items: flex-start;
  padding: 0.4rem 0.65rem;
}
.confirm-modal-body .confirm-card-row + .confirm-card-row {
  border-top: 1px solid #f3dfb0;
}
.confirm-modal-body .confirm-card-row p {
  margin-bottom: 0;
  color: var(--ink);
}
.confirm-modal-body .confirm-card-row svg {
  flex: 0 0 auto;
  margin-top: 0.1rem;
}
/* Row 1: a small filled icon badge (matches the header badge, scaled
   down) next to a bold heading, with its description stacked underneath
   -- both indented to line up with the heading, not the icon. */
.confirm-modal-body .confirm-card-icon-badge {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--warning);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.confirm-modal-body .confirm-card-icon-badge svg {
  width: 11px;
  height: 11px;
}
.confirm-modal-body .confirm-card-row strong {
  display: block;
  color: var(--warning);
  font-size: 0.85rem;
  margin-bottom: 0.1rem;
}
/* Rows 2+: a plain (no badge background) icon, colored to match the
   card's tone, sized like a normal inline icon rather than a badge.
   Direct-child selector only -- NOT a descendant selector -- so this
   doesn't also reach into .confirm-card-icon-badge above and repaint its
   (deliberately white, for contrast against the filled badge) icon
   amber-on-amber. */
.confirm-modal-body .confirm-card-row > .icon-inline {
  color: var(--warning);
}
.confirm-modal-body .confirm-card-row-note {
  color: var(--muted);
  font-size: 0.7rem;
}
/* The muted disclaimer is a footnote that sits BELOW the amber card (a
   sibling of it, not a row inside it), so it needs no divider line of its
   own -- just a little air between it and the card's bottom edge, and it
   lines up with the card's left edge rather than being indented like a
   card row. */
.confirm-modal-body .confirm-card-row.confirm-card-row-note {
  border-top: none;
  align-items: center;
  padding: 0.65rem 0.25rem 0;
}
.confirm-modal-body .confirm-card-row-note p {
  color: var(--muted);
}
.confirm-modal-body .confirm-card-row-note .confirm-card-icon-badge {
  width: 16px;
  height: 16px;
  background: var(--muted);
}
.confirm-modal-body .confirm-card-row-note .confirm-card-icon-badge svg {
  width: 9px;
  height: 9px;
}

.confirm-modal-footer {
  border-top: none;
  padding: 0.6rem 1rem 0.75rem;
  gap: 0.5rem;
  flex-wrap: wrap;
}
/* Cancel + Confirm fill the footer's full width between them (instead of
   Bootstrap's default right-aligned, content-width buttons), but each
   button's own text ("Cancel" vs. a longer "Continue Check-In ->") sets
   its OWN minimum width now, rather than an even flex-basis-0 50/50
   split. That even split used to force a short "Cancel" and a much
   longer "Continue Check-In" into equal widths -- fine on desktop, but
   on a narrow phone there wasn't enough room left for the longer label,
   so "Continue Check-In" wrapped onto two cramped lines with the arrow
   icon stranded on its own. min-width: max-content stops a button from
   ever shrinking below what its own label needs on one line; flex-wrap
   above means if the two together still don't fit the modal's width (a
   very long custom label, or a very narrow screen), the footer drops to
   one full-width button per row instead of wrapping text mid-button. */
.confirm-modal-footer .btn {
  flex: 1 1 auto;
  min-width: max-content;
  white-space: nowrap;
  padding: 0.5rem 1.15rem;
  font-weight: 700;
  font-size: 0.875rem;
  border-radius: 999px;
}
/* Quiet neutral pill instead of a bordered outline button -- next to a
   bold solid-color Confirm pill, an outline reads as a second competing
   action rather than the lower-emphasis "never mind" choice it is. */
.confirm-modal-footer .btn-outline-secondary {
  background-color: var(--canvas);
  border-color: transparent;
  color: var(--muted);
}
.confirm-modal-footer .btn-outline-secondary:hover {
  background-color: var(--border);
  color: var(--ink);
}
.confirm-modal-footer #confirmActionModalConfirmBtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}
.confirm-modal-footer #confirmActionModalConfirmBtn svg {
  flex: 0 0 auto;
}

/* ------------------------------ Buttons ------------------------------ */
.btn {
  /* Rounder + bolder than a plain form input (which stays at
     --radius-control/8px -- .form-control and .form-select still
     reference that token directly, so this doesn't affect them) for a
     more confident, CTA-like button feel. */
  border-radius: var(--radius-control);
  font-weight: 700;
  letter-spacing: 0.005em;
  transition:
    background-color 0.15s var(--ease),
    border-color 0.15s var(--ease),
    color 0.15s var(--ease),
    box-shadow 0.15s var(--ease),
    transform 0.15s var(--ease);
}
.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 4px var(--primary-ring);
}
.btn-primary {
  background-color: var(--primary);
  border-color: var(--primary);
}
/* Full-size green CTAs get the soft glow + a little lift; the compact
   .btn-sm ones inside dense tables stay flat. */
.btn-primary:not(.btn-sm):not(:disabled):not(.disabled),
.btn-success:not(.btn-sm):not(:disabled):not(.disabled) {
  box-shadow: var(--shadow-cta);
}
.btn-primary:not(.btn-sm):not(:disabled):not(.disabled):hover,
.btn-success:not(.btn-sm):not(:disabled):not(.disabled):hover {
  transform: translateY(-1px);
}
/* Pickleball-yellow highlight button (hero bands, welcome panels). */
.btn-accent {
  background-color: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  box-shadow: 0 10px 22px -10px rgba(245, 207, 30, 0.7);
}
.btn-accent:hover,
.btn-accent:focus-visible {
  background-color: #ffdc36;
  border-color: #ffdc36;
  color: var(--accent-ink);
  transform: translateY(-1px);
}
.btn-primary:hover,
.btn-primary:focus {
  background-color: var(--primary-dark);
  border-color: var(--primary-dark);
}
.btn-outline-primary {
  color: var(--primary);
  border-color: var(--primary);
}
.btn-outline-primary:hover {
  background-color: var(--primary);
  border-color: var(--primary);
}
/* Without this, an active/selected .btn-outline-primary (e.g. the "Pool A"
   button in a View filter group) falls back to Bootstrap's own default
   blue instead of the app's green -- Bootstrap only themes that state via
   its own --bs-* variables, which the rules above don't touch. */
.btn-outline-primary.active,
.btn-outline-primary:active,
.btn-check:checked + .btn-outline-primary {
  background-color: var(--primary) !important;
  border-color: var(--primary) !important;
  color: #fff !important;
}
.btn-success {
  background-color: var(--primary);
  border-color: var(--primary);
}
.btn-outline-secondary {
  color: var(--muted);
  border-color: var(--border-strong);
}
.btn-outline-secondary:hover {
  background-color: var(--primary);
  color: #fff;
  border-color: var(--primary);
}
.btn-outline-danger {
  color: var(--danger);
  border-color: #f3c6c3;
}
.btn-outline-danger:hover {
  background-color: var(--danger);
  border-color: var(--danger);
}

.btn-primary:focus,
.btn-primary:active,
.btn-primary.active,
.btn-check:checked + .btn-primary,
.show > .btn-primary.dropdown-toggle {
  background-color: var(--primary) !important;
  border-color: var(--primary) !important;
  color: #fff !important;
  box-shadow: none;
}

.btn-success:hover,
.btn-success:active,
.btn-success:active {
  background-color: var(--primary-dark) !important;
  border-color: var(--primary-dark) !important;
}

/* --------------------------- Dropdown menus --------------------------- */
/* Action-button dropdowns (row kebab menus, etc.) -- a floating rounded
   card instead of Bootstrap's plain square-cornered, hard-bordered
   default, to match the rest of the "sporty" surfaces (cards, modals,
   toasts) elsewhere. */
.dropdown-menu {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-pop);
  padding: 0.4rem;
  margin-top: 0.35rem !important;
}
.dropdown-item {
  border-radius: var(--radius-control);
  padding: 0.5rem 0.75rem;
  font-weight: 600;
  font-size: 0.875rem;
  color: var(--ink);
  transition: background-color 0.12s ease, color 0.12s ease;
}
.dropdown-item:hover,
.dropdown-item:focus {
  background-color: var(--primary-light);
  color: var(--primary);
}
/* A destructive item (Delete, etc.) gets its own danger-tinted hover
   instead of the same green as every other item -- same "the color
   matches the consequence" logic as the confirm modal's tone system. */
.dropdown-item.text-danger:hover,
.dropdown-item.text-danger:focus {
  background-color: var(--danger-light);
  color: var(--danger);
}
.dropdown-divider {
  border-top-color: var(--border);
  margin: 0.35rem 0.1rem;
}
/* Kebab (⋮) trigger button -- a perfect circle rather than .btn's usual
   rounded-rectangle, the common "icon-only action" shape, plus a filled
   state while its menu is open so it doesn't look inert mid-interaction. */
.btn-kebab {
  border-radius: 50%;
  /* Sits next to flex-grow-1 Update/Save buttons on the Pool Play mobile
     card (see pool_play/view.html) -- without this, a narrow phone lets
     the flexbox shrink this button below its own content size instead of
     shrinking the already-growing siblings further, squashing the
     receipt icon into an oval instead of a circle. */
  flex-shrink: 0;
}
.btn-kebab:hover,
.btn-kebab:focus,
.btn-kebab[aria-expanded="true"] {
  background-color: var(--primary-light);
  border-color: var(--primary-light);
  color: var(--primary);
}
/* Receipt trigger specifically loses the outline ring -- a plain icon
   next to Update/Save reads lighter than another bordered pill, unlike
   the other .btn-kebab uses (serve-tracking "..." menu, Teams row
   actions) which keep theirs. */
.scoresheet-receipt-trigger,
.scoresheet-receipt-trigger:hover,
.scoresheet-receipt-trigger:focus,
.scoresheet-receipt-trigger:disabled {
  border-color: transparent;
}

/* ------------------------------- Badges ------------------------------- */
.badge {
  font-weight: 700;
  letter-spacing: 0.01em;
  padding: 0.4em 0.75em;
  border-radius: var(--radius-pill);
}

/* -------------------------------- Forms -------------------------------- */
.form-control,
.form-select {
  border-radius: var(--radius-control);
  border-color: var(--border-strong);
  transition:
    border-color 0.15s var(--ease),
    box-shadow 0.15s var(--ease);
}
.form-control:hover:not(:focus):not(:disabled),
.form-select:hover:not(:focus):not(:disabled) {
  border-color: #c3cadc;
}
.form-control:focus,
.form-select:focus {
  border-color: var(--primary);
  /* Was a stray Bootstrap blue (rgba(59,125,221,...)) that never got
     themed -- every focused field on the site glowed blue against an
     otherwise all-green brand. */
  box-shadow: 0 0 0 4px var(--primary-ring);
}
.form-label {
  font-weight: 700;
  font-size: 0.82rem;
  letter-spacing: 0.01em;
  color: var(--ink);
}
.form-check-input:checked {
  background-color: var(--primary);
  border-color: var(--primary);
}
.form-check-input:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 4px var(--primary-ring);
}

/* Every <select class="form-select"> on the site (pool pickers, payment
   status, roles, pool/bracket size, ...): Bootstrap already draws its own
   SVG chevron here (not the OS-native arrow), just in a flat gray that
   matches nothing else on the page -- swap it for the same branded green
   chevron used elsewhere (the pool-collapse-toggle icon), rather than
   leaving one lone gray arrow among an otherwise all-green icon set. */
.form-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23187339' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
  background-size: 16px 16px;
}

/* Flatpickr (payment-date fields' dropdown calendar) -- overrides its
   default blue theme to match the app's green brand. The plain
   <input type="date"> this replaced couldn't be styled this way at all:
   its dropdown is an OS-native popup with zero CSS hooks in every
   browser; Flatpickr renders its own DOM instead, so every part of it
   (header, selected day, hover, today marker) is themeable. */
.flatpickr-calendar {
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  border: 1px solid var(--border);
}
.flatpickr-calendar.arrowTop:before,
.flatpickr-calendar.arrowTop:after {
  display: none;
}
.flatpickr-months .flatpickr-month {
  color: var(--ink);
}
.flatpickr-current-month .flatpickr-monthDropdown-months,
.flatpickr-current-month input.cur-year {
  color: var(--ink);
  font-weight: 600;
}
.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
  border-radius: 6px;
}
.flatpickr-months .flatpickr-prev-month:hover,
.flatpickr-months .flatpickr-next-month:hover {
  background-color: var(--primary-light);
}
.flatpickr-months .flatpickr-prev-month svg,
.flatpickr-months .flatpickr-next-month svg {
  fill: var(--muted);
}
.flatpickr-months .flatpickr-prev-month:hover svg,
.flatpickr-months .flatpickr-next-month:hover svg {
  fill: var(--primary);
}
span.flatpickr-weekday {
  color: var(--muted);
  font-weight: 700;
}
.flatpickr-day {
  border-radius: 6px;
  color: var(--ink);
}
.flatpickr-day:hover,
.flatpickr-day:focus {
  background-color: var(--primary-light);
  border-color: var(--primary-light);
}
.flatpickr-day.today {
  border-color: var(--primary);
}
.flatpickr-day.today:hover {
  background-color: var(--primary);
  color: #fff;
}
.flatpickr-day.selected,
.flatpickr-day.selected:hover,
.flatpickr-day.selected:focus {
  background-color: var(--primary);
  border-color: var(--primary);
  color: #fff;
}
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay {
  color: var(--muted);
}
.flatpickr-input[readonly],
input.flatpickr-payment-date,
input.flatpickr-match-time {
  cursor: pointer;
  background-color: var(--surface);
}

/* Flatpickr in time-only mode (Pool Play / Bracket "Time") -- same themed
   shell as the date picker above (.flatpickr-calendar etc. already cover
   it), plus these rules for the hour/minute steppers and AM/PM toggle
   that only exist in time mode. Replaces the plain <input type="time">,
   which -- like <input type="date"> -- is an OS-native popup with no CSS
   hooks, and (on top of that) renders as three separately-clickable
   segments that don't match this app's "one input, one popup" pattern
   anywhere else. */
.flatpickr-time input.numInput {
  color: var(--ink);
  font-weight: 600;
}
.flatpickr-time input.flatpickr-minute {
  font-weight: 500;
}
.flatpickr-time input:hover,
.flatpickr-time input:focus,
.flatpickr-time .flatpickr-am-pm:hover,
.flatpickr-time .flatpickr-am-pm:focus {
  background: var(--primary-light);
}
.flatpickr-time .flatpickr-am-pm {
  color: var(--primary);
  font-weight: 600;
}
.flatpickr-time .flatpickr-time-separator {
  color: var(--muted);
}
.numInputWrapper:hover {
  background: var(--primary-light);
}
.numInputWrapper span.arrowUp:after {
  border-bottom-color: var(--muted);
}
.numInputWrapper span.arrowDown:after {
  border-top-color: var(--muted);
}
.numInputWrapper:hover span.arrowUp:after {
  border-bottom-color: var(--primary);
}
.numInputWrapper:hover span.arrowDown:after {
  border-top-color: var(--primary);
}

/* -------------------------------- Tables -------------------------------- */
/* Wrap every data table in a consistent, responsive, aligned layout:
   fixed-ish column behavior via vertical-align + nowrap on numeric/short
   columns, consistent header styling, subtle row hover, no double borders
   from combining table-bordered with our own card border. */
.table {
  margin-bottom: 0;
  color: var(--ink);
}
.table thead th {
  background-color: var(--canvas);
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  vertical-align: middle;
}
.table td {
  vertical-align: middle;
  border-color: var(--border);
}
.table-hover tbody tr:hover {
  background-color: var(--primary-light);
}
/* Numeric / short-value columns: consistent alignment across every table
   so figures line up instead of drifting left/right table to table. */
.table .col-num,
.table td.col-num,
.table th.col-num {
  text-align: center;
  white-space: nowrap;
}
.table-responsive {
  border-radius: var(--radius-card);
}
.advance-row td {
  background-color: var(--success-light) !important;
}
/* Mobile stacked rows (Pool Play, Standings) reuse .advance-row on a <div>
   instead of a <tr> -- the rule above only ever matches <td>, which a div
   row has none of, so the highlight needs its own rule here too. */
.match-row-mobile.advance-row,
.standings-row-mobile.advance-row {
  background-color: var(--success-light) !important;
}
.winner-name {
  font-weight: 700;
  color: var(--success);
}
/* A pool-play player who hasn't checked in yet (see the team_players
   macro in _macros.html) -- greyed until they do. A winner's green still
   wins, same "winner reads green" rule as the pills below. */
.player-unchecked {
  color: var(--muted);
}
.winner-name .player-unchecked {
  color: inherit;
}

/* Compact inputs/selects placed inline inside table cells (score entry) --
   keep them narrow and aligned instead of stretching to fill the cell. */
.table .form-control-sm,
.table .form-select-sm {
  min-width: 3.5rem;
}

.table td,
.table th {
  font-size: clamp(0.75rem, 0.7rem + 0.2vw, 0.8125rem);
  font-weight: 500;
}

/* ----------------------------- Progress bars ----------------------------- */
.progress {
  background-color: var(--canvas);
  border-radius: 999px;
  overflow: hidden;
}
.progress-bar {
  border-radius: 999px;
}

/* ------------------------------ Bracket cards ------------------------------ */
.bracket-card {
  border-radius: var(--radius-control);
}

/* ------------------------------ Public pages sub-nav ------------------------------ */
/* Pill-shaped tab strip (Pool Play / Standings / Bracket / Check-In / All
   Tournaments) on every public spectator page -- a plain white card
   holding icon+label tabs, the active one picked out by color + an
   underline rather than a filled button background. */
.public-subnav {
  display: flex;
  align-items: stretch;
  gap: 0.25rem;
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: 0.4rem;
  overflow-x: auto;
}
.public-subnav-item {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.65rem 1.1rem;
  border-radius: var(--radius-control);
  color: var(--muted);
  font-weight: 700;
  font-size: 0.9rem;
  text-decoration: none;
  white-space: nowrap;
  position: relative;
  transition:
    color 0.15s var(--ease),
    background-color 0.15s var(--ease),
    box-shadow 0.15s var(--ease);
}
.public-subnav-item:hover {
  color: var(--primary);
  background-color: var(--primary-light);
}
/* The current tab is a solid green pill -- unmistakable at a glance on a
   phone in bright sunlight, which the old colour-only + underline wasn't. */
.public-subnav-item.active,
.public-subnav-item.active:hover {
  color: #fff;
  background-color: var(--primary);
  box-shadow: var(--shadow-cta);
}
@media (max-width: 767.98px) {
  .public-subnav-item span {
    display: none;
  }
  .public-subnav-item {
    padding: 0.65rem 0.8rem;
  }
}

/* "Format" chip on the public Standings page -- a plain, non-interactive
   readout (this app doesn't have multiple scoring formats to pick between,
   so it's not a real <select>) styled to match the app's form controls. */
.standings-format-chip {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background-color: var(--surface);
  color: var(--ink);
  font-size: 0.85rem;
  font-weight: 500;
  max-width: 100%;
}
.standings-format-chip svg {
  color: var(--muted);
  flex-shrink: 0;
}

/* "View" pool filter (public Standings and Pool Play): a Bootstrap
   .btn-group doesn't wrap its buttons, so a tournament with many pools
   (e.g. 16 pools feeding a Round of 32) would otherwise blow out the
   whole page horizontally. Contain that overflow to a scrollable strip
   instead -- same pattern as .bracket-scroll below -- so the button group
   scrolls within its own bounds and the page itself never does. */
.pool-filter-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  max-width: 100%;
  padding-bottom: 2px; /* room for the scrollbar so it doesn't clip the buttons' bottom border */
}
.pool-filter-scroll .btn-group {
  flex-wrap: nowrap;
}
.pool-filter-scroll .btn {
  flex-shrink: 0;
}

/* ------------------------------ Responsive tweaks ------------------------------ */
@media (max-width: 767.98px) {
  .card-header {
    flex-wrap: wrap;
    gap: 0.5rem;
  }
  h2 {
    font-size: 1.4rem;
  }
  .page-title {
    font-size: clamp(1.8rem, 8vw, 2.2rem);
  }
}

/* Proper touch targets on the sidebar's slide-in drawer (mobile/tablet) --
   Bootstrap's default padding is comfortable for a mouse pointer but
   under the ~44px minimum recommended for touch. */
@media (max-width: 991.98px) {
  .sidebar-link {
    padding: 0.75rem 0.9rem;
    font-size: 1rem;
  }
}

/* Mobile stacked card rows (Pool Play, Teams, Standings): give primary
   action buttons a full touch-friendly height instead of the compact
   .btn-sm sizing used in dense desktop tables. */
@media (max-width: 767.98px) {
  .match-row-mobile .btn,
  .team-row-mobile .btn,
  .standings-row-mobile .btn {
    min-height: 44px;
  }
}

/* Dashboard's desktop Actions column (main/index.html): below 1440px the
   three full-label buttons (Copy Standings Link / Edit / Delete) don't
   have room to sit inline without wrapping and colliding with the row
   below, so collapse to a single dropdown there instead. At 1440px and
   up there's enough width to show all three buttons directly. */
.tournament-actions-inline {
  display: none;
}
.tournament-actions-dropdown {
  display: inline-block;
}
@media (min-width: 1440px) {
  .tournament-actions-inline {
    display: inline-flex;
  }
  .tournament-actions-dropdown {
    display: none;
  }
}

/* ------------------------------ Login styles ------------------------------ */

.bg-image-vertical {
  position: relative;
  overflow: hidden;
  background-repeat: no-repeat;
  background-position: right center;
  background-size: auto 100%;
}

@media (min-width: 1025px) {
  .h-custom-2 {
    height: 100%;
  }
}

.brand-font {
  font-family: "Playball", cursive;
  font-size: 1.3rem; /* Playball reads small at default size, worth bumping up */
  color: var(--primary);
  /* Playball's swashy capital "S" pushes its natural ascent well above a
     normal font's, so the browser's default 1.5 line-height (inherited
     from Bootstrap) pads out a box far taller than the visible letters --
     centering THAT box next to an icon (the hamburger button, the modal
     icon badges) visually reads as the word sitting low. Tightening the
     line-height brings the box much closer to the glyphs' own ink, so
     flexbox's align-items:center lines it up with icons the way it looks
     like it should, not just where the unadjusted font math says it is. */
  line-height: 1;
}

.login-footnote {
  font-size: clamp(0.8125rem, 0.75rem + 0.3vw, 0.875rem);
}

/* ------------------------------ Login styles ------------------------------ */

/* ------------------------------ Main styles ------------------------------ */
.tournament-name-cell {
  max-width: 200px;
}
.tournament-name-cell a {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.stat-label {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}
/* ------------------------------ Main styles ------------------------------ */

/* ------------------------------ page title styles ------------------------------ */
/* Condensed italic display caps with a slanted yellow bar underneath --
   the one rule that restyles every page heading (all use <h2 class="page-title">). */
.page-title {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 800 !important;
  font-size: clamp(2rem, 1.5rem + 1.6vw, 2.75rem);
  letter-spacing: 0.005em;
  line-height: 1;
  text-transform: uppercase;
  color: var(--ink);
}
.page-title::after {
  content: "";
  display: block;
  width: 3.75rem;
  height: 5px;
  margin-top: 0.6rem;
  background: var(--accent);
  clip-path: polygon(6px 0, 100% 0, calc(100% - 6px) 100%, 0 100%);
}
/* ------------------------------ page title styles ------------------------------ */

/* ------------------------------ category back-link styles ------------------------------ */
/* "<- {Tournament} -- Categories" link shown at the top of every
   category-scoped admin page (Teams, Check-In, Pool Play, Standings,
   Bracket, Master Schedule, plus the New/Edit Category forms). Was a
   plain small/muted <a> with no visual weight -- easy to miss and a tiny
   tap target, especially on a touch/kiosk screen. A real pill button
   instead: a proper min-height tap target, a visible border/background
   at rest so it doesn't read as throwaway caption text, and a clear
   hover/focus state so it's obviously interactive. */
.category-back-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 38px;
  padding: 0.4rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background-color: var(--surface);
  color: var(--ink) !important;
  font-size: 0.9rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none !important;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.category-back-link:hover,
.category-back-link:focus-visible {
  background-color: var(--primary-light);
  border-color: var(--primary);
  color: var(--primary) !important;
}
.category-back-link .icon-inline {
  flex-shrink: 0;
}
/* ------------------------------ category back-link styles ------------------------------ */

/* ------------------------------ teams table styles ------------------------------ */
.teams-table td,
.teams-table th {
  vertical-align: middle;
}

/* Add Team / Bulk Import column on Teams -- stays in view while scrolling
   a long pool list on the right, instead of scrolling away with it. Same
   position:sticky-within-its-own-column pattern .sidebar itself already
   uses (see that rule above) -- the containing .col-lg-4 is stretched to
   the taller right column's height by the .row's own flexbox (Bootstrap
   grid columns default to align-items: stretch), which is exactly the
   room this needs to have somewhere to "stick" as the page scrolls.
   lg-and-up only, matching the col-lg-4/col-lg-8 split actually being
   active -- below that breakpoint the columns stack vertically instead,
   where sticking this above a long scroll would just get in the way. */
@media (min-width: 992px) {
  .teams-sidebar-sticky {
    position: sticky;
    top: 1.5rem;
  }
}

/* Pool list on Teams -- its own bounded, independently-scrolling panel
   (desktop/big-resolution screens only, not tablet or mobile -- see
   .teams-sidebar-sticky's own comment on why that split matters; below
   lg the columns stack and a capped-height inner scroller would just
   fight the page's own natural scroll). Search/Export CSV above it, and
   Add Team/Bulk Import in the other column, both stay in a fixed spot on
   screen as a result -- but as a SIDE EFFECT of them simply sitting
   outside this panel in normal flow, not because they're position:sticky
   themselves overlaying scrolling content (an earlier version tried
   that: sticky only ever overlays whatever's behind it, never clips it,
   so at almost any scroll position a pool card ended up sitting exactly
   half behind/half in front of the bar -- cut clean in half, ugly and
   confusing). A bounded scroll panel can't produce that: a card is
   either fully inside its visible area or fully clipped by its own
   edge, never straddling it -- exactly "hidden, not half-visible". */
@media (min-width: 992px) {
  .teams-pools-scroll {
    max-height: calc(100vh - 220px);
    overflow-y: auto;
    padding-right: 0.5rem;
  }
}

/* Same bounded-scroll-panel pattern as .teams-pools-scroll above, applied
   to Master Schedule only -- Check-In, Pool Play, and Standings each had
   their own version of this too at one point, but were reverted back to
   normal full-page scroll on request. */
@media (min-width: 992px) {
  .schedule-scroll {
    max-height: calc(100vh - 220px);
    overflow-y: auto;
  }
  /* Column headers (Round, Match, Team 1, ...) freeze at the top of the
     scroll panel instead of scrolling away with the rows -- sticky on
     the individual <th> cells rather than on <thead> itself, which has
     inconsistent/unreliable browser support in table layout; per-cell
     sticky works reliably everywhere. An opaque background is required
     (unlike a plain page-level sticky header, which never has anything
     scrolling behind it) since match rows scroll directly underneath
     these cells as the panel scrolls -- matches the card's own white
     surface so it reads as a frozen header, not a floating strip. */
  .schedule-scroll thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background-color: var(--surface);
  }
}

.payment-select {
  width: auto;
  min-width: 100px;
  flex-shrink: 0;
}

.payment-control {
  min-width: 0;
}
.payment-remarks-input {
  min-width: 0;
  flex: 1 1 auto;
}
.payment-save-btn {
  flex-shrink: 0;
}

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

/* Search bar with an inline icon (Teams tab, and reused wherever a search
   box needs the same look). */
.search-input-wrapper {
  position: relative;
}
.search-input-wrapper .search-input-icon {
  position: absolute;
  left: 0.9rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted);
  pointer-events: none;
  font-size: 0.85rem;
  /* Sit above the input itself: Bootstrap bumps a `.form-control` to
     `z-index: 5` on `:focus`/`:hover`/`:active`, which would otherwise
     paint the input on top of this icon since it comes later in the DOM. */
  z-index: 6;
}
.search-input-wrapper .form-control {
  padding-left: 2.25rem;
}

/* Hides whatever the public Pool Play search (initPoolSearch in
   live_poll.js) filtered out. A class instead of inline display so it
   doesn't fight the pool filter's own display:none on the same cards. */
.search-hidden {
  display: none !important;
}

/* A row of header CTA buttons (see main/index.html's Tournaments page) --
   full width stacked on mobile so each button's label stays legible
   instead of three buttons squeezed into whatever space is left next to
   the page title; side by side, auto-width, at their natural size on
   desktop. */
.header-cta-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: 100%;
}
.header-cta-group .btn {
  width: 100%;
}
@media (min-width: 768px) {
  .header-cta-group {
    flex-direction: row;
    width: auto;
  }
  .header-cta-group .btn {
    width: auto;
  }
}

/* Master Schedule's status filter, paired with the search input above it
   (see schedule/view.html) -- full width stacked on mobile, capped and
   inline with the search box on desktop. */
.schedule-status-filter {
  width: 100%;
}
@media (min-width: 768px) {
  .schedule-status-filter {
    width: auto;
    max-width: 180px;
    flex-shrink: 0;
  }
}

/* Sortable column header button -- used by the Master Schedule's Court
   column (see schedule/view.html) and the Categories list's Name column
   (see main/categories.html). A plain inline button rather than the
   whole <th>, so its hit target doesn't fight the header cell's own
   text alignment. */
.table-sort-btn {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  font-weight: inherit;
  /* Bootstrap resets both of these on <button>, so without inheriting
     them back the label loses the header row's uppercase styling. */
  text-transform: inherit;
  letter-spacing: inherit;
  color: inherit;
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  cursor: pointer;
}
.table-sort-btn:hover,
.table-sort-btn:focus-visible {
  color: var(--primary);
}
.table-sort-icon {
  display: inline-flex;
  opacity: 0.35;
  transition: transform 0.15s ease, opacity 0.15s ease;
}
/* Points up for ascending (small court numbers / A-Z first), down for
   descending -- chevron-down is the icon's resting orientation, so only
   the ascending state needs the flip. Faint/un-rotated until the column
   has actually been sorted at least once (no data-sort-dir yet). */
.table-sort-btn[data-sort-dir] .table-sort-icon {
  opacity: 1;
}
.table-sort-btn[data-sort-dir="asc"] .table-sort-icon {
  transform: rotate(180deg);
}
/* 1 / 2 next to the arrow while two columns are sorted at once (1 = main sort). */
.table-sort-rank {
  font-size: 0.65rem;
  font-weight: 700;
  line-height: 1;
  color: var(--primary);
}

/* Icon-only sort button for card layouts, where there's no column header
   to click (the public Tournaments list on mobile). Shows a neutral
   up/down arrow until sorted, then A-Z / Z-A (inline SVGs in
   public/tournament_list.html, swapped by data-sort-dir).

   It deliberately does NOT reuse .btn-outline-secondary: that class's
   :hover fills the button solid green (the theme's .btn-outline-secondary:hover
   rule, further up), and a phone leaves a
   tapped button stuck in :hover -- so the moment you sorted, the icon
   (green, to mark "active") sat on a green fill and vanished. Every state
   is spelled out here instead, none of them ever paints the icon onto its
   own color, and the hover tint is limited to devices that really hover so
   a tap never leaves a stuck look behind. */
.btn.sort-icon-btn,
.btn.sort-icon-btn:hover,
.btn.sort-icon-btn:focus,
.btn.sort-icon-btn:active {
  display: inline-flex;
  align-items: center;
  background-color: #fff;
  /* A step darker than --muted (#8a94a6, only ~3:1 on white): this icon is
     the whole affordance, so it has to be easy to find. ~6:1 on white. */
  color: #59637a;
  border-color: var(--border);
  box-shadow: none;
}
.btn.sort-icon-btn:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}
/* A sort is applied: green icon + border on a faint green tint, in every
   state (resting, hovered, focused, pressed). */
.btn.sort-icon-btn[data-sort-dir],
.btn.sort-icon-btn[data-sort-dir]:hover,
.btn.sort-icon-btn[data-sort-dir]:focus,
.btn.sort-icon-btn[data-sort-dir]:active {
  background-color: #e7f1ea;
  color: var(--primary);
  border-color: var(--primary);
}
/* Pressed feedback -- a slightly deeper tint, still readable. */
.btn.sort-icon-btn:active {
  background-color: #eef0f6;
}
.btn.sort-icon-btn[data-sort-dir]:active {
  background-color: #cfe4d6;
}
@media (hover: hover) {
  .btn.sort-icon-btn:not([data-sort-dir]):hover {
    color: var(--primary);
    border-color: var(--primary);
  }
}
.sort-icon-btn .sort-icon-asc,
.sort-icon-btn .sort-icon-desc {
  display: none;
}
.sort-icon-btn[data-sort-dir] .sort-icon-idle {
  display: none;
}
.sort-icon-btn[data-sort-dir="asc"] .sort-icon-asc,
.sort-icon-btn[data-sort-dir="desc"] .sort-icon-desc {
  display: inline-block;
}

/* Player avatar circle (initials) on the redesigned Teams table -- colored
   by that player's payment status so the whole row scans at a glance. */
.player-avatar {
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  font-weight: 700;
}
.player-avatar.is-paid {
  background-color: var(--success-light);
  color: var(--success);
}
.player-avatar.is-unpaid {
  background-color: var(--danger-light);
  color: var(--danger);
}

.payment-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
  display: inline-block;
}
.payment-dot.is-paid {
  background-color: var(--success);
}
.payment-dot.is-unpaid {
  background-color: var(--danger);
}

.team-status-badge {
  font-size: 0.72rem;
}
.team-status-sub {
  font-size: 0.7rem;
  font-weight: 600;
  margin-top: 0.2rem;
  opacity: 0.85;
}

/* Badge/chip sitting inside a solid-color .pool-header (team/check-in
   counts, etc.) -- a translucent white pill reads clearly against the
   header's own solid primary color, where a light colored badge (built
   for the header's old pale-tint background) would nearly disappear. */
.pool-header-chip {
  background-color: rgba(255, 255, 255, 0.22) !important;
  color: #fff !important;
  border-color: transparent !important;
}

/* Pool card header doubles as a collapse toggle -- chevron rotates to
   point at the content instead of away from it when collapsed. */
.pool-header[data-bs-toggle="collapse"] {
  cursor: pointer;
}
.pool-collapse-toggle {
  display: inline-block;
  transition: transform 0.15s ease;
}
.pool-header[aria-expanded="false"] .pool-collapse-toggle {
  transform: rotate(-90deg);
}

/* Inline Material Symbols icons (see the svg_icon macro in _macros.html) --
   nudged down slightly so their optical center lines up with adjacent
   text instead of sitting high against the baseline. */
.icon-inline {
  vertical-align: -0.15em;
  flex-shrink: 0;
}

/* Check-In dashboard's Player column: a fixed-width name slot so the
   "Unpaid" badge that may follow lines up at the same x position on every
   row, instead of drifting left/right with each player's name length. */
.checkin-player-name {
  min-width: 190px;
  margin-right: 0.5rem;
}

.btn-kebab {
  width: 2rem;
  height: 2rem;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  font-size: 1.1rem;
}

/* Check-In landing list's row-end arrow -- same icon-button size as
   .btn-kebab above (Teams table's row-actions button) instead of a bare
   floating glyph, so it reads as a real affordance. Rounded square
   (var(--radius-control), same corner radius as the app's other
   controls/cards) rather than a full circle. Fills with the brand color
   on row hover, matching how .list-group-item-action rows elsewhere
   already signal "tap me". */
.checkin-landing-arrow {
  width: 2rem;
  height: 2rem;
  flex-shrink: 0;
  border-radius: var(--radius-control);
  border: 1px solid var(--border);
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.list-group-item-action:hover .checkin-landing-arrow,
.list-group-item-action:focus .checkin-landing-arrow {
  background-color: var(--primary);
  border-color: var(--primary);
  color: #fff;
}

/* "Quick Check-In" QR panel on the Check-In landing page -- lets one
   kiosk device cover both check-in paths (tap the list right there, or
   scan this with your own phone) instead of needing a second device
   just to display a QR code. */
.checkin-qr-panel {
  background-color: var(--surface);
  border-color: var(--border);
}
.checkin-qr-panel-icon {
  width: 2.5rem;
  height: 2.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background-color: var(--primary);
  color: #fff;
}
.checkin-qr-panel-code {
  display: inline-flex;
  padding: 0.75rem;
  background-color: var(--surface);
  border: 1px solid var(--primary-light);
  border-radius: var(--radius-control);
  line-height: 0; /* collapses the inline-SVG's own baseline gap so the padding above is exact */
}
.checkin-qr-panel-or {
  display: flex;
  align-items: center;
  /* Without this, the two flex-grow .checkin-footer-hr lines cap out at
     their own max-width (96px, tuned for the wider page footer this
     class is shared with -- see below) well before filling this
     narrower panel's width, and the leftover space collapses to the
     right by default (justify-content: flex-start) -- shifting the
     whole "── or ──" row visibly off-center to the left instead of
     centering it. */
  justify-content: center;
  gap: 0.6rem;
  width: 100%;
}
.checkin-qr-panel-hint {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  width: 100%;
  padding: 0.6rem 0.9rem;
  background-color: var(--primary-light);
  border-radius: var(--radius-control);
  color: var(--primary);
}

/* Public pages' shared bottom footer (Check-In landing, Tournament list):
   a grey rule flanking "Powered by: Speedup" on both sides, and a green
   vertical divider between the WhatsApp/email contact rows once they sit
   side by side (md+ -- see the d-none d-md-block on the divider itself;
   below that they stack in a column, where a vertical line between them
   wouldn't read as a separator). */
.checkin-footer-heading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  width: 100%;
}
.checkin-footer-hr {
  flex: 1 1 60px;
  max-width: 96px;
  height: 1px;
  background-color: var(--border);
}
.checkin-footer-divider {
  width: 1px;
  align-self: stretch;
  background-color: var(--primary);
  opacity: 0.5;
}
/* ------------------------------ teams table styles ------------------------------ */

/* ------------------------------ poolplay table styles ------------------------------ */
/* Every pool renders its own <table>, and table-layout: auto sizes each
   one's columns from that table's own content -- so Pool A and Pool B end
   up with different Court/Time/Score/Winner widths whenever their team
   names are different lengths. Fixed widths on the short columns (with
   the two Team columns left unset to split the remainder evenly) keep
   every pool's table sharing identical column widths. */
.pool-play-table {
  table-layout: fixed;
  width: 100%;
  /* Sum of every column's width/min-width below -- on a viewport narrower
     than this, the table overflows its .table-responsive wrapper and
     scrolls horizontally instead of squeezing the Team columns down to
     nothing (which wraps names into a single vertical letter per line). */
  min-width: 44.5rem;
}
.pool-play-table td,
.pool-play-table th {
  vertical-align: middle;
}
.pool-play-table th:nth-child(1),
.pool-play-table td:nth-child(1) {
  width: 3rem;
}
.pool-play-table th:nth-child(2),
.pool-play-table td:nth-child(2),
.pool-play-table th:nth-child(3),
.pool-play-table td:nth-child(3) {
  min-width: 8rem;
}
.pool-play-table th:nth-child(4),
.pool-play-table td:nth-child(4) {
  width: 4.5rem;
}
.pool-play-table th:nth-child(5),
.pool-play-table td:nth-child(5) {
  width: 5.5rem;
}
.pool-play-table th:nth-child(6),
.pool-play-table td:nth-child(6) {
  width: 4.5rem;
}
.pool-play-table th:nth-child(7),
.pool-play-table td:nth-child(7) {
  width: 11rem;
}
/* Timer -- only present when tournament.pool_timer_active/bracket_timer_enabled, as the 8th/last
   column (public/pool_play.html has no Update/Save column after it,
   unlike the admin table which sizes this via inline style instead).
   Without an explicit width here, table-layout:fixed leaves this column
   unconstrained and its content renders left of where the "Timer" header
   actually sits. */
.pool-play-table th:nth-child(8),
.pool-play-table td:nth-child(8) {
  width: 6.5rem;
}
.match-row-mobile:first-child {
  border-top: none;
}

/* Public Pool Play layout toggle -- table vs. stacked cards, picked
   explicitly via the icon buttons in the page header (see
   initViewModeToggle in live_poll.js) rather than only by viewport width.
   The media query below is just the no-JS/pre-hydration default (cards
   on a narrow screen, table otherwise); #live-content's own view-cards /
   view-table class -- applied as soon as the toggle script runs --
   overrides it immediately via the extra ID + class specificity. */
.pool-card-view {
  display: none;
}
@media (max-width: 767.98px) {
  .pool-table-view {
    display: none;
  }
  .pool-card-view {
    display: block;
  }
}
#live-content.view-table .pool-table-view {
  display: block;
}
#live-content.view-table .pool-card-view {
  display: none;
}
#live-content.view-cards .pool-table-view {
  display: none;
}
#live-content.view-cards .pool-card-view {
  display: block;
}
.view-toggle-group .btn {
  display: inline-flex;
  align-items: center;
}
/* ------------------------------ poolplay table styles ------------------------------ */

/* Admin Tournaments and Categories list layout toggles -- same
   table-vs-cards pattern as the public Pool Play toggle above, just with
   their own container ids so a toggle on one page doesn't reach into the
   other's DOM. Table is the default/no-JS fallback here (unlike Pool
   Play's cards default) since these were desktop-table-first pages
   before the toggle existed. */
.tournament-card-view,
.category-card-view {
  display: none;
}
@media (max-width: 767.98px) {
  .tournament-table-view,
  .category-table-view {
    display: none;
  }
  .tournament-card-view,
  .category-card-view {
    display: block;
  }
}
#tournamentListContent.view-table .tournament-table-view,
#categoryListContent.view-table .category-table-view {
  display: block;
}
#tournamentListContent.view-table .tournament-card-view,
#categoryListContent.view-table .category-card-view {
  display: none;
}
#tournamentListContent.view-cards .tournament-table-view,
#categoryListContent.view-cards .category-table-view {
  display: none;
}
#tournamentListContent.view-cards .tournament-card-view,
#categoryListContent.view-cards .category-card-view {
  display: block;
}

/* ------------------------------ standings table styles ------------------------------ */
/* Same fix as .pool-play-table: each pool is its own <table>, so fix the
   layout and give the narrow stat columns explicit widths (Team column
   left unset to take the remainder) so Pool A/B/C/D line up with each
   other regardless of how long any pool's team names happen to be. */
.standings-table {
  table-layout: fixed;
  width: 100%;
  /* Sum of every column's width/min-width below -- see .pool-play-table's
     comment above for why this prevents the Team column from being
     squeezed to nothing on narrow viewports. */
  min-width: 30.5rem;
}
.standings-table td,
.standings-table th {
  vertical-align: middle;
}
.standings-table th:nth-child(1),
.standings-table td:nth-child(1) {
  width: 3.5rem;
}
.standings-table th:nth-child(2),
.standings-table td:nth-child(2) {
  min-width: 10rem;
}
.standings-table th:nth-child(3),
.standings-table td:nth-child(3) {
  width: 3rem;
}
.standings-table th:nth-child(4),
.standings-table td:nth-child(4) {
  width: 3rem;
}
.standings-table th:nth-child(5),
.standings-table td:nth-child(5) {
  width: 3.5rem;
}
.standings-table th:nth-child(6),
.standings-table td:nth-child(6) {
  width: 3.5rem;
}
.standings-table th:nth-child(7),
.standings-table td:nth-child(7) {
  width: 4rem;
}
/* Rank + stat columns read like a scoreboard: condensed display digits. */
.standings-table td:not(:nth-child(2)) {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.standings-row-mobile:first-child {
  border-top: none;
}
/* ------------------------------ standings table styles ------------------------------ */
/* ------------------------------ bracket table styles ------------------------------ */
/* Wide enough for a full two-player team name ("First Last / First Last")
   without truncating. Below the sidebar's own 992px breakpoint (see the
   media query further down) this is overridden to fill the whole
   scrollable width instead -- one round's cards per screen, full-size and
   easy to tap, swipe/scroll (or use the round-jump nav) to reach the
   next round -- rather than cramming several narrow columns into a phone
   or tablet's width. */
.bracket-col {
  flex: 0 0 auto;
  width: 260px;
}
/* Was a fixed max-width + Bootstrap's .text-truncate (nowrap + ellipsis),
   which clipped anything past ~15 characters -- long team names like
   "Ethan Carter / Liam Brookssss" got cut off mid-word. Letting the name
   grow/shrink as a flex item (with min-width:0 so it can actually shrink
   below its content size instead of overflowing the row) and wrap onto a
   second line instead keeps the full name readable. */
/* Bracket page: "Pool pairings" status panel (admin) -- which pool pairings
   are seeded, ready to seed, or still waiting on pool play. */
.bracket-pairs {
  margin-bottom: 1.25rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background-color: var(--surface);
}
.bracket-pairs-title {
  margin-bottom: 0.6rem;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.bracket-pairs-hint {
  margin-left: 0.4rem;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
}
.bracket-pairs-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.bracket-pair {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 11rem;
  padding: 0.5rem 0.8rem;
  border-radius: 12px;
  border-left: 4px solid var(--border);
  background-color: var(--canvas);
}
.bracket-pair-name {
  font-weight: 700;
  color: var(--ink);
}
.bracket-pair-state {
  font-size: 0.8rem;
  color: var(--muted);
}
.bracket-pair.is-seeded {
  border-left-color: var(--success);
  background-color: var(--success-light);
}
.bracket-pair.is-seeded .bracket-pair-state {
  color: var(--primary);
  font-weight: 600;
}
.bracket-pair-note {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.75rem;
  font-weight: 500;
  color: #8a5a00;
}
.bracket-pair.is-ready {
  border-left-color: var(--warning);
  background-color: var(--warning-light);
}
.bracket-pair.is-ready .bracket-pair-state {
  color: #8a5a00;
  font-weight: 600;
}
.bracket-team-name {
  flex: 1 1 auto;
  min-width: 0;
  display: block;
  overflow-wrap: break-word;
}
/* Admin bracket: the "A - Top 1" tag above each team -- the pool it came from and
   whether it finished 1st or 2nd there. It keeps its line even for a TBD slot, so a
   card doesn't grow (and shift its connector lines) when a winner advances into it. */
.bracket-seed-tag {
  min-height: 1rem;
  margin-top: 0.1rem;
  font-size: 0.72rem;
  line-height: 1rem;
  color: var(--muted);
}
.bracket-seed-tag .seed-pool {
  font-weight: 700;
  color: var(--ink);
}
.bracket-seed-tag .seed-place.is-top1 {
  font-weight: 700;
  color: var(--primary);
}
.bracket-seed-tag .seed-place.is-other {
  font-weight: 600;
  color: #8a5a00;
}
.bracket-score-input {
  width: 55px;
  flex-shrink: 0;
}
.bracket-court-input {
  width: 65px;
  flex-shrink: 0;
}
/* Best-of-2 bracket scoring (any round the tournament opts into -- see
   Tournament.best_of_2_round_set / the bo2_score_grid macro), ADMIN
   view: 3 games (G1/G2/TB) side-by-side don't fit the big score-stepper
   in a 260px bracket column, so this is a compact grid of plain number
   inputs instead. */
.bo2-score-grid-head,
.bo2-score-row {
  display: grid;
  grid-template-columns: 1fr repeat(3, 2.6rem);
  align-items: center;
  gap: 0.35rem;
}
.bo2-score-grid-head {
  font-size: 0.7rem;
  color: var(--muted);
  text-align: center;
  margin-bottom: 0.15rem;
}
.bo2-score-grid-head span:first-child {
  text-align: left;
}
.bo2-score-row {
  margin-bottom: 0.35rem;
}
.bo2-score-input {
  width: 2.6rem;
  padding: 0.25rem 0.1rem;
}
.bo2-hint {
  font-size: 0.72rem;
  line-height: 1.2;
}
/* Applied by bo2ApplyFieldLock() in live_poll.js for BOTH its "readonly"
   and "disabled" lock modes -- native :disabled styling doesn't apply to
   a merely-readonly input, so this covers that case explicitly. Looks
   the same either way; the hint text below each match explains why. */
.bo2-field-locked {
  background-color: var(--canvas);
  opacity: 0.7;
  cursor: not-allowed;
}
/* Best-of-2 bracket scoring, REFEREE view (see the bo2_score_stepper_block
   macro): keeps the same big tap-friendly score-stepper referees use
   for a single-game match, one game per row (Game 1/Game 2/tiebreaker)
   instead of 3 side-by-side, so each stepper stays full-size within a
   normal-width bracket card. */
.bo2-stepper-row {
  display: flex;
  align-items: center;
  /* space-between (not just a fixed gap) so the stepper sits at the row's
     trailing edge instead of packed right up against the G1/G2/TB label
     -- on a full-width mobile card there's a lot of row left over
     otherwise, which read as broken/half-empty rather than intentional. */
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.3rem;
}
.bo2-stepper-label {
  flex: 0 0 1.6rem;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--muted);
}
.bracket-scroll {
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  /* Positioning context for .bracket-connectors-svg below -- it's an
     absolutely-positioned CHILD of this same scrollable element (not a
     sibling) specifically so it scrolls along with the columns instead
     of drifting out of alignment the moment you scroll horizontally. */
  position: relative;
  /* Needed for the horizontal round-by-round scroll -- was Bootstrap's
     .overflow-auto utility (both axes) before; narrowed to X only here.
     Note this narrowing is cosmetic, not a real fix for anything: per
     the CSS overflow spec, setting overflow-x to anything but visible
     forces overflow-y to compute as something other than visible too
     (you can't have one axis scroll and the other stay visible), so
     this element still ends up as a scroll container on both axes
     either way. That's harmless for its own layout (it never actually
     needs to scroll vertically -- the page does), but it does mean it,
     not the viewport, becomes the containing block that any
     position:sticky descendant resolves against -- see .round-header's
     comment for why that broke it and why it's plain (non-sticky) now. */
  overflow-x: auto;
}
.bracket-col {
  scroll-snap-align: start;
  /* Sits above the connector SVG in the same stacking context so a
     card's opaque background always covers the line ends tucked just
     behind its edges, not the other way around. */
  position: relative;
}
/* Mobile + tablet: one round fills the screen instead of several narrow
   columns. `width: 100%` (not a vw-based calc) so it sizes off
   .bracket-scroll's own content-box width -- already exactly the visible
   area, correctly accounting for the sidebar, .container-fluid's padding,
   and any scrollbar -- rather than trying to reproduce that math against
   the viewport and risking a sliver of overflow/underflow. flex-basis
   is the same 100% so it doesn't get squeezed by flex's default sizing;
   flex-shrink: 0 keeps it from shrinking to fit the next column into
   view, which is exactly the multi-column layout this is replacing. */
@media (max-width: 991.98px) {
  .bracket-col {
    flex: 0 0 100%;
    width: 100%;
  }
  .bracket-card .card-body {
    padding: 1rem;
  }
  .bracket-team-name {
    font-size: 1.05rem;
  }
  /* Best-of-2 referee stepper rows (see .bo2-stepper-row above): a
     full-width mobile card has plenty of room, so scale the stepper up
     to match -- bigger tap targets, and the label reads more like a
     normal field caption instead of a cramped abbreviation. */
  .bo2-stepper-label {
    flex-basis: 2.1rem;
    font-size: 0.85rem;
  }
  .bo2-stepper-row .score-stepper-btn {
    width: 3rem;
    height: 3rem;
    font-size: 1.5rem;
  }
  .bo2-stepper-row .score-stepper-input {
    width: 3.5rem;
    font-size: 1.5rem;
  }
}

/* Public Bracket layout toggle -- horizontal tree/connector-line view
   (#bracketTreeScroll) vs. a single scrolling list of every match,
   styled after the referee's own bracket cards (.bracket-list-view) --
   same picked-explicitly-or-by-viewport mechanism as Pool Play's
   card/table toggle (see initViewModeToggle in live_poll.js).

   #bracketTreeScroll carries Bootstrap's .d-flex (display:flex!important),
   so hiding it needs !important too to win the tie -- style.css loads
   after bootstrap.min.css, so source order settles equal-specificity
   !important rules in this file's favor (see .pool-play-table's own
   "min-width" comment for the same pattern elsewhere). The explicit
   #live-content.view-tree rule below it re-shows #bracketTreeScroll at
   any width (not just desktop) when a mobile visitor deliberately picks
   Bracket view -- its extra #live-content + .view-tree prefix outweighs
   the plain media-query rule regardless of importance tier. */
.bracket-list-view {
  display: none;
}
@media (max-width: 767.98px) {
  #bracketTreeScroll {
    display: none !important;
  }
  .bracket-list-view {
    display: block;
  }
}
#live-content.view-tree #bracketTreeScroll {
  display: flex !important;
}
#live-content.view-tree .bracket-list-view {
  display: none;
}
#live-content.view-list #bracketTreeScroll {
  display: none !important;
}
#live-content.view-list .bracket-list-view {
  display: block;
}

/* Admin Bracket layout toggle -- unlike the public page above, this
   doesn't swap between two differently-laid-out views (admin needs the
   score inputs on screen no matter what) -- it's the exact same tree
   layout either way, just with the connector lines hidden in
   .view-no-lines. Scoped to #bracket-live-content specifically (the
   admin/referee page's own container, distinct from the public page's
   #live-content/#bracketTreeScroll) so it can't cross-interact with the
   rules above. */
#bracket-live-content.view-no-lines .bracket-connectors-svg {
  display: none;
}

/* Elbow connector lines between adjacent bracket rounds -- see
   bracket_connectors.js, which sizes/populates this on load, on resize,
   and whenever the bracket content changes. Never intercepts clicks
   (pointer-events: none) since it sits visually behind/between the
   actual match cards, which remain the real interactive elements. */
.bracket-connectors-svg {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  overflow: visible;
}
.bracket-connector-line {
  fill: none;
  /* var(--border) (#e7eaf3) was barely distinguishable from the page's
     own canvas background (#f5f7fb) -- practically invisible in the gap
     between columns, where these lines actually live. --muted matches
     the medium-gray connector lines in reference bracket UIs (Liquipedia
     etc.) and reads clearly against the light background. */
  stroke: var(--muted);
  stroke-width: 2px;
}

/* Small legend above the bracket explaining the connector lines and the
   two visual cues (LIVE badge, winner highlight) already used on every
   match card -- easy to miss/misread on a first glance without it. */
.bracket-guide {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 1.25rem;
  color: var(--muted);
  font-size: 0.8rem;
}
.bracket-guide-sample {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.bracket-guide-line {
  display: inline-block;
  width: 22px;
  height: 2px;
  background-color: var(--muted);
  flex: 0 0 auto;
}
.bracket-guide-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--danger);
  flex: 0 0 auto;
}

/* Public/spectator bracket cards (both the tree view's per-round columns
   and the list view) now reuse the referee-styled public_match_card
   macro -- see .serve-card/.serve-team-row/etc. above -- rather than
   their own bespoke card styles. Bronze Match keeps its own border
   accent to match the .bronze-header it sits under. */
.bracket-card-bronze {
  border-color: var(--bronze);
}
/* ------------------------------ bracket table styles ------------------------------ */

/* ------------------------------ Referee score stepper ------------------------------ */
/* Big tap-friendly -/+ control for referees entering live scores courtside.
   Both the buttons and the number field write to the same input, so a
   referee can either tap to bump the score by one point or type/correct a
   value directly -- whichever's faster in the moment. */
.score-stepper {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.score-stepper-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  line-height: 1;
  border-radius: 50%;
  border: 2px solid var(--primary);
  background-color: var(--primary-light);
  color: var(--primary);
  font-weight: 700;
  user-select: none;
  touch-action: manipulation;
  transition:
    background-color 0.1s ease,
    color 0.1s ease;
}
.score-stepper-btn:hover {
  background-color: var(--primary);
  color: #fff;
}
.score-stepper-btn:active {
  background-color: var(--primary-dark);
  border-color: var(--primary-dark);
  color: #fff;
}
.score-stepper-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  background-color: var(--primary-light);
  color: var(--primary);
}
.score-stepper-input {
  flex-shrink: 0;
  text-align: center;
  font-family: var(--font-display);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  padding: 0.25rem 0.1rem;
  -moz-appearance: textfield;
}
.score-stepper-input::-webkit-outer-spin-button,
.score-stepper-input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* "md" -- used everywhere a referee scores (Pool Play table, Pool Play
   mobile cards, Bracket cards): a comfortable tap target without needing
   extra column/card width to fit it. */
.score-stepper-md .score-stepper-btn {
  width: 2.5rem;
  height: 2.5rem;
  font-size: 1.35rem;
}
.score-stepper-md .score-stepper-input {
  width: 2.75rem;
  font-size: 1.7rem;
}

/* ------------------------------ Live scoring feedback ------------------------------ */
/* Pulsing badge shown next to a match's score while it's been Updated
   (live) but not yet Saved/finalized -- visible to admins/referees and
   on the public spectator pages. The small white dot before the text
   (.live-badge-dot) is added consistently everywhere this badge appears
   -- see the live_badge macro in _macros.html, the single place that
   renders it now. */
.live-badge {
  display: inline-flex;
  align-items: center;
  background-color: var(--danger);
  color: #fff;
  letter-spacing: 0.04em;
  animation: live-pulse 1.6s ease-in-out infinite;
}
/* display: inline-block (rather than relying on the parent's `gap`, which
   only exists while .live-badge stays a flex container) so the dot still
   renders at its intended 6x6 size even where a caller adds Bootstrap's
   .d-inline-block -- that utility class is !important, so it silently
   overrides .live-badge's inline-flex, and CSS ignores width/height on a
   plain (non-flex-item, non-block) inline element entirely. That's the
   admin/referee Bracket view (see bracket/view.html's live_badge() call,
   the only spot that passes an extra display-related class), where the
   dot was invisible even though the badge text/color rendered fine.
   margin-right (not the parent's gap) keeps the dot->text spacing working
   in both display modes. */
.live-badge-dot {
  display: inline-block;
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background-color: #fff;
  margin-right: 0.35rem;
}
@keyframes live-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.55;
  }
}

/* ------------------------------ Per-match timer (see match_timer macro) ------------------------------ */
/* Its own dedicated table column on Pool Play (admin/referee AND public)
   -- kept out of the already-busy Court/Time/Score/Update-Save cells
   rather than stacked on top of them, so both line up cleanly row to
   row. Bracket cards and the Pool Play mobile stack place this same
   block on its own line above the score/action buttons instead. */
.match-timer {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-bottom: 0.35rem;
}
/* Status pill -- same rounded-badge language as .live-badge and the
   payment-status badges elsewhere in the app, so this reads as "a status"
   at a glance rather than a stray line of text competing with the
   Court/Time/Score inputs next to it. */
.match-timer-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.match-timer-pill .icon-inline {
  flex: 0 0 auto;
}
.match-timer-pill.is-running {
  background-color: var(--primary-light);
  color: var(--primary);
}
/* Past the configured length -- the match clock keeps counting (now
   showing "-1:23") until someone actually Saves a score, rather than
   just stopping at zero. Red flags that this match is running long. */
.match-timer-pill.is-running.match-timer-overtime {
  background-color: var(--danger-light);
  color: var(--danger);
}
.match-timer-pill.is-stopped {
  background-color: var(--canvas);
  color: var(--ink);
  border: 1px solid var(--border);
}
/* Blank placeholder cell (public/read-only view, match never timed) --
   faint rather than fully empty, so the Timer column still lines up
   visually instead of leaving a ragged gap on rows that never got one. */
.match-timer-pill.is-empty {
  color: var(--muted);
  font-weight: 400;
  padding-left: 0;
}
.match-timer-start-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}
.match-timer-reset-btn {
  padding-top: 0.15rem;
  padding-bottom: 0.15rem;
}

/* ------------------------------ Referee match summary + full-screen scoring modal ------------------------------ */
/* Compact, tappable row a referee taps to open one match's own
   full-screen scoring modal (see referee_match_summary macro) -- a
   plain <button> styled to read as a card, not a button, matching the
   sporty card language used everywhere else (rounded corners, soft
   lift on hover/focus like .referee-tournament-card). */
.referee-match-summary {
  display: block;
  width: 100%;
  text-align: left;
  background-color: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 0.85rem 1rem;
  margin-bottom: 0.75rem;
  cursor: pointer;
  transition: box-shadow 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.referee-match-summary:hover,
.referee-match-summary:focus-visible {
  box-shadow: 0 8px 24px rgba(23, 32, 63, 0.1);
  transform: translateY(-2px);
  border-color: var(--primary);
  outline: none;
}
.referee-match-summary-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.5rem;
}
.referee-match-summary-done {
  background-color: var(--success-light);
  color: var(--success);
}
.referee-match-summary-team {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  padding: 0.2rem 0;
  font-weight: 600;
  color: var(--ink);
}
.referee-match-summary-score {
  font-weight: 800;
  flex-shrink: 0;
  min-width: 1.5rem;
  text-align: right;
}
.referee-match-summary-bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
}
.referee-match-summary-arrow {
  color: var(--primary);
  display: inline-flex;
}

/* Full-screen scoring modal (see the modal markup around
   referee_serve_card in pool_play/view.html and bracket/view.html) --
   the whole point is a single match filling the entire screen with
   nothing else competing for space, so a referee doesn't have to
   scroll mid-match to find the score controls. */
.referee-scoring-modal .modal-content {
  border: none;
}
/* Bootstrap's .modal-fullscreen sizes itself off `height: 100%` up
   through a `position: fixed` ancestor chain, which iOS Safari resolves
   against the wrong viewport as its address bar shows/hides -- the modal
   ends up shorter than the real visible area, leaving a gap where the
   dimmed page behind it (and, once that's scrollable at all, the page's
   own header/content) shows through. `100dvh` tracks the actual visual
   viewport instead; the plain vh rule stays as a fallback for anything
   that doesn't support dvh. See also the body-scroll-lock in
   match_signature.js, which stops that background page from being
   scrollable/bounceable behind this modal on iOS in the first place. */
.referee-scoring-modal .modal-dialog.modal-fullscreen {
  height: 100vh;
  height: 100dvh;
}
/* Header: same hero treatment as the admin scoresheet modal (see
   .receipt-hero) -- a brand-green band with what's being scored and a big
   round close button, rather than a bare bar. Its content sits in a
   480px column (the same width the scoring card below keeps) so on a
   tablet or laptop the title lines up with the card instead of hugging
   the far-left edge of a full-width bar, with the close button at that
   column's right edge. Safe-area insets keep it clear of a notch / home
   indicator on a phone. */
.referee-scoring-modal-header {
  display: block;
  padding: calc(1.25rem + env(safe-area-inset-top, 0px)) 1.35rem 1.15rem;
  border: 0;
  border-radius: 0;
  color: #fff;
  background: var(--brand-hero-striped);
}
.referee-modal-hero-inner {
  position: relative;
  max-width: 480px;
  margin: 0 auto;
}
.referee-modal-hero-label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.85;
}
.referee-scoring-modal-header .modal-title.referee-modal-hero-title {
  margin: 0.55rem 0 0;
  padding-right: 3rem;
  font-family: var(--font-display);
  font-size: 1.95rem;
  font-style: italic;
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  color: #fff;
}
.referee-modal-hero-sub {
  margin-top: 0.2rem;
  padding-right: 3rem;
  font-size: 0.9rem;
  opacity: 0.85;
}
.referee-modal-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.95rem;
}
.referee-modal-chip {
  display: inline-flex;
  align-items: center;
  padding: 0.22rem 0.65rem;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  background-color: rgba(255, 255, 255, 0.16);
}
/* 44px: a real touch target -- the old bare X was ~16px. Double class so
   this beats Bootstrap's `.modal-header .btn-close` negative margins. */
.referee-scoring-modal-header .referee-modal-close {
  position: absolute;
  top: -0.15rem;
  right: 0;
  width: 2.75rem;
  height: 2.75rem;
  margin: 0;
  padding: 0;
  border-radius: 50%;
  background-color: rgba(255, 255, 255, 0.18);
  background-size: 0.85rem;
  opacity: 1;
}
.referee-scoring-modal-header .referee-modal-close:hover,
.referee-scoring-modal-header .referee-modal-close:focus-visible {
  background-color: rgba(255, 255, 255, 0.3);
}
/* Opens by rising into place rather than Bootstrap's default drop-in from
   above -- a full-screen sheet reads as coming up from the court, not down
   from the browser chrome. */
.referee-scoring-modal.fade .modal-dialog {
  transform: translateY(28px);
}
.referee-scoring-modal.show .modal-dialog {
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  .referee-scoring-modal.fade .modal-dialog {
    transform: none;
  }
}
.referee-scoring-modal-body {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 1.25rem 1.25rem calc(1.25rem + env(safe-area-inset-bottom, 0px));
  background-color: var(--canvas);
}
/* The card keeps its own max-width on a wide screen (a tablet propped
   up at a court, or a laptop) instead of stretching edge to edge -- a
   serve-tracking card that wide would put the two teams' name pills
   and score steppers uncomfortably far apart from each other. */
.referee-scoring-modal-body .serve-card {
  /* Not margin:auto -- auto margins on a flex item override the
     parent's align-items for that item specifically, which is what
     was centering this vertically instead of letting it sit near the
     top like .referee-scoring-modal-body's flex-start intends. */
  width: 100%;
  max-width: 480px;
  margin-left: auto;
  margin-right: auto;
}

/* ------------------------------ Referee serve-tracking card (see referee_serve_card macro) ------------------------------ */
.serve-card .card-body {
  padding: 0.9rem;
}
.serve-subtitle {
  min-height: 1.1rem;
}
.serve-team-row {
  padding: 0.35rem 0;
}
/* Player-name pill -- plain by default, same rounded-badge language as
   .match-timer-pill/.live-badge elsewhere in the app. A plain <button>
   with no border when tappable (fault or first-server pick) so it still
   reads as "text", not a boxed control, until the referee actually needs
   to act on it. */
.serve-name-pill {
  display: inline-block;
  border: none;
  background: none;
  padding: 0.1rem 0.3rem;
  border-radius: 999px;
  font-weight: 500;
  color: var(--ink);
}
button.serve-name-pill {
  cursor: pointer;
}
button.serve-name-pill:hover {
  background-color: var(--canvas);
}
.serve-name-pill.is-serving {
  background-color: var(--primary);
  color: #fff;
  font-weight: 600;
}
/* Awaiting the referee's pick for a team that just won a side-out --
   amber, distinct from both "already serving" (solid primary) and "you
   can tap this to register a fault" (plain, just a hover state). */
.serve-name-pill.is-pickable {
  background-color: var(--warning-light);
  color: var(--warning);
  font-weight: 600;
}
/* This team's tracked right-court player (see Match.team1_right_player_num)
   -- per the real rule, exactly who should serve first -- a ring around
   the already-amber pickable pill, not a new color, since it's an
   indicator layered on top of "you can tap this", not a different kind
   of state. Still just as tappable as its un-ringed partner (a referee
   can always override for a deliberately stacked lineup). Thicker than a
   typical focus ring (3px, not 2px) since this has to read at a glance
   on a phone in a hurry -- a `title` tooltip (the only other way this
   was marked) never shows on a touch device at all, so the ring plus
   the star below are
   the ONLY signal a referee actually gets. */
.serve-name-pill.is-suggested {
  box-shadow: 0 0 0 3px var(--warning);
}
/* Always-visible star next to the suggestion (not just the ring) -- see
   the comment above on why a hover-only cue isn't enough on a phone. */
.serve-suggested-star {
  color: var(--warning);
  font-size: 0.7em;
  margin-left: 0.15em;
}
.serve-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  border-radius: 50%;
  background-color: var(--primary-light);
  color: var(--primary);
  font-size: 0.7rem;
  font-weight: 700;
  margin-left: 0.15rem;
}
.serve-step-btn {
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 50%;
  border: 2px solid var(--primary);
  background-color: var(--primary-light);
  color: var(--primary);
  font-size: 1.1rem;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.serve-step-btn:hover:not(:disabled) {
  background-color: var(--primary);
  color: #fff;
}
.serve-step-btn:disabled {
  opacity: 0.4;
}
.serve-step-value {
  min-width: 1.4rem;
  text-align: center;
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.serve-score-plain {
  display: inline-block;
  min-width: 1.4rem;
  text-align: center;
  color: var(--muted);
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* Once a match is Saved, the winning team's whole row (see
   referee_serve_card) picks up .winner-name for its green/bold styling --
   but .serve-name-pill and .serve-score-plain both set their own `color`
   explicitly (see above), which would otherwise win over the inherited
   green. Let them inherit instead, same "winner reads green" convention
   used everywhere else in the app (.winner-name itself, bracket cards,
   the admin grid). */
.winner-name .serve-name-pill,
.winner-name .serve-score-plain {
  color: inherit;
  font-weight: inherit;
}
/* The gray "server-receiver-server number" summary box -- same neutral
   surface as .match-timer-pill.is-stopped, but sized as its own block
   rather than an inline pill since it's the card's focal readout. */
.serve-score-summary {
  background-color: var(--canvas);
  border-radius: var(--radius-control);
  padding: 0.5rem;
  text-align: center;
  margin-top: 0.5rem;
}
.serve-score-summary-value {
  font-family: var(--font-display);
  font-size: 1.85rem;
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
}
.serve-score-summary-label {
  font-size: 0.7rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.serve-fault-banner {
  background-color: var(--danger-light);
  color: var(--danger);
  border-radius: var(--radius-control);
  padding: 0.5rem 0.65rem;
  font-weight: 600;
  font-size: 0.85rem;
  gap: 0.5rem;
}
/* On a narrow phone there isn't room for both the "Fault -- first serve,
   confirm side out?" message and the button on one line -- without this,
   the flex row squeezed the BUTTON down to fit, wrapping its own "Side
   out" label onto two lines inside the pill. Locking the button to its
   natural single-line size instead makes the message text (the one that
   can actually read fine wrapped) give way first. */
.serve-fault-banner .btn {
  white-space: nowrap;
  flex-shrink: 0;
}
.serve-fault-banner span {
  min-width: 0;
}

/* ------------------------------ Match e-signature scoresheet (see match-scoresheet in referee_serve_card) ------------------------------ */
.match-scoresheet {
  border-top: 1px solid var(--border);
}
.match-signature-pad {
  display: block;
  width: 100%;
  height: 150px;
  border: 1px dashed var(--border);
  border-radius: var(--radius-control);
  background-color: var(--canvas);
  cursor: crosshair;
}
.match-scoresheet-signed {
  background-color: var(--success-light);
  border-radius: var(--radius-control);
  padding: 0.65rem 0.75rem;
}
.match-signature-image {
  display: block;
  width: 100%;
  max-width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-control);
  background-color: #fff;
}

/* ------------------------------ Referee scoring modal: landscape phone ------------------------------
   The card's two column wrappers (see referee_serve_card) are invisible
   wrappers everywhere by default -- `display: contents` makes the browser
   lay their children out exactly as if the wrappers weren't there, so
   portrait, tablet and desktop keep the single tall column unchanged. */
.serve-card-primary,
.serve-card-controls {
  display: contents;
}

/* A phone turned sideways is only ~320-430px tall. Stacked, the full-screen
   modal's hero (~160px) plus the single-column card (~360-490px) left just
   a sliver of scrollable room, with Update/Save pushed off the bottom. So
   on a short landscape screen: the hero collapses to one compact row, and
   the card spreads across the width as two columns -- who's playing and
   their scores on the left, the serve/score readout, court/time, timer and
   Update/Save on the right -- so a whole match fits on screen with no
   scrolling in the common states. Gated on height (not just orientation) so
   a tablet or laptop in landscape keeps the roomy layout. Safe-area insets
   keep everything clear of a notch/rounded corner on the sides. */
@media (orientation: landscape) and (max-height: 520px) {
  .referee-scoring-modal-header {
    padding: 0.55rem max(1rem, env(safe-area-inset-right, 0px)) 0.55rem max(1rem, env(safe-area-inset-left, 0px));
  }
  .referee-modal-hero-inner {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: 0.6rem 0.9rem;
    max-width: 720px;
    min-height: 2.25rem;
    padding-right: 2.9rem;
  }
  .referee-modal-hero-label {
    display: none;
  }
  .referee-scoring-modal-header .modal-title.referee-modal-hero-title {
    flex-shrink: 0;
    margin: 0;
    padding: 0;
    font-size: 1.15rem;
    white-space: nowrap;
  }
  .referee-modal-hero-sub {
    min-width: 0;
    margin: 0;
    padding: 0;
    font-size: 0.8rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .referee-modal-chips {
    flex-shrink: 0;
    flex-wrap: nowrap;
    margin: 0;
  }
  .referee-modal-chip {
    padding: 0.15rem 0.55rem;
    font-size: 0.68rem;
    white-space: nowrap;
  }
  .referee-scoring-modal-header .referee-modal-close {
    top: 50%;
    margin-top: -1.125rem;
    width: 2.25rem;
    height: 2.25rem;
  }

  .referee-scoring-modal-body {
    padding: 0.7rem max(1rem, env(safe-area-inset-right, 0px)) calc(0.7rem + env(safe-area-inset-bottom, 0px))
      max(1rem, env(safe-area-inset-left, 0px));
  }
  .referee-scoring-modal-body .serve-card {
    max-width: 720px;
    margin-bottom: 0;
  }
  .referee-scoring-modal-body .serve-card .card-body {
    display: grid;
    /* Right column: at least 13rem, at most 44% of the card (so a narrow
       phone leaves the names/stepper column most of the width) and never
       wider than 17rem (so a wide phone doesn't over-stretch it). */
    grid-template-columns: minmax(0, 1fr) minmax(13rem, min(44%, 17rem));
    column-gap: 1rem;
    align-items: start;
    padding: 0.7rem 0.85rem;
  }
  .referee-scoring-modal-body .serve-card-primary {
    display: block;
    min-width: 0;
  }
  /* Right column is its own small grid so the court/time text and the timer
     can share one row (each was a full-width row of its own, which is what
     pushed Update/Save toward the bottom edge); everything else in it stays
     a full-width row. With no timer configured, court/time simply sits
     alone in the first column. */
  .referee-scoring-modal-body .serve-card-controls {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 0.5rem;
    align-items: center;
    min-width: 0;
  }
  .serve-card-controls > * {
    grid-column: 1 / -1;
  }
  .serve-card-controls > [data-live-field*="-courttime-"] {
    grid-column: 1;
  }
  .serve-card-controls > .match-timer {
    grid-column: 2;
  }
  /* The e-signature block is the one thing that needs the full width. */
  .referee-scoring-modal-body .serve-card .match-scoresheet {
    grid-column: 1 / -1;
    margin-top: 0.6rem !important;
    padding-top: 0.6rem !important;
  }

  /* Tighten the right-hand column's vertical rhythm. */
  .serve-card-controls .serve-score-summary {
    margin-top: 0;
    padding: 0.35rem;
  }
  .serve-card-controls .serve-score-summary-value {
    font-size: 1.15rem;
  }
  .serve-card-controls .mt-2 {
    margin-top: 0.4rem !important;
  }
  .serve-card-controls .mb-2 {
    margin-bottom: 0.4rem !important;
  }
  .serve-card-controls .serve-fault-banner {
    flex-wrap: wrap;
    gap: 0.4rem;
    padding: 0.4rem 0.55rem;
    font-size: 0.8rem;
  }
  .referee-scoring-modal-body .match-signature-pad {
    height: 96px;
  }
}

/* Admin's match receipt (match_scoresheet_modal in _macros.html): a modern
   scorecard -- brand-green hero with the event, a row of stat tiles, a
   two-row scoreboard with the winner highlighted, the winner's signature,
   and a sticky action bar -- on the same white/rounded/soft-border
   language as the rest of the app. The same markup prints as a plain
   black-on-white receipt (see the @media print block below). */
.receipt-modal .modal-dialog {
  max-width: 400px;
}
.receipt-modal-content {
  position: relative;
  border: 0;
  border-radius: 22px;
  overflow: hidden;
  background-color: var(--surface);
  box-shadow: 0 24px 64px rgba(16, 24, 40, 0.32);
}
.receipt-modal-body {
  padding: 0;
}
.receipt-close {
  position: absolute;
  top: 0.9rem;
  right: 0.9rem;
  z-index: 5;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border-radius: 50%;
  background-color: rgba(255, 255, 255, 0.18);
  background-size: 0.7rem;
  opacity: 1;
}
.receipt-close:hover {
  background-color: rgba(255, 255, 255, 0.3);
}

/* Hero */
.receipt-hero {
  padding: 1.35rem 1.35rem 1.15rem;
  color: #fff;
  background: var(--brand-hero-striped);
}
.receipt-hero-label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.85;
}
.receipt-hero-title {
  margin-top: 0.6rem;
  padding-right: 2.25rem;
  font-family: var(--font-display);
  font-size: 1.65rem;
  font-style: italic;
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}
.receipt-hero-sub {
  margin-top: 0.2rem;
  font-size: 0.9rem;
  opacity: 0.85;
}
.receipt-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.95rem;
}
.receipt-chip {
  display: inline-flex;
  align-items: center;
  padding: 0.22rem 0.65rem;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  background-color: rgba(255, 255, 255, 0.16);
}
.receipt-chip.is-final {
  background-color: #fff;
  color: var(--primary);
}

/* Body */
.receipt-body {
  padding: 1.15rem 1.35rem 1.25rem;
}
.receipt-meta {
  display: grid;
  /* Time ("9:30 AM") is the widest value -- give it the most room so it
     never wraps, including at the 80mm print width. */
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 0.85fr) minmax(0, 1.3fr);
  gap: 0.5rem;
  margin: 0 0 1.1rem;
}
.receipt-meta > div {
  min-width: 0;
  padding: 0.6rem 0.75rem;
  border-radius: 12px;
  background-color: var(--canvas);
}
.receipt-meta dt {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin: 0;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.receipt-meta dd {
  margin: 0.2rem 0 0;
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The Referee tile spans the full width under Match / Court / Time, and --
   unlike those short values -- holds a person's name, which can be long: let
   it wrap instead of truncating. */
.receipt-meta .receipt-meta-wide {
  grid-column: 1 / -1;
}
.receipt-meta .receipt-meta-wide dd {
  white-space: normal;
  overflow-wrap: anywhere;
}
.receipt-meta dd.is-empty {
  font-weight: 500;
  color: var(--muted);
}

/* Scoreboard */
.receipt-scoreboard {
  border: 1px solid var(--border);
  border-radius: 16px;
  overflow: hidden;
}
.receipt-scoreboard-head {
  display: flex;
  justify-content: flex-end;
  gap: 0.25rem;
  padding: 0.5rem 0.95rem 0;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.receipt-scoreboard-head span {
  min-width: 2.25rem;
  text-align: right;
}
.receipt-team {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.8rem 0.95rem 0.8rem 0.8rem;
  border-left: 4px solid transparent;
}
.receipt-team + .receipt-team {
  border-top: 1px solid var(--border);
}
.receipt-team.is-winner {
  border-left-color: var(--success);
  background-color: var(--success-light);
}
.receipt-team-names {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.receipt-player {
  font-weight: 600;
  line-height: 1.35;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.receipt-winner-tag {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  margin-top: 0.4rem;
  padding: 0.14rem 0.55rem;
  border-radius: 999px;
  font-size: 0.64rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--primary);
  background-color: #fff;
}
.receipt-team-scores {
  display: flex;
  gap: 0.25rem;
  flex-shrink: 0;
}
.receipt-team-scores span {
  min-width: 2.25rem;
  text-align: right;
  font-size: 1.75rem;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.receipt-scoreboard.is-multi .receipt-team-scores span {
  font-size: 1.3rem;
}
.receipt-team.is-winner .receipt-team-scores span {
  color: var(--primary);
}

/* Signature */
.receipt-section-label {
  margin: 1.2rem 0 0.5rem;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.receipt-signature {
  padding: 0.85rem 0.95rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 16px;
  background-color: #fff;
}
.receipt-signature-img {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: 110px;
  margin: 0 auto;
  object-fit: contain;
}
.receipt-signature-line {
  margin: 0.4rem 0 0.55rem;
  border-top: 1px solid var(--border);
}
.receipt-signature-name {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink);
}
.receipt-signature-time {
  margin-top: 0.15rem;
  text-align: center;
  font-size: 0.68rem;
  color: var(--muted);
}
.receipt-signature-empty {
  padding: 1rem;
  border: 1px dashed var(--border);
  border-radius: 16px;
  text-align: center;
  font-size: 0.85rem;
  color: var(--muted);
  background-color: var(--canvas);
}

/* Footer stamp */
.receipt-foot {
  margin-top: 1.2rem;
  padding-top: 0.95rem;
  border-top: 1px dashed var(--border);
  text-align: center;
  font-size: 0.7rem;
  line-height: 1.6;
  color: var(--muted);
}
.receipt-foot-id {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ink);
}

/* Action bar */
.receipt-modal-footer {
  flex-wrap: nowrap;
  gap: 0.6rem;
  padding: 0.9rem 1.35rem;
  border-top: 1px solid var(--border);
  background-color: var(--surface);
}
.receipt-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  margin: 0;
  padding: 0.65rem 1rem;
  border-radius: 12px;
  font-weight: 600;
}
.receipt-modal-footer .scoresheet-receipt-print-btn {
  flex: 1;
}

/* Printing an open receipt: exactly ONE page containing just the receipt.

   The previous version hid everything with `visibility: hidden`, which
   only makes elements invisible -- they still take up layout space, so on
   a real tournament (hundreds of matches behind the modal) the browser
   still paginated the entire hidden page: dozens of mostly-blank sheets,
   and because the open modal is `position: fixed` the receipt repeated on
   every one of them, vertically centered and cut off at the page bottom.

   Instead, everything that isn't the open receipt modal (or one of its
   ancestors) is taken out of the layout with `display: none`, and the
   ancestors -- the page shell, the modal and its dialog -- are flattened
   to plain static blocks, so nothing is left to spill onto a second page
   or to repeat. Every selector is gated on a receipt modal being open
   (.receipt-modal.show), so it never affects any other printout (check-in
   sheet, QR page) and a closed receipt modal can't print in place of the
   open one. Deliberately no @page rule: paper size and margins stay
   whatever the user's printer/browser chose (A4, Letter, or an 80mm
   receipt roll), and the receipt just fits inside them. Relies on :has(),
   which every current browser supports; an older one simply ignores these
   rules and prints as it used to, rather than breaking.

   (:has(.receipt-modal.show) only matches elements ABOVE the modal, since
   the .show has to be a descendant of the element being tested -- that's
   why the modal / dialog / content / body are listed explicitly in the
   flatten rule below instead of being caught by it.) */
@media print {
  /* Not the open receipt modal, not on the way to it: gone from the layout
     entirely (page chrome, every other modal, the backdrop, toasts...). */
  body:has(.receipt-modal.show) *:not(:has(.receipt-modal.show)):not(.receipt-modal.show):not(.receipt-modal.show *),
  /* ...and the screen-only controls inside the receipt modal itself. */
  .receipt-modal.show .receipt-close,
  .receipt-modal.show .receipt-modal-footer {
    display: none !important;
  }
  /* What remains above and around the receipt: strip everything that
     positions, sizes, centers, clips or decorates it, so the receipt just
     flows at the top of the printable area. */
  html:has(.receipt-modal.show),
  body:has(.receipt-modal.show),
  body:has(.receipt-modal.show) *:has(.receipt-modal.show),
  .receipt-modal.show,
  .receipt-modal.show .modal-dialog,
  .receipt-modal.show .modal-content,
  .receipt-modal.show .modal-body {
    position: static !important;
    display: block !important;
    float: none !important;
    width: auto !important;
    max-width: none !important;
    height: auto !important;
    min-height: 0 !important;
    margin: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    overflow: visible !important;
    transform: none !important;
    opacity: 1 !important;
    background: none !important;
    box-shadow: none !important;
  }
  /* The receipt itself: capped at 80mm (a standard receipt-roll width,
     and about what it measures on screen), centered, never split across
     pages. Plain white/black -- printers drop background colors by
     default, which would leave the hero's white text invisible. */
  .receipt-modal.show .scoresheet-receipt {
    width: 100%;
    max-width: 80mm;
    margin: 0 auto;
    color: #000;
    background: #fff;
    border: 1px dashed #888;
    break-inside: avoid;
    page-break-inside: avoid;
  }
  .receipt-modal.show .receipt-hero {
    padding: 3mm 4mm 2.5mm;
    color: #000;
    background: none;
    border-bottom: 1px solid #000;
  }
  .receipt-modal.show .receipt-hero-label,
  .receipt-modal.show .receipt-hero-sub {
    opacity: 1;
  }
  /* "Official Match Scoresheet" is long enough that its screen letter-spacing
     wraps it onto two lines on a narrow receipt roll. */
  .receipt-modal.show .receipt-hero-label {
    letter-spacing: 0.06em;
    font-size: 0.62rem;
  }
  .receipt-modal.show .receipt-hero-title {
    padding-right: 0;
    font-size: 1.1rem;
  }
  .receipt-modal.show .receipt-chip {
    color: #000;
    background: none;
    border: 1px solid #000;
  }
  .receipt-modal.show .receipt-body {
    padding: 3mm 4mm;
  }
  /* On a narrow receipt roll the usable width can be under 60mm once the
     printer's own margins are taken off -- size the three tiles to their
     content and drop their icons so labels never collide or clip. */
  .receipt-modal.show .receipt-meta {
    grid-template-columns: auto auto 1fr;
    gap: 1.5mm;
  }
  .receipt-modal.show .receipt-meta > div {
    padding: 1.5mm 2mm;
    background: none;
    border: 1px solid #bbb;
  }
  .receipt-modal.show .receipt-meta dt {
    letter-spacing: 0.04em;
  }
  .receipt-modal.show .receipt-meta dt svg {
    display: none;
  }
  .receipt-modal.show .receipt-meta dd.is-empty {
    color: #444;
  }
  /* Tighter vertical rhythm on paper than on screen -- the receipt has to
     stay on ONE page even with long names (wrapped team names, a long
     referee name), and a fixed-height page has no slack for airy spacing. */
  .receipt-modal.show .receipt-meta {
    margin-bottom: 0.75rem;
  }
  .receipt-modal.show .receipt-team {
    padding-top: 0.55rem;
    padding-bottom: 0.55rem;
  }
  .receipt-modal.show .receipt-section-label {
    margin: 0.85rem 0 0.4rem;
  }
  .receipt-modal.show .receipt-foot {
    margin-top: 0.8rem;
    padding-top: 0.6rem;
  }
  .receipt-modal.show .receipt-meta dd {
    overflow: visible;
    text-overflow: clip;
  }
  .receipt-modal.show .receipt-meta dt,
  .receipt-modal.show .receipt-scoreboard-head,
  .receipt-modal.show .receipt-section-label,
  .receipt-modal.show .receipt-foot,
  .receipt-modal.show .receipt-signature-time,
  .receipt-modal.show .receipt-signature-empty {
    color: #444;
  }
  .receipt-modal.show .receipt-scoreboard,
  .receipt-modal.show .receipt-signature {
    border-color: #888;
    break-inside: avoid;
  }
  .receipt-modal.show .receipt-team {
    break-inside: avoid;
  }
  .receipt-modal.show .receipt-team.is-winner {
    background: none;
    border-left-color: #000;
  }
  .receipt-modal.show .receipt-player,
  .receipt-modal.show .receipt-team-scores span,
  .receipt-modal.show .receipt-team.is-winner .receipt-team-scores span,
  .receipt-modal.show .receipt-meta dd,
  .receipt-modal.show .receipt-signature-name,
  .receipt-modal.show .receipt-foot-id {
    color: #000;
  }
  .receipt-modal.show .receipt-winner-tag {
    color: #000;
    background: none;
    border: 1px solid #000;
  }
  .receipt-modal.show .receipt-signature-empty {
    background: none;
  }
  /* The signature is the one part of the receipt whose height isn't fixed
     by its text: bound it so a tall signature can't push the receipt onto
     a second page. */
  .receipt-modal.show .receipt-signature-img {
    max-height: 24mm;
  }
  .receipt-modal.show .receipt-signature {
    padding: 2.5mm 3mm 2mm;
  }
}

/* Brief highlight on the row/card that was just saved via AJAX -- visible
   feedback right where the user is looking, independent of the toast
   notification. Applied to <td> cells rather than <tr> itself (and to
   bracket .card elements directly): box-shadow/background animations on
   table rows are unreliable across browsers, since <tr> isn't a normal
   CSS box the way <td> and block elements are. */
@keyframes save-flash-anim {
  0% {
    background-color: var(--success-light);
  }
  100% {
    background-color: transparent;
  }
}
.save-flash-cell {
  animation: save-flash-anim 1.2s ease-out;
}
.save-flash-card {
  animation: save-flash-anim 1.2s ease-out;
}

/* ------------------------------ Login splash screen ------------------------------ */
/* Brief animated intro shown before the login form -- logo + loading bar.
   Pure CSS keyframe animation, no JavaScript: it can never get stuck open
   if a script fails elsewhere on the page, and it works even with JS
   disabled. `pointer-events: none` engages at the very end so the form
   underneath becomes clickable the instant the overlay is gone. */
.splash-screen {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  background: var(--brand-hero-striped);
  animation: splash-lifecycle 5s ease forwards;
}
/* Soft yellow glow behind the medallion. */
.splash-screen::before {
  content: "";
  position: absolute;
  width: 34rem;
  height: 34rem;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgba(245, 207, 30, 0.22) 0%,
    rgba(245, 207, 30, 0) 62%
  );
  pointer-events: none;
}
/* Product caption under the loading bar. */
.splash-screen::after {
  content: "Tournament Manager";
  position: relative;
  color: rgba(255, 255, 255, 0.85);
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-style: italic;
  font-weight: 800;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}

/* The logo sits on a white rounded medallion so its dark-green ribbon reads
   against the green screen. */
.splash-logo {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.6rem 2rem;
  border-radius: 32px;
  background: var(--surface);
  box-shadow: 0 30px 60px -24px rgba(0, 0, 0, 0.55);
}
.splash-logo img {
  display: block;
  width: min(240px, 60vw);
  height: auto;
}

.splash-logo-icon {
  font-size: 2.5rem;
  line-height: 1;
}

.splash-bar-track {
  position: relative;
  width: 220px;
  height: 6px;
  border-radius: 999px;
  background-color: rgba(255, 255, 255, 0.22);
  overflow: hidden;
}

.splash-bar-fill {
  height: 100%;
  width: 45%;
  border-radius: 999px;
  background-color: var(--accent);
  animation: splash-bar-slide 1.1s ease-in-out infinite;
}

@keyframes splash-bar-slide {
  0% {
    transform: translateX(-120%);
  }
  100% {
    transform: translateX(320%);
  }
}

/* Single timeline for the whole overlay: fully visible for the first
   ~70%, fades over the remainder, then gets out of the way entirely. */
@keyframes splash-lifecycle {
  0% {
    opacity: 1;
  }
  99.9% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .splash-screen {
    animation-duration: 0.4s;
  }
  .splash-bar-fill {
    animation: none;
    width: 100%;
  }
}

.skip-splash .splash-screen {
  display: none !important;
  animation: none !important;
}
