/* ============================================================
   BACK9OPS BRAND COMPONENTS
   Reusable admin UI patterns built on top of design tokens.
   These styles establish the visual language for all admin
   surfaces: dashboards, tables, forms, modals, and cards.
   ============================================================ */

/* Player-facing interactive text guardrail.
  Prevents regressions where actionable text falls below base readability. */
:where(.pfeed-btn-primary, .pfeed-btn-secondary, .mp-empty-cta) {
  font-size: var(--font-size-base, 1rem);
}


/* ── Page shell ─────────────────────────────────────────────── */
.b9-page {
  background: var(--b9-surface);
  min-height: 100%;
  color:      var(--b9-text);
  font-family: var(--b9-font-sans);
}

.b9-page-body {
  max-width:  1200px;
  margin:     0 auto;
  padding:    var(--b9-page-padding-y) var(--b9-page-padding-x);
}

.b9-page-body-wide {
  padding: var(--b9-page-padding-y) var(--b9-page-padding-x);
}


/* ── Page header ────────────────────────────────────────────── */
.b9-page-header {
  display:     flex;
  align-items: flex-start;
  justify-content: space-between;
  gap:         1rem;
  flex-wrap:   wrap;
  margin-bottom: 1.5rem;
}

.b9-page-title {
  font-size:      1.5rem;
  font-weight:    800;
  color:          var(--text-primary);
  letter-spacing: -0.03em;
  margin:         0;
  line-height:    1.2;
}

.b9-page-subtitle {
  font-size:  0.875rem;
  color:      var(--text-secondary);
  margin-top: 0.3rem;
  line-height: 1.5;
}

.b9-page-actions {
  display:     flex;
  align-items: center;
  gap:         0.5rem;
  flex-wrap:   wrap;
  flex-shrink: 0;
}


/* ── Dashboard metric / stat card ──────────────────────────── */
.b9-stat-card {
  background:    var(--bg-surface);
  border:        1px solid var(--border-subtle);
  border-radius: var(--b9-card-radius);
  box-shadow:    var(--b9-card-shadow);
  padding:       1.35rem 1.5rem;
  display:       flex;
  flex-direction: column;
  gap:           0.4rem;
  position:      relative;
  overflow:      hidden;
  transition:    box-shadow 0.18s ease, border-color 0.18s ease, transform 0.15s ease;
}

.b9-stat-card:hover {
  box-shadow:    var(--b9-card-shadow-hover);
  border-color:  var(--border-default);
  transform:     translateY(-1px);
}

.b9-stat-label {
  font-size:      0.72rem;
  font-weight:    800;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color:          var(--text-secondary);
}

.b9-stat-value {
  font-size:   2rem;
  font-weight: 800;
  color:       var(--text-primary);
  line-height: 1;
  letter-spacing: -0.04em;
}

.b9-stat-delta {
  font-size:  0.78rem;
  font-weight: 500;
  color:      var(--text-secondary);
}

.b9-stat-icon {
  position:  absolute;
  top:       1.1rem;
  right:     1.1rem;
  width:     38px;
  height:    38px;
  border-radius: 10px;
  display:   flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
}

/* Stat card accent variants */
.b9-stat-blue   .b9-stat-icon { background: var(--brand-soft); color: var(--b9-primary-text); }
.b9-stat-green  .b9-stat-icon { background: var(--b9-accent-green-soft); color: var(--b9-accent-green); }
.b9-stat-amber  .b9-stat-icon { background: var(--b9-warning-soft); color: var(--b9-warning); }
.b9-stat-red    .b9-stat-icon { background: var(--b9-danger-soft); color: var(--b9-danger); }

.b9-stat-blue  { border-top: 2px solid var(--brand); }
.b9-stat-green { border-top: 2px solid var(--b9-accent-green); }
.b9-stat-amber { border-top: 2px solid var(--b9-warning); }
.b9-stat-red   { border-top: 2px solid var(--b9-danger); }


/* ── Section header ─────────────────────────────────────────── */
.b9-section-header {
  display:       flex;
  align-items:   center;
  gap:           0.65rem;
  margin-bottom: 1rem;
}

.b9-section-icon {
  width:           34px;
  height:          34px;
  border-radius:   9px;
  display:         flex;
  align-items:     center;
  justify-content: center;
  font-size:       0.95rem;
  flex-shrink:     0;
}

.b9-section-title {
  font-size:   0.95rem;
  font-weight: 700;
  color:       var(--text-primary);
  margin:      0;
  flex:        1;
  letter-spacing: -0.01em;
}

.b9-section-action {
  font-size:   0.78rem;
  font-weight: 600;
  color:       var(--b9-primary-text);
  text-decoration: none;
  margin-left: auto;
}

.b9-section-action:hover {
  color: var(--b9-primary-text);
  text-decoration: underline;
}


/* ── Data / admin tables ────────────────────────────────────── */
.b9-table-wrap {
  overflow-x:    auto;
  border-radius: var(--b9-card-radius);
  border:        1px solid var(--border-subtle);
  box-shadow:    var(--b9-card-shadow);
  background:    var(--bg-surface);
  transition:    border-color 0.18s;
}

.b9-table-wrap:hover {
  border-color: var(--border-default);
}

.b9-table {
  width:           100%;
  border-collapse: collapse;
  font-size:       0.875rem;
  color:           var(--text-primary);
}

.b9-table thead th {
  background:     var(--bg-elevated);
  color:          var(--text-secondary);
  font-size:      0.68rem;
  font-weight:    800;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  padding:        0.7rem 1rem;
  border-bottom:  1px solid var(--border-subtle);
  white-space:    nowrap;
  text-align:     left;
}

.b9-table tbody td {
  padding:        0.75rem 1rem;
  border-bottom:  1px solid var(--border-subtle);
  vertical-align: middle;
  color:          var(--text-primary);
}

.b9-table tbody tr:last-child td {
  border-bottom: none;
}

.b9-table tbody tr:hover td {
  background: rgba(255,255,255,0.03);
}

.b9-table-actions {
  display:    flex;
  gap:        0.4rem;
  align-items: center;
}

.b9-table-actions .btn,
.b9-table-actions .ui-btn,
.b9-table-actions a,
.b9-table-actions button {
  min-height: var(--touch-target-min, 48px);
  min-width: var(--touch-target-min, 48px);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}


/* ── Badges / chips ─────────────────────────────────────────── */
.b9-chip {
  display:       inline-flex;
  align-items:   center;
  gap:           0.3rem;
  font-size:     0.7rem;
  font-weight:   700;
  padding:       3px 10px;
  border-radius: 20px;
  white-space:   nowrap;
  letter-spacing: 0.015em;
  border:        1px solid transparent;
}

.b9-chip-outline {
  background: transparent;
  border-color: var(--b9-border-strong);
  color: var(--b9-text-muted);
}

/* Event status chips — dark-optimised */
.b9-chip-draft    { background: rgba(255,255,255,0.07); color: var(--text-secondary); border-color: var(--border-subtle); }
.b9-chip-open     { background: var(--b9-accent-green-soft); color: var(--b9-accent-green-text); border-color: rgba(34,197,94,0.2); }
.b9-chip-active   { background: var(--b9-danger-soft); color: var(--b9-danger-text); border-color: rgba(239,68,68,0.2); }
.b9-chip-closed   { background: var(--brand-soft); color: var(--b9-primary-text); border-color: rgba(29,78,216,0.25); }
.b9-chip-cancelled{ background: rgba(255,255,255,0.05); color: var(--text-secondary); border-color: var(--border-subtle); text-decoration: line-through; }


/* ── Empty state card ───────────────────────────────────────── */
.b9-empty-state {
  display:        flex;
  flex-direction: column;
  align-items:    center;
  justify-content: center;
  text-align:     center;
  padding:        4rem 2rem;
  background:     var(--bg-surface);
  border:         1px solid var(--border-subtle);
  border-radius:  var(--b9-card-radius);
  box-shadow:     var(--b9-card-shadow);
}

.b9-empty-icon {
  font-size:     2.75rem;
  margin-bottom: 1rem;
  opacity:       0.55;
}

.b9-empty-title {
  font-size:   1rem;
  font-weight: 700;
  color:       var(--text-primary);
  margin:      0 0 0.4rem;
  letter-spacing: -0.01em;
}

.b9-empty-body {
  font-size:  0.875rem;
  color:      var(--text-secondary);
  max-width:  380px;
  margin:     0 auto 1.5rem;
  line-height: 1.65;
}


/* ── Filter / toolbar bar ───────────────────────────────────── */
.b9-filter-bar {
  display:       flex;
  align-items:   center;
  gap:           0.65rem;
  flex-wrap:     wrap;
  padding:       0.75rem 1rem;
  background:    var(--bg-elevated);
  border:        1px solid var(--border-subtle);
  border-radius: var(--b9-card-radius);
  margin-bottom: 1rem;
}

.b9-filter-search {
  position: relative;
  flex:     1;
  min-width: 180px;
}

.b9-filter-search-icon {
  position:  absolute;
  left:      0.75rem;
  top:       50%;
  transform: translateY(-50%);
  color:     var(--b9-text-soft);
  font-size: 0.85rem;
}

.b9-filter-input {
  width:         100%;
  padding:       0.45rem 0.85rem 0.45rem 2.2rem;
  border:        1.5px solid var(--b9-input-border);
  border-radius: 8px;
  background:    var(--b9-input-bg);
  color:         var(--b9-text);
  font-size:     0.875rem;
  outline:       none;
  transition:    border-color 0.15s, box-shadow 0.15s;
}

.b9-filter-input::placeholder {
  color: var(--b9-input-placeholder);
}

.b9-filter-input:focus {
  border-color: var(--b9-input-border-focus);
  box-shadow:   var(--b9-focus-ring);
  background:   var(--b9-surface-alt);
}


/* ── Modal / drawer overlay ─────────────────────────────────── */
.b9-modal-overlay {
  position:   fixed;
  inset:      0;
  background: rgba(15,23,42,0.55);
  backdrop-filter: blur(2px);
  z-index:    1040;
  display:    flex;
  align-items: center;
  justify-content: center;
  padding:    1rem;
}

.b9-modal {
  background:    var(--b9-card-bg);
  border:        1.5px solid var(--b9-card-border);
  border-radius: 16px;
  box-shadow:    0 20px 60px rgba(15,23,42,0.20);
  width:         100%;
  max-width:     560px;
  max-height:    90vh;
  overflow-y:    auto;
  display:       flex;
  flex-direction: column;
}

.b9-modal-header {
  display:       flex;
  align-items:   center;
  gap:           0.75rem;
  padding:       1.25rem 1.5rem 1rem;
  border-bottom: 1px solid var(--b9-border);
  flex-shrink:   0;
}

.b9-modal-title {
  font-size:   1rem;
  font-weight: 800;
  color:       var(--b9-text);
  margin:      0;
  flex:        1;
}

.b9-modal-body {
  padding:    1.25rem 1.5rem;
  flex:       1;
}

.b9-modal-footer {
  display:        flex;
  justify-content: flex-end;
  gap:            0.5rem;
  padding:        0.85rem 1.5rem 1.25rem;
  border-top:     1px solid var(--b9-border);
  flex-shrink:    0;
}


/* ── Side drawer ────────────────────────────────────────────── */
.b9-drawer-overlay {
  position:        fixed;
  inset:           0;
  background:      rgba(2,6,23,0.7);
  backdrop-filter: blur(3px);
  z-index:         1040;
}

.b9-drawer {
  position:   fixed;
  top:        0;
  right:      0;
  bottom:     0;
  width:      min(420px, 95vw);
  background: var(--bg-surface);
  border-left: 1px solid var(--border-subtle);
  box-shadow: -8px 0 40px rgba(0,0,0,0.55), -1px 0 0 rgba(255,255,255,0.06);
  z-index:    1050;
  display:    flex;
  flex-direction: column;
  overflow:   hidden;
}

.b9-drawer-header {
  display:       flex;
  align-items:   center;
  gap:           0.75rem;
  padding:       1.25rem 1.5rem;
  border-bottom: 1px solid var(--b9-border);
  flex-shrink:   0;
}

.b9-drawer-title {
  font-size:   1rem;
  font-weight: 800;
  color:       var(--b9-text);
  margin:      0;
  flex:        1;
}

.b9-drawer-body {
  flex:       1;
  overflow-y: auto;
  padding:    1.25rem 1.5rem;
}

.b9-drawer-footer {
  padding:    1rem 1.5rem;
  border-top: 1px solid var(--b9-border);
  display:    flex;
  gap:        0.5rem;
  justify-content: flex-end;
  flex-shrink: 0;
}


/* ── Form sections ──────────────────────────────────────────── */
.b9-form-section {
  background:    var(--bg-surface);
  border:        1px solid var(--border-subtle);
  border-radius: var(--b9-card-radius);
  box-shadow:    var(--b9-card-shadow);
  margin-bottom: 1.5rem;
  overflow:      hidden;
  transition:    border-color 0.18s;
}

.b9-form-section-header {
  padding:        0.9rem 1.35rem;
  background:     var(--bg-elevated);
  border-bottom:  1px solid var(--border-subtle);
  font-size:      0.7rem;
  font-weight:    800;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color:          var(--text-secondary);
}

.b9-form-section-body {
  padding: 1.35rem;
}

/* Form control overrides using tokens */
.b9-input,
.fo-input {
  display:       block;
  width:         100%;
  padding:       10px 13px;
  background:    var(--bg-elevated);
  border:        1px solid var(--border-default);
  border-radius: 8px;
  color:         var(--text-primary);
  font-size:     0.875rem;
  font-family:   var(--b9-font-sans);
  outline:       none;
  transition:    border-color 0.15s, box-shadow 0.15s, background 0.15s;
  line-height:   1.5;
}

.b9-input::placeholder,
.fo-input::placeholder {
  color:   var(--text-muted);
  opacity: 1;
}

.b9-input:hover:not(:disabled),
.fo-input:hover:not(:disabled) {
  border-color: var(--border-strong);
  background:   var(--bg-elevated);
}

.b9-input:focus,
.fo-input:focus {
  border-color: var(--brand);
  box-shadow:   0 0 0 3px var(--brand-ring);
  background:   var(--bg-elevated);
}

.b9-input:disabled,
.fo-input:disabled {
  opacity:    0.72;
  cursor:     not-allowed;
  background: rgba(255,255,255,0.03);
}

/* ── Label ── */
.b9-label,
.fo-label {
  font-size:      0.78rem;
  font-weight:    700;
  color:          var(--text-helper, var(--text-secondary));
  margin-bottom:  0.4rem;
  display:        block;
  line-height:    1.4;
  letter-spacing: 0.015em;
}

/* ── Form field wrapper (label + input + gap) ── */
.fo-field {
  display:        flex;
  flex-direction: column;
  gap:            6px;
  margin-bottom:  16px;
}

.fo-field:last-child { margin-bottom: 0; }

/* ── Form hint text ── */
.fo-hint {
  font-size:  0.775rem;
  color:      var(--text-helper, var(--text-secondary));
  margin-top: 4px;
  line-height: 1.45;
}


/* ── Activity timeline ──────────────────────────────────────── */
.b9-timeline {
  display:        flex;
  flex-direction: column;
  gap:            0;
  position:       relative;
}

.b9-timeline-item {
  display:     flex;
  gap:         0.85rem;
  padding-bottom: 1.25rem;
  position:    relative;
}

.b9-timeline-item::before {
  content:    '';
  position:   absolute;
  left:       15px;
  top:        28px;
  bottom:     0;
  width:      2px;
  background: var(--b9-border);
}

.b9-timeline-item:last-child::before {
  display: none;
}

.b9-timeline-dot {
  width:         32px;
  height:        32px;
  border-radius: 50%;
  display:       flex;
  align-items:   center;
  justify-content: center;
  flex-shrink:   0;
  font-size:     0.85rem;
  background:    var(--bg-elevated);
  border:        1px solid var(--border-subtle);
  color:         var(--text-secondary);
  z-index:       1;
}

.b9-timeline-content {
  flex:       1;
  padding-top: 4px;
}

.b9-timeline-title {
  font-size:   0.875rem;
  font-weight: 600;
  color:       var(--b9-text);
}

.b9-timeline-meta {
  font-size: 0.75rem;
  color:     var(--b9-text-soft);
  margin-top: 2px;
}


/* ── Leaderboard highlight row ──────────────────────────────── */
.b9-lb-rank-1 { background: linear-gradient(90deg, rgba(245,158,11,0.10), transparent); }
.b9-lb-rank-2 { background: linear-gradient(90deg, rgba(156,163,175,0.10), transparent); }
.b9-lb-rank-3 { background: linear-gradient(90deg, rgba(180,83,9,0.10), transparent); }

.b9-lb-medal-1 { color: #F59E0B; }
.b9-lb-medal-2 { color: #9CA3AF; }
.b9-lb-medal-3 { color: #D97706; }


/* ── Responsive grid helpers ────────────────────────────────── */
.b9-grid-stats {
  display:               grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap:                   1rem;
}

.b9-grid-cards {
  display:               grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap:                   1rem;
}

@media (max-width: 640px) {
  .b9-grid-stats {
    grid-template-columns: repeat(2, 1fr);
  }
  .b9-grid-cards {
    grid-template-columns: 1fr;
  }
  .b9-page-header {
    flex-direction: column;
  }
}

/* ════════════════════════════════════════════════════════════════════
   SPONSOR MARK  (Components/Shared/SponsorMark.razor)

   One sponsor's mark, drawn one of two ways. A sponsor who sent artwork
   gets .sponsor-mark-logo; a sponsor who did not gets .sponsor-mark-plate
   — a NAMEPLATE, which is what their physical tee-box sign looks like.
   At a community outing most sponsors never send a file, so the plate is
   the normal case, not an error state.

   Both treatments also carry the host surface's own logo class, so each
   board keeps its own sizing and a plate stays the same size as the logo
   next to it. These stay LIGHT in every theme on purpose: the plate is an
   image surface standing in for printed sign stock, not app chrome.
   ════════════════════════════════════════════════════════════════════ */
.sponsor-mark-plate {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  padding: 0.35rem 0.6rem;
  background: #FBFAF6;
  color: #14181A;
  border: 2px solid #14181A;
  border-radius: 6px;
  overflow: hidden;
}

.sponsor-mark-plate .sponsor-plate-text {
  font-weight: 800;
  font-size: 0.62rem;
  line-height: 1.05;
  letter-spacing: 0.01em;
  text-transform: uppercase;
  text-align: center;
  /* Long business names ("Lakeshore Real Estate Krage & Associates") must wrap
     inside the plate rather than push the row wide or spill outside it. */
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The TV board is read from across a room — bigger plate type than a phone. */
.tv-sponsor-logo.sponsor-mark-plate .sponsor-plate-text {
  font-size: clamp(0.6rem, 1.05vw, 1.05rem);
}

/* ════════════════════════════════════════════════════════════════════
   TEE-BOX SPONSOR BAND  (Components/Shared/TeeBoxSponsorBand.razor)

   Sits above score entry on the web scorecard and the QR guest page.
   Global (not scoped) so both hosts share one definition — and because a
   scoped rule would not reach <SponsorMark />'s markup anyway.
   ════════════════════════════════════════════════════════════════════ */
/* The tee-box sign, set as part of the hole's identity rather than as an advert.
   It was a tinted, bordered, stacked band with an uppercase "THIS HOLE BROUGHT TO YOU BY"
   eyebrow — which is exactly the small, differently-styled top-of-screen box that NN/g
   eye-tracking finds readers classify as promotional and skip. Now it reads as hole detail:
   no box, no border, no accent eyebrow, one line, a fraction of the height. Matches the phone. */
.tbs-band {
  margin: 0 0 0.5rem;
}

.tbs-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.15rem 0.75rem;
}

.tbs-item { display: flex; align-items: center; gap: 0.4rem; min-width: 0; }

/* Only drawn for a sponsor that actually sent artwork; a logo-less business is its name alone,
   because printing a nameplate AND the same name beside it says everything twice. */
.tbs-mark {
  flex: none;
  width: 22px;
  height: 22px;
  object-fit: contain;
  background: #fff;
  border-radius: 5px;
  padding: 2px;
}

.tbs-name {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--b9-text-muted);
  line-height: 1.3;
  min-width: 0;
  overflow-wrap: anywhere;
}


/* ── Outline buttons: readable on the surface, whichever surface it is ────────
   Bootstrap's raw brand colours bleed through and measure ~3.9:1 on the dark card
   background (#111827) — below AA's 4.5:1 for body text. axe flagged them as serious on
   the pairings and teams pages against real production data.

   Only the RESTING text colour moves, to tokens the palette already ships
   (--b9-primary-text 9.84:1, --b9-success-text 12.63:1). Hover / focus / active are left
   entirely alone: there Bootstrap fills the button with the solid brand colour and switches
   the label to white, which already passes and is the shipped look.

   No new hues — see the brand lock.

   🔴 THE SELECTOR IS :root, NOT [data-theme="dark"] — X01 chunk C, 2026-09-09.
   This whole block and the sweep below it were written for dark and gated on dark, but the
   defect they fix was never about dark: #0d6efd measures 4.11:1 on the ops card in LIGHT and
   #dc3545 4.13:1, both under the same 4.5 bar, on the same buttons, for the same reason —
   Bootstrap compiles its raw brand hex into --bs-btn-color, so re-pointing --bs-primary never
   reaches them. Every token named below is already theme-aware, so :root resolves to exactly
   what [data-theme="dark"] resolved to before in dark, and to the light twin in light.
   :root is a pseudo-class, so specificity is unchanged at (0,6,0) — dark renders the same
   declarations at the same weight it always did, which is why it cannot regress here. */
:root .btn-outline-primary:not(:hover):not(:focus):not(:active):not(.active) {
  color: var(--b9-primary-text);
  border-color: var(--b9-primary-text);
}

:root .btn-outline-success:not(:hover):not(:focus):not(:active):not(.active) {
  color: var(--b9-success-text);
  border-color: var(--b9-success-text);
}

/* The variant the sweep never covered at all, because in dark it PASSES: Bootstrap's #ffc107
   on #111827 is 10.7:1. On a white ops card it is 1.63:1 — the "Unpaid" chip beside every
   unpaid golfer on the events Command Center, 84 of them on event 74 alone, and the single
   worst-reading control on the ops surfaces after the club-page editor.

   Light-gated on purpose. The other three variants above were already declaring these values
   in dark, so widening them changes nothing there; warning was declaring nothing, so widening
   it would repaint a dark control that reads fine — a change to the shipped look with no
   defect behind it. Fix what is broken. */
html[data-theme="light"] .btn-outline-warning:not(:hover):not(:focus):not(:active):not(.active) {
  color: var(--b9-warning-text);
  border-color: var(--b9-warning-text);
}

/* ── Status colour on the dark theme: the rest of the sweep ───────────────────
   The outline-button block above fixed .btn-outline-primary and .btn-outline-success in
   August and stopped there. Measured on the events Command Center against real data
   (2026-09-02), three more failures were still live:

     Withdraw (.btn-outline-danger)          #dc3545 on #111827   3.92:1
     "LIVE" badge (white on .bg-success)     #fff on #22C55E      2.28:1
     "Teams set · Edit" (.text-success)      #22C55E on #d1e7dd   1.76:1

   The third is the tell for the whole class: this app themes Bootstrap's COLOUR variables
   for dark but never sets data-bs-theme, so Bootstrap's own -subtle and -emphasis tokens
   stay at their LIGHT values. #d1e7dd is a pale-green chip rendered on a #020617 page —
   both unreadable and visibly wrong in a dark UI.

   Same rules as the block above: no new hues, fills keep the RAW brand colour, only the
   INK adapts, and hover/focus/active are untouched because there the control fills solid
   and flips its label to white — which already passes and is the shipped look. */

/* 1 · The outline button that was missed. :root, not [data-theme="dark"] — see the block
       above: "Withdraw" measures 3.92:1 on the dark card and 4.13:1 on the light one. */
:root .btn-outline-danger:not(:hover):not(:focus):not(:active):not(.active) {
  color: var(--b9-danger-text);
  border-color: var(--b9-danger-text);
}

/* 2 · Bootstrap's light-only subtle tokens, given dark equivalents. Background and border
       keep the raw brand hue at low alpha — decoration is not held to the text bar. */
[data-theme="dark"] {
  --bs-success-bg-subtle:     rgba(34, 197, 94, 0.14);
  --bs-success-border-subtle: rgba(34, 197, 94, 0.32);
  --bs-danger-bg-subtle:      rgba(239, 68, 68, 0.14);
  --bs-danger-border-subtle:  rgba(239, 68, 68, 0.32);
  --bs-warning-bg-subtle:     rgba(245, 158, 11, 0.14);
  --bs-warning-border-subtle: rgba(245, 158, 11, 0.32);
  --bs-primary-bg-subtle:     rgba(29, 78, 216, 0.16);
  --bs-primary-border-subtle: rgba(29, 78, 216, 0.36);
  --bs-secondary-bg-subtle:     rgba(148, 163, 184, 0.14);
  --bs-secondary-border-subtle: rgba(148, 163, 184, 0.30);
}

/* 2b · The OTHER half of Bootstrap's status pair, and the reason block 2 above was not enough.
       Bootstrap builds every .alert-* from THREE tokens:
           .alert-warning { --bs-alert-color:       var(--bs-warning-text-emphasis);
                            --bs-alert-bg:          var(--bs-warning-bg-subtle);
                            --bs-alert-border-color:var(--bs-warning-border-subtle);
                            --bs-alert-link-color:  var(--bs-warning-text-emphasis) }
       Block 2 gave dark values to the -subtle pair and stopped. The app themes on [data-theme] and
       never sets [data-bs-theme], so -text-emphasis kept Bootstrap's LIGHT ink — and darkening the
       fill under unchanged dark ink is what made these unreadable. Measured on the events public
       page at 375px, alpha-composited over #020617: secondary 1.25, success 1.25, danger 1.33,
       primary 1.35, warning 2.11, against an AA bar of 4.5.

       The tell is that info (7.65) and light (7.97) both PASS — they are the two variants block 2
       did not touch. So this is not "Bootstrap is bad in dark"; it is a half-applied sweep.

       One token per variant fixes three things at once: the alert's text, links inside the alert,
       and the .text-*-emphasis utility class, which shares the same variable. Values are the
       palette's existing -text inks — no new hues. Worst case after this change is 6.80:1
       (danger on --bg-raised); every other combination is 7.3:1 or better.

       INVARIANT, enforced by DarkThemeStatusColourContrastTests: a variant given a dark -bg-subtle
       above MUST be given a dark -text-emphasis here — a fill without its ink is exactly the bug
       this block exists to undo. Block 2c below finishes info as a PAIR for that reason. */
[data-theme="dark"] {
  --bs-success-text-emphasis:   var(--b9-success-text);
  --bs-danger-text-emphasis:    var(--b9-danger-text);
  --bs-warning-text-emphasis:   var(--b9-warning-text);
  --bs-primary-text-emphasis:   var(--b9-primary-text);
  /* The palette ships no secondary ink; body copy is the right neutral and adds no hue. */
  --bs-secondary-text-emphasis: var(--text-primary);
}

/* 2c · info — the last pale panel left in the dark app.
       Blocks 2 and 2b left info alone because it was LEGIBLE: Bootstrap's #cff4fc fill under
       #055160 ink measures 7.65:1 and passes AA. Legible was never the whole bar. It is a
       hardcoded LIGHT panel painted into a dark UI — a white-ish card that glows out of
       #020617 on the field list and the scoring console — and it reaches the page from
       Bootstrap's light-theme defaults, not from any token this app owns. So it breaks the
       house rule ("design tokens, never hardcoded light backgrounds") while passing the
       contrast test that was watching it.

       Fixed the same way as its five siblings and as a PAIR, so the invariant above holds:
       the raw brand info hue at low alpha for the fill and border, the palette's existing
       readable ink on dark for the text. No new hues. Measured alpha-composited over the
       page ground #020617: #7DD3FC on rgba(56,189,248,0.14) = 9.9:1, comfortably past the
       4.5 AA bar and better than the 7.65 it replaces. Light mode is untouched — everything
       here lives inside [data-theme="dark"].

       One token per line still fixes three things, because Bootstrap wires them together:
       .alert-info's text, links inside it, and the .text-info-emphasis utility class. */
[data-theme="dark"] {
  --bs-info-bg-subtle:     rgba(56, 189, 248, 0.14);
  --bs-info-border-subtle: rgba(56, 189, 248, 0.32);
  --bs-info-text-emphasis: var(--b9-info-text);
}

/* 3 · Status TEXT: the raw brand colour was picked to sit behind something, and does not
       survive being the writing. Swap to the -text variants the palette already ships.

       :root, not [data-theme="dark"] — a fill written as ink is unreadable on cream for the
       same reason it is unreadable on charcoal. In light this is what makes the events
       Command Center's "Teams set · Edit" chip readable (#22C55E on Bootstrap's own #d1e7dd
       measured 1.76:1; --b9-success-text #166534 on it is 5.50:1) and turns the required-field
       asterisk on every ops form from 3.76:1 into 8.31:1. */
:root .text-success { color: var(--b9-success-text) !important; }
:root .text-danger  { color: var(--b9-danger-text) !important; }
:root .text-warning { color: var(--b9-warning-text) !important; }

/* 3b · .text-secondary was never themed at all, in either direction, and the reason is one
        character: this app re-points --bs-secondary-COLOR (which .text-muted reads) but
        Bootstrap's .text-secondary reads --bs-secondary-RGB, a different variable that still
        holds the framework default 108,117,125. So the utility rendered #6c757d on a near-black
        page and on a white one alike — 3.61:1 on the dark roster and 3.65:1 on the light one,
        on the tab counts of the handicap import screen.

        Re-pointing --bs-secondary-rgb itself would also repaint every .bg-secondary fill that
        reads the same triplet, which is a much wider blast radius than the defect. The utility
        is corrected where it is used as ink instead, to the same token .text-muted already
        resolves to, so the two neutral utilities finally agree. */
:root .text-secondary { color: var(--text-secondary) !important; }

/* 4 · A filled status badge keeps its brand fill; the ink on it goes dark, which is the
       same answer the club page reaches with --ol-accent-ink. White on #22C55E is 2.28:1;
       near-black on it is 8.31:1, and the badge still reads as the same green pill.

       :root for the same reason as block 3 — white on #22C55E is 2.28:1 in EVERY theme,
       because the fill does not move when the theme does. This was the single most common
       failure on the ops surfaces: 190 "Active" / "Confirmed" / "Ready" badges across the
       field list, the players list, settings and the course pages.

       .text-bg-success is here too. It is a different Bootstrap class that paints the same
       green and declares `color: #fff !important` in its own rule rather than reading
       --bs-badge-color, so block 4 never touched it and it failed in BOTH themes on the course
       page ("Ready", beside the course name). Beating an !important takes an !important; it is
       scoped to the two filled status variants and nothing else. */
:root .badge.bg-success,
:root .badge.bg-warning {
  color: var(--b9-status-fill-ink);
}

:root .badge.text-bg-success,
:root .badge.text-bg-warning {
  color: var(--b9-status-fill-ink) !important;
}

/* 5 · The same answer for a filled status BUTTON, which block 4 did not cover.
       Found by the Playwright legibility sweep, not by eye: "Open & Print Flyer" on the events
       Command Center is a .btn-success, and Bootstrap's #198754 fill under this app's light ink
       measures 4.18:1 — under AA, and close enough to pass a glance.

       White on #198754 would be 4.53:1, which clears the bar by 0.03 and would fail again the
       next time anyone nudges the green. So the button takes the brand fill and the dark ink the
       badges already use: near-black on #22C55E is 8.3:1, and it still reads as the same green
       button. Hover/focus/active are untouched — they already fill solid and pass. */
:root .btn-success:not(:hover):not(:focus):not(:active):not(.active) {
  background-color: var(--b9-success, #22C55E);
  border-color: var(--b9-success, #22C55E);
  color: var(--b9-status-fill-ink);
}

/* Same failure, same fix, found the same way. A filled .btn-warning in dark mode measured 1.03:1 —
   the ink resolves from a token the dark theme restyles, so the label all but vanished on its own
   amber fill. The AA sweep caught it the moment a warning button first appeared on the Command
   Center (the frost-delay Post button); before that nothing on the page used one. */
[data-theme="dark"] .btn-warning:not(:hover):not(:focus):not(:active):not(.active) {
  background-color: var(--b9-warning, #F59E0B);
  border-color: var(--b9-warning, #F59E0B);
  color: var(--b9-status-fill-ink);
}

/* 6 · Inline code. Bootstrap's --bs-code-color is #d63384, which was never re-pointed for
       either theme and fails in BOTH: 4.27:1 on the light page, 4.48:1 on the dark one. It
       is the club's own public address on the tee-sheet QR kit — the one string on that page
       somebody retypes into a phone, so "nearly readable" is the wrong answer.

       Kept as the same magenta, moved far enough for the ground it is written on. Measured
       on the real page grounds: #A3195B on #F8F9FA is 7.00:1; #F8A8CE on #020617 is 11.03:1.
       A token rather than a hex at the call site, because the theme has to be able to pick. */
:root {
  --bs-code-color: var(--b9-code-ink);
}

/* ═══════════════════════════════════════════════════════════════════════════════════════
   OPS PAGE TOTALS, DEMOTED — UX audit 2026-09-06, O04 + X09 (rethink R4)

   Every operations index page opened with a strip of stat tiles: the dashboard with five
   full-width cards, Events with four, Leagues with four, Communications with five, Courses
   and Players with two each. On a 390px phone that was the whole first screen, so on a
   Saturday morning the page answered "how many things exist" when the question was "what do
   I do now".

   A count is not work. It comes AFTER the work now, in one quiet line, and it is not drawn
   at all when it is 0 or 1 — a full-width tile reading "1 courses" earned none of the screen
   it took. One implementation, shared (Components/Shared/OpsStatLine.razor), because six
   copies of a rule is how five of them drift.

   Still 48px tall where it is a control: a count you can tap is a control, and a control a
   thumb cannot hit is worse than no control at all.
   ═══════════════════════════════════════════════════════════════════════════════════════ */
.ops-stats {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 1.5rem;
  margin-top: 1.5rem;
  padding-top: 0.25rem;
  border-top: 1px solid var(--b9-border);
}

.ops-stat {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 48px;
  padding: 0 0.25rem;
  font-size: 0.875rem;
  color: var(--b9-text-muted);
  text-decoration: none;
}

.ops-stat-value {
  font-weight: 700;
  color: var(--b9-text-secondary);
}

a.ops-stat:hover,
a.ops-stat:focus-visible,
button.ops-stat:hover,
button.ops-stat:focus-visible {
  color: var(--b9-text);
  text-decoration: none;
}

/* A stat that filters the list it sits under. Quiet like the rest until it is on. */
button.ops-stat {
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  font-size: 0.875rem;
}

button.ops-stat:focus-visible {
  outline: 2px solid var(--b9-primary);
  outline-offset: 2px;
  border-radius: 6px;
}

button.ops-stat[aria-pressed="true"] {
  color: var(--b9-text);
}

button.ops-stat[aria-pressed="true"] .ops-stat-value {
  color: var(--b9-text);
  text-decoration: underline;
  text-underline-offset: 3px;
}
