/* app/assets/stylesheets/application.css */
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap');

/* Tailwind base is loaded via tailwind.css — this file handles extras */

/* ── Appearance (light/dark/system/party) ──
   Semantic tokens consumed by the .app-* classes below. Dark's values are
   the literal current design (gray-950/900/800/100/400/500) so switching to
   Dark is pixel-identical to today's hardcoded classes. Party gets its own
   deep-violet page with hot-pink/cyan/lime/gold accents (state colors are
   recolored to match: lime=success, hot pink=error/disputed, cyan=accent,
   gold/sky=live/pending) — no template changes needed, every view already
   consumes these same .app-* classes. */
[data-appearance="light"],
[data-appearance="system"] {
  --color-page: #ffffff;
  --color-header: #ffffff;
  --color-surface: #ffffff;
  --color-surface-raised: #f9fafb;
  --color-input: #ffffff;
  --color-border: #e5e7eb;
  --color-text: #111827;
  --color-text-muted: #4b5563;
  --color-text-subtle: #6b7280;
  --color-text-dim: #4b5563;
  --color-text-secondary: #374151;
  --color-text-faint: #9ca3af;
  --color-text-body: #1f2937;
  --color-code-text: #854d0e;
  --color-border-strong: #d1d5db;
  --color-border-hover: #9ca3af;
  --color-border-hover-subtle: #d1d5db;
  --color-neutral-button-bg: #e5e7eb;
  --color-neutral-button-hover-bg: #d1d5db;
  --color-neutral-button-text: #111827;
  --color-focus: #4f46e5;
  --shadow-card: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
  --color-success-bg: #f0fdf4;
  --color-success-border: #86efac;
  --color-success-text: #166534;
  --color-error-bg: #fef2f2;
  --color-error-border: #fca5a5;
  --color-error-text: #991b1b;
  --color-accent-bg: #eef2ff;
  --color-accent-border: #c7d2fe;
  --color-accent-text: #4338ca;
  --color-badge-success-text: #166534;
  --color-green-badge-bg: var(--color-success-bg);
  --color-green-badge-border: var(--color-success-border);
  --color-status-complete-text: #166534;
  --color-live-bg: #fefce8;
  --color-live-border: #fde047;
  --color-live-text: #854d0e;
  --color-pending-bg: #eff6ff;
  --color-pending-border: #93c5fd;
  --color-pending-text: #1d4ed8;
  --color-disputed-bg: var(--color-error-bg);
  --color-disputed-border: var(--color-error-border);
  --color-disputed-text: var(--color-error-text);
  --color-row-border: var(--color-border);
  --color-row-hover-bg: var(--color-surface-raised);
  --color-form-error-bg: var(--color-error-bg);
  --color-form-error-border: var(--color-error-border);
  --color-form-error-text: var(--color-error-text);
  --color-reg-pending-bg: var(--color-live-bg);
  --color-reg-pending-border: var(--color-live-border);
  --color-reg-pending-text: var(--color-live-text);
  --color-reg-approved-bg: var(--color-success-bg);
  --color-reg-approved-border: var(--color-success-border);
  --color-reg-approved-text: var(--color-success-text);
  --color-reg-declined-bg: var(--color-error-bg);
  --color-reg-declined-border: var(--color-error-border);
  --color-reg-declined-text: var(--color-error-text);
  --color-danger-box-bg: var(--color-error-bg);
  --color-danger-box-border: var(--color-error-border);
  --color-danger-button-bg: #b91c1c;
  --color-danger-button-hover-bg: #991b1b;
  --color-danger-button-text: #ffffff;
}

[data-appearance="dark"] {
  --color-page: #030712;
  --color-header: #111827;
  --color-surface: #111827;
  --color-surface-raised: #1f2937;
  --color-input: #030712;
  --color-border: #1f2937;
  --color-text: #f3f4f6;
  --color-text-muted: #9ca3af;
  --color-text-subtle: #6b7280;
  --color-text-dim: #4b5563;
  --color-text-secondary: #d1d5db;
  --color-text-faint: #374151;
  --color-text-body: #e5e7eb;
  --color-code-text: #fde047;
  --color-border-strong: #374151;
  --color-border-hover: #6b7280;
  --color-border-hover-subtle: #4b5563;
  --color-neutral-button-bg: #374151;
  --color-neutral-button-hover-bg: #4b5563;
  --color-neutral-button-text: #ffffff;
  --color-focus: #6366f1;
  --shadow-card: none;
  --color-success-bg: rgb(20 83 45 / 0.5);
  --color-success-border: #15803d;
  --color-success-text: #86efac;
  --color-error-bg: rgb(127 29 29 / 0.5);
  --color-error-border: #b91c1c;
  --color-error-text: #fca5a5;
  --color-accent-bg: rgb(49 46 129 / 0.5);
  --color-accent-border: #4338ca;
  --color-accent-text: #a5b4fc;
  --color-badge-success-text: #4ade80;
  --color-green-badge-bg: rgb(20 83 45 / 0.4);
  --color-green-badge-border: rgb(34 197 94 / 0.2);
  --color-status-complete-text: #22c55e;
  --color-live-bg: rgb(250 204 21 / 0.1);
  --color-live-border: rgb(250 204 21 / 0.2);
  --color-live-text: #facc15;
  --color-pending-bg: rgb(30 58 138 / 0.3);
  --color-pending-border: rgb(59 130 246 / 0.2);
  --color-pending-text: #60a5fa;
  --color-disputed-bg: rgb(127 29 29 / 0.3);
  --color-disputed-border: rgb(239 68 68 / 0.2);
  --color-disputed-text: #f87171;
  --color-row-border: rgb(31 41 55 / 0.5);
  --color-row-hover-bg: rgb(31 41 55 / 0.3);
  --color-form-error-bg: rgb(127 29 29 / 0.4);
  --color-form-error-border: #b91c1c;
  --color-form-error-text: #fca5a5;
  --color-reg-pending-bg: rgb(66 32 6 / 0.2);
  --color-reg-pending-border: rgb(161 98 7 / 0.5);
  --color-reg-pending-text: #fde047;
  --color-reg-approved-bg: rgb(5 46 22 / 0.2);
  --color-reg-approved-border: rgb(21 128 61 / 0.5);
  --color-reg-approved-text: #86efac;
  --color-reg-declined-bg: rgb(69 10 10 / 0.2);
  --color-reg-declined-border: rgb(185 28 28 / 0.5);
  --color-reg-declined-text: #fca5a5;
  --color-danger-box-bg: rgb(69 10 10 / 0.2);
  --color-danger-box-border: #7f1d1d;
  --color-danger-button-bg: #7f1d1d;
  --color-danger-button-hover-bg: #991b1b;
  --color-danger-button-text: #ffffff;
}

/* Party — deep violet/navy page with hot-pink/cyan/lime/gold highlights.
   State colors are recolored to fit (lime=success, hot pink=error/disputed,
   cyan=accent, gold/sky=live/pending) while keeping each state's own hue so
   they stay distinguishable from one another; --shadow-card gets a visible
   pink glow instead of Dark's `none`, matching the doc's "more expressive
   button shadows and borders". Contrast-checked against this exact --color-
   page/--color-surface (WCAG relative-luminance): all text tokens clear
   4.5:1 except --color-text-dim (3.5:1) and --color-text-faint (1.7:1),
   which intentionally match Dark's own low-contrast values for those two
   deliberately-subtle tiers. Sport-brand colors (lime/red/green) also clear
   4.5:1+ against --color-page, so --sport-brand-contrast is left undefined
   here (falls back to the bright --sport-brand, same as Dark). */
[data-appearance="party"] {
  --color-page: #170a2e;
  --color-header: #1f0f3d;
  --color-surface: #1f0f3d;
  --color-surface-raised: #2c1652;
  --color-input: #130826;
  --color-border: #3d1f6b;
  --color-text: #f5eeff;
  --color-text-muted: #c9b8ea;
  --color-text-subtle: #a68fd1;
  --color-text-dim: #7c63a8;
  --color-text-secondary: #ddd0f7;
  --color-text-faint: #4a3570;
  --color-text-body: #e4d9fb;
  --color-code-text: #ffd60a;
  --color-border-strong: #5b2f9e;
  --color-border-hover: #a855f7;
  --color-border-hover-subtle: #7c3aed;
  --color-neutral-button-bg: #4c2073;
  --color-neutral-button-hover-bg: #5b2f9e;
  --color-neutral-button-text: #f5eeff;
  --color-focus: #ff3ea5;
  --shadow-card: 0 4px 24px -8px rgb(236 72 220 / 0.35), 0 0 0 1px rgb(255 62 165 / 0.15);
  --color-success-bg: rgb(35 71 10 / 0.45);
  --color-success-border: #84cc16;
  --color-success-text: #a3e635;
  --color-error-bg: rgb(80 8 48 / 0.45);
  --color-error-border: #ec4899;
  --color-error-text: #f9a8d4;
  --color-accent-bg: rgb(8 60 74 / 0.4);
  --color-accent-border: #22d3ee;
  --color-accent-text: #67e8f9;
  --color-badge-success-text: #a3e635;
  --color-green-badge-bg: rgb(35 71 10 / 0.4);
  --color-green-badge-border: rgb(132 204 22 / 0.3);
  --color-status-complete-text: #a3e635;
  --color-live-bg: rgb(66 45 0 / 0.35);
  --color-live-border: #fbbf24;
  --color-live-text: #fde047;
  --color-pending-bg: rgb(8 47 73 / 0.4);
  --color-pending-border: #38bdf8;
  --color-pending-text: #7dd3fc;
  --color-disputed-bg: rgb(80 8 48 / 0.35);
  --color-disputed-border: #f472b6;
  --color-disputed-text: #f9a8d4;
  --color-row-border: rgb(61 31 107 / 0.5);
  --color-row-hover-bg: rgb(44 22 82 / 0.45);
  --color-form-error-bg: rgb(80 8 48 / 0.4);
  --color-form-error-border: #ec4899;
  --color-form-error-text: #f9a8d4;
  --color-reg-pending-bg: rgb(66 45 0 / 0.25);
  --color-reg-pending-border: rgb(217 119 6 / 0.5);
  --color-reg-pending-text: #fde047;
  --color-reg-approved-bg: rgb(35 71 10 / 0.25);
  --color-reg-approved-border: rgb(132 204 22 / 0.5);
  --color-reg-approved-text: #a3e635;
  --color-reg-declined-bg: rgb(80 8 48 / 0.25);
  --color-reg-declined-border: rgb(236 72 153 / 0.5);
  --color-reg-declined-text: #f9a8d4;
  --color-danger-box-bg: rgb(80 8 48 / 0.25);
  --color-danger-box-border: #db2777;
  --color-danger-button-bg: #831843;
  --color-danger-button-hover-bg: #9d174d;
  --color-danger-button-text: #ffffff;
}

@media (prefers-color-scheme: dark) {
  [data-appearance="system"] {
    --color-page: #030712;
    --color-header: #111827;
    --color-surface: #111827;
    --color-surface-raised: #1f2937;
    --color-input: #030712;
    --color-border: #1f2937;
    --color-text: #f3f4f6;
    --color-text-muted: #9ca3af;
    --color-text-subtle: #6b7280;
    --color-text-dim: #4b5563;
    --color-text-secondary: #d1d5db;
    --color-text-faint: #374151;
    --color-text-body: #e5e7eb;
    --color-code-text: #fde047;
    --color-border-strong: #374151;
    --color-border-hover: #6b7280;
    --color-border-hover-subtle: #4b5563;
    --color-neutral-button-bg: #374151;
    --color-neutral-button-hover-bg: #4b5563;
    --color-neutral-button-text: #ffffff;
    --color-focus: #6366f1;
    --shadow-card: none;
    --color-success-bg: rgb(20 83 45 / 0.5);
    --color-success-border: #15803d;
    --color-success-text: #86efac;
    --color-error-bg: rgb(127 29 29 / 0.5);
    --color-error-border: #b91c1c;
    --color-error-text: #fca5a5;
    --color-accent-bg: rgb(49 46 129 / 0.5);
    --color-accent-border: #4338ca;
    --color-accent-text: #a5b4fc;
    --color-badge-success-text: #4ade80;
    --color-green-badge-bg: rgb(20 83 45 / 0.4);
    --color-green-badge-border: rgb(34 197 94 / 0.2);
    --color-status-complete-text: #22c55e;
    --color-live-bg: rgb(250 204 21 / 0.1);
    --color-live-border: rgb(250 204 21 / 0.2);
    --color-live-text: #facc15;
    --color-pending-bg: rgb(30 58 138 / 0.3);
    --color-pending-border: rgb(59 130 246 / 0.2);
    --color-pending-text: #60a5fa;
    --color-disputed-bg: rgb(127 29 29 / 0.3);
    --color-disputed-border: rgb(239 68 68 / 0.2);
    --color-disputed-text: #f87171;
    --color-row-border: rgb(31 41 55 / 0.5);
    --color-row-hover-bg: rgb(31 41 55 / 0.3);
    --color-form-error-bg: rgb(127 29 29 / 0.4);
    --color-form-error-border: #b91c1c;
    --color-form-error-text: #fca5a5;
    --color-reg-pending-bg: rgb(66 32 6 / 0.2);
    --color-reg-pending-border: rgb(161 98 7 / 0.5);
    --color-reg-pending-text: #fde047;
    --color-reg-approved-bg: rgb(5 46 22 / 0.2);
    --color-reg-approved-border: rgb(21 128 61 / 0.5);
    --color-reg-approved-text: #86efac;
    --color-reg-declined-bg: rgb(69 10 10 / 0.2);
    --color-reg-declined-border: rgb(185 28 28 / 0.5);
    --color-reg-declined-text: #fca5a5;
    --color-danger-box-bg: rgb(69 10 10 / 0.2);
    --color-danger-box-border: #7f1d1d;
    --color-danger-button-bg: #7f1d1d;
    --color-danger-button-hover-bg: #991b1b;
    --color-danger-button-text: #ffffff;
  }
}

.app-page { background: var(--color-page); color: var(--color-text); }
.app-header { background: var(--color-header); border-color: var(--color-border); }
.app-surface { background: var(--color-surface); border-color: var(--color-border); }
.app-surface-raised {
  background: var(--color-surface-raised);
  border-color: var(--color-border);
  box-shadow: var(--shadow-card);
}

/* Menu-item hover — appearance-aware instead of a literal translucent dark
   overlay, which (combined with light-mode text colors) had too little
   contrast once the dropdown itself could be a white surface. The text-color
   brightening is scoped to .app-muted (the inactive-item class) so it never
   matches the active item (.theme-brand-text) and overrides its accent
   color — the background highlight alone is fine on either state. */
.app-menu-item:hover {
  background: var(--color-surface-raised);
}
.app-menu-item.app-muted:hover {
  color: var(--color-text);
}

.app-input {
  background: var(--color-input);
  border-color: var(--color-border-strong);
  color: var(--color-text);
}
.app-input:focus {
  border-color: var(--color-focus);
  box-shadow: 0 0 0 1px var(--color-focus);
}
.app-input::placeholder { color: var(--color-text-subtle); }
.app-bg-input { background: var(--color-input); }
/* Background-only, no border-color — for cards that need .app-surface's
   background but must keep a different, literal border (e.g. the disputed
   card's red ring). .app-surface bundles both together, and since this
   stylesheet loads after Tailwind, its border-color always beats a
   Tailwind border-* utility of equal specificity composed alongside it. */
.app-bg-surface { background: var(--color-surface); }

.app-button-secondary {
  background: var(--color-surface-raised);
  color: var(--color-text);
  border-color: var(--color-border);
}
.app-button-secondary:hover { border-color: var(--color-focus); }

/* Solid neutral action button (e.g. "CLOSE TOURNAMENT") — sits alongside
   intentionally-colored solid buttons (red/emerald/blue) that stay literal
   across every appearance, but gray carries no specific meaning, so it
   follows the theme instead. Dedicated tokens (not .app-button-secondary,
   which is calibrated for a bordered/raised-surface look, not this button's
   original solid gray-700/gray-600/white) so Dark reproduces those exact
   literals while Light/Party get their own solid neutral-button colors. */
.app-button-neutral {
  background: var(--color-neutral-button-bg);
  color: var(--color-neutral-button-text);
}
.app-button-neutral:hover { background: var(--color-neutral-button-hover-bg); }

/* Solid danger action button (e.g. "ARCHIVE TOURNAMENT") — use a red surface
   with light text so the destructive action is visually distinct from the
   green primary actions and remains readable in dark appearances. */
.app-button-danger {
  background: var(--color-danger-button-bg);
  color: var(--color-danger-button-text);
}
.app-button-danger:hover { background: var(--color-danger-button-hover-bg); }

.app-border { border-color: var(--color-border); }
.app-border-strong { border-color: var(--color-border-strong); }
.app-divider { background-color: var(--color-border); }

/* Hover-only border intensification — drop-in for a literal hover:border-
   gray-500/600 utility on an element whose resting border color already
   comes from .app-border/.app-border-strong (kept in the class list
   alongside these; only the :hover state is defined here). Dark's two
   values are the exact original gray-500/gray-600 literals; Light/Party get
   their own appearance-appropriate hover colors. */
.app-border-hover:hover { border-color: var(--color-border-hover); }
.app-border-hover-subtle:hover { border-color: var(--color-border-hover-subtle); }

/* Tournament card is a single full-card link (see app/views/tournaments/index.html.erb)
   — style keyboard focus explicitly instead of relying on the browser's
   default link outline, which :focus (not :focus-visible) would also draw
   on a mouse click. */
.tournament-card:focus { outline: none; }
.tournament-card:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}
/* Matches Tailwind v4's own .divide-y selector (:not(:last-child), border-
   bottom) exactly — an earlier > * + * / border-top-color version targeted
   the wrong children and the wrong edge, so the border Tailwind actually
   draws stayed unset and fell back to currentColor (bright text-colored
   dividers) instead of picking up this override. */
.app-divide-y > :not(:last-child) { border-bottom-color: var(--color-border); }

/* Table row divider/hover — a dedicated pair (not .app-border/.app-surface-
   raised) because Dark's original values were translucent (gray-800 at 50%/
   30%), not the opaque --color-border/--color-surface-raised used elsewhere.
   Reusing those opaque tokens made Dark rows read visibly heavier than
   before; --color-row-border/--color-row-hover-bg keep Dark's exact original
   alpha while giving Light its own (already-correct, non-translucent —
   nothing needs to show through) values. */
.app-table-row { border-color: var(--color-row-border); }
.app-table-row:hover { background: var(--color-row-hover-bg); }

.app-text { color: var(--color-text); }
.app-secondary { color: var(--color-text-secondary); }
.app-muted { color: var(--color-text-muted); }
.app-subtle { color: var(--color-text-subtle); }
.app-dim { color: var(--color-text-dim); }

/* Interactive-only: subtle text that brightens to full contrast on hover.
   A plain hover:text-gray-300 assumes an always-dark surface and nearly
   vanishes against a white .app-surface in Light — scoped to a separate
   class (not .app-subtle itself) so static, non-interactive subtle text
   elsewhere doesn't pick up an unintended hover color shift. */
.app-subtle-hover { color: var(--color-text-subtle); }
.app-subtle-hover:hover { color: var(--color-text); }
.app-dim-hover { color: var(--color-text-dim); }
.app-dim-hover:hover { color: var(--color-text); }
.app-faint { color: var(--color-text-faint); }
.app-body-text { color: var(--color-text-body); }
.app-code-text { color: var(--color-code-text); }

.app-badge {
  background: var(--color-surface-raised);
  color: var(--color-text-muted);
  border-color: var(--color-border);
}
.app-badge-subtle {
  background: var(--color-surface-raised);
  color: var(--color-text-subtle);
  border-color: var(--color-border);
}
.app-badge-strong {
  background: var(--color-surface-raised);
  color: var(--color-text-secondary);
  border-color: var(--color-border);
}
.app-badge-success {
  background: var(--color-success-bg);
  color: var(--color-badge-success-text);
  border-color: var(--color-success-border);
}
.app-badge-accent {
  background: var(--color-accent-bg);
  color: var(--color-accent-text);
  border-color: var(--color-accent-border);
}

/* Round-card badges — same idea as .app-badge above, but this card explicitly
   draws a visible border (the others didn't), and the original used the
   stronger gray-700 border tier rather than gray-800, so they need their own
   pair rather than reusing .app-badge/.app-badge-subtle. */
.round-badge {
  background: var(--color-surface-raised);
  color: var(--color-text-muted);
  border-color: var(--color-border-strong);
}
.round-badge-subtle {
  background: var(--color-surface-raised);
  color: var(--color-text-subtle);
  border-color: var(--color-border-strong);
}
.round-badge-live-court {
  background: var(--color-green-badge-bg);
  color: var(--color-badge-success-text);
  border-color: var(--color-green-badge-border);
}
.round-badge-complete {
  background: var(--color-green-badge-bg);
  color: var(--color-status-complete-text);
  border-color: var(--color-green-badge-border);
}
.round-badge-live {
  background: var(--color-live-bg);
  color: var(--color-live-text);
  border-color: var(--color-live-border);
}
.round-badge-pending {
  background: var(--color-pending-bg);
  color: var(--color-pending-text);
  border-color: var(--color-pending-border);
}
.round-badge-disputed {
  background: var(--color-disputed-bg);
  color: var(--color-disputed-text);
  border-color: var(--color-disputed-border);
}

.round-win-text { color: var(--color-badge-success-text); }
.round-disputed-text { color: var(--color-disputed-text); }
.round-amber-text { color: var(--color-live-text); }
.round-blue-text { color: var(--color-pending-text); }

.app-alert-success {
  background: var(--color-success-bg);
  border-color: var(--color-success-border);
  color: var(--color-success-text);
}
.app-alert-error {
  background: var(--color-error-bg);
  border-color: var(--color-error-border);
  color: var(--color-error-text);
}
.app-alert-warning {
  background: var(--color-live-bg);
  border-color: var(--color-live-border);
  color: var(--color-live-text);
}

/* Form validation-error box (bg-red-900/40) — a slightly different Dark
   opacity than the flash .app-alert-error (bg-red-900/50), so it gets its
   own token trio rather than reusing that one and drifting from the
   original literal. */
.app-alert-form-error {
  background: var(--color-form-error-bg);
  border-color: var(--color-form-error-border);
  color: var(--color-form-error-text);
}

/* Registration status boxes (roster tab) — each has its own Dark literal
   (yellow/green/red at -950/20 bg, -700/50 border, -300 text) that doesn't
   match any existing badge/alert token, so each gets a dedicated trio;
   Light reuses the existing live/success/error tokens since there's no
   legacy value to match there. */
.reg-status-pending {
  background: var(--color-reg-pending-bg);
  border-color: var(--color-reg-pending-border);
  color: var(--color-reg-pending-text);
}
.reg-status-approved {
  background: var(--color-reg-approved-bg);
  border-color: var(--color-reg-approved-border);
  color: var(--color-reg-approved-text);
}
.reg-status-declined {
  background: var(--color-reg-declined-bg);
  border-color: var(--color-reg-declined-border);
  color: var(--color-reg-declined-text);
}

/* Danger warning box (ownership transfer) — solid red-900 border rather
   than the translucent red-700 .reg-status-declined uses, so it needs its
   own token pair; body text inside is plain (.app-body-text/.app-muted),
   not tinted red. */
.app-alert-danger {
  background: var(--color-danger-box-bg);
  border-color: var(--color-danger-box-border);
}

.app-text-success { color: var(--color-success-text); }
.app-text-error { color: var(--color-error-text); }
/* Stronger red-400 variant of .app-text-error (red-300) — for error text
   whose original literal was text-red-400, not text-red-300. */
.app-text-danger { color: var(--color-disputed-text); }

/* Positive/negative stat highlighting (wins/losses, +/- diff) — same green-400
   / red-400 dark values as .round-win-text/.round-disputed-text, kept as
   separate generically-named aliases so non-rounds call sites (rankings)
   don't read as rounds-specific. */
.stat-positive { color: var(--color-badge-success-text); }
.stat-negative { color: var(--color-disputed-text); }

.app-link-muted { color: var(--color-text-muted); }
.app-link-muted:hover { color: var(--color-text); }

.app-radio-card {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
  padding: 0.75rem;
  cursor: pointer;
  background: var(--color-surface);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.app-radio-card:has(input:checked) {
  border-color: var(--color-focus);
  box-shadow: 0 0 0 1px var(--color-focus);
}

.app-swatch-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 9999px;
  border: 1px solid var(--color-border);
  display: inline-block;
}

/* Translucent card surfaces — solid in Light (nothing to show through), but
   the exact original translucent value in Dark/Party so cards keep letting
   the hero gradient (.app-hero-gradient) show through behind them.
   -translucent = gray-900/70, -translucent-deep = gray-950/70 (index.html.erb
   empty state), -nested = gray-950/60 (dashboard summary's recessed cards —
   a different original opacity, so it needs its own tier). */
.app-surface-translucent {
  background: var(--color-surface);
  border-color: var(--color-border);
}
.app-surface-translucent-deep {
  background: var(--color-surface-raised);
  border-color: var(--color-border);
}
.app-surface-nested {
  background: var(--color-surface-raised);
  border-color: var(--color-border);
}
[data-appearance="dark"] .app-surface-translucent {
  background: rgb(17 24 39 / 0.7);
}
[data-appearance="dark"] .app-surface-translucent-deep {
  background: rgb(3 7 18 / 0.7);
}
[data-appearance="dark"] .app-surface-nested {
  /* Default 0.6 matches the dashboard's recessed cards; override per-instance
     with style="--nested-opacity: 0.8" etc. where the original used a
     different literal opacity (e.g. the host panel's gray-950/80). */
  background: rgb(3 7 18 / var(--nested-opacity, 0.6));
}
/* Party reuses the same translucent-card mechanism, but tinted violet (its
   own --color-surface/--color-input hues) instead of Dark's neutral gray, so
   cards read as part of the Party palette rather than a recolored Dark. */
[data-appearance="party"] .app-surface-translucent {
  background: rgb(31 15 61 / 0.7);
}
[data-appearance="party"] .app-surface-translucent-deep {
  background: rgb(19 8 38 / 0.7);
}
[data-appearance="party"] .app-surface-nested {
  background: rgb(19 8 38 / var(--nested-opacity, 0.6));
}
@media (prefers-color-scheme: dark) {
  [data-appearance="system"] .app-surface-translucent {
    background: rgb(17 24 39 / 0.7);
  }
  [data-appearance="system"] .app-surface-translucent-deep {
    background: rgb(3 7 18 / 0.7);
  }
  [data-appearance="system"] .app-surface-nested {
    background: rgb(3 7 18 / var(--nested-opacity, 0.6));
  }
}

/* Hero gradient — the radial sport-accent glow only makes sense against a
   near-black page, so it's opt-in per appearance rather than baked into
   .theme-hero (which stays permanently dark for pages not yet migrated). */
[data-appearance="dark"] .app-hero-gradient,
[data-appearance="party"] .app-hero-gradient {
  background-image:
    radial-gradient(ellipse at 20% 50%, var(--sport-surface) 0%, transparent 60%),
    radial-gradient(circle at 85% 20%, var(--sport-surface-2) 0%, transparent 32%);
}
@media (prefers-color-scheme: dark) {
  [data-appearance="system"] .app-hero-gradient {
    background-image:
      radial-gradient(ellipse at 20% 50%, var(--sport-surface) 0%, transparent 60%),
      radial-gradient(circle at 85% 20%, var(--sport-surface-2) 0%, transparent 32%);
  }
}

:root,
.theme-pickleball {
  --sport-brand: #7cff2b;
  --sport-brand-hover: #a3ff5c;
  --sport-brand-soft: rgb(124 255 43 / 0.14);
  --sport-brand-border-soft: rgb(124 255 43 / 0.34);
  --sport-surface: rgb(18 113 48 / 0.30);
  --sport-surface-2: rgb(97 255 66 / 0.14);
  --sport-brand-glow: rgb(124 255 43 / 0.42);
}

.theme-table-tennis {
  --sport-brand: #ef4444;
  --sport-brand-hover: #f87171;
  --sport-brand-soft: rgb(239 68 68 / 0.12);
  --sport-brand-border-soft: rgb(239 68 68 / 0.28);
  --sport-surface: rgb(127 29 29 / 0.28);
  --sport-surface-2: rgb(248 113 113 / 0.08);
  --sport-brand-glow: rgb(239 68 68 / 0.22);
}

.theme-tennis {
  --sport-brand: #22c55e;
  --sport-brand-hover: #4ade80;
  --sport-brand-soft: rgb(34 197 94 / 0.12);
  --sport-brand-border-soft: rgb(34 197 94 / 0.28);
  --sport-surface: rgb(21 128 61 / 0.25);
  --sport-surface-2: rgb(74 222 128 / 0.08);
  --sport-brand-glow: rgb(34 197 94 / 0.22);
}

/* Sport-brand text/icon color on light backgrounds — the base --sport-brand
   values above are tuned for near-black surfaces (Dark/Party) and fail
   contrast on white. --sport-brand-contrast overrides just the text/icon
   color for Normal and system-as-light; it falls back to --sport-brand
   itself everywhere else (Dark, Party, system-as-dark), so buttons/borders/
   glows — which still read fine on their own surfaces — are unaffected. */
[data-appearance="light"] .theme-pickleball,
[data-appearance="system"] .theme-pickleball {
  --sport-brand-contrast: #3f6212;
}
[data-appearance="light"] .theme-table-tennis,
[data-appearance="system"] .theme-table-tennis {
  --sport-brand-contrast: #b91c1c;
}
[data-appearance="light"] .theme-tennis,
[data-appearance="system"] .theme-tennis {
  --sport-brand-contrast: #15803d;
}

@media (prefers-color-scheme: dark) {
  [data-appearance="system"] .theme-pickleball,
  [data-appearance="system"] .theme-table-tennis,
  [data-appearance="system"] .theme-tennis {
    --sport-brand-contrast: var(--sport-brand);
  }
}

.theme-header { border-bottom-color: var(--sport-brand); }
.theme-hero {
  /* Always a near-black surface regardless of appearance, so the light-mode
     --sport-brand-contrast override (above) would be unreadable here —
     reset back to the bright --sport-brand for this subtree. */
  --sport-brand-contrast: var(--sport-brand);
  background-color: #030712;
  background-image:
    radial-gradient(ellipse at 20% 50%, var(--sport-surface) 0%, transparent 60%),
    radial-gradient(circle at 85% 20%, var(--sport-surface-2) 0%, transparent 32%);
}

.theme-brand-text { color: var(--sport-brand-contrast, var(--sport-brand)); }
.theme-brand-text-hover:hover { color: var(--sport-brand-contrast, var(--sport-brand)); }
.theme-brand-border { border-color: var(--sport-brand); }
.theme-brand-border-hover:hover { border-color: var(--sport-brand); }
.theme-brand-soft { background-color: var(--sport-brand-soft); }
.theme-brand-border-subtle { border-color: var(--sport-brand-border-soft); }
.theme-brand-divider { background-color: var(--sport-brand-border-soft); }
.theme-brand-glow {
  text-shadow:
    0 0 10px var(--sport-brand-glow),
    0 0 24px color-mix(in srgb, var(--sport-brand) 28%, transparent);
}

.theme-button-primary {
  background-color: var(--sport-brand);
  color: #030712;
  box-shadow:
    0 16px 30px -18px var(--sport-brand),
    0 0 18px -6px var(--sport-brand-glow);
}

.theme-button-primary:hover {
  background-color: var(--sport-brand-hover);
}

.theme-input:focus {
  border-color: var(--sport-brand);
  box-shadow: 0 0 0 1px var(--sport-brand);
}

/* Remove number input spinners for score fields */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
input[type="number"] { -moz-appearance: textfield; }

/* Smooth tab transitions */
[id^="tab-"] { animation: fadeIn 0.15s ease; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  [id^="tab-"] { animation: none; }
}

/* Live score pulse on game cards */
.game-live { border-color: rgb(234 88 12) !important; }
.game-live {
  box-shadow:
    0 0 0 1px rgb(234 88 12 / 0.45),
    0 0 22px -10px rgb(234 88 12 / 0.7);
}
/* Party — this literal orange predates the appearance system and is left
   alone everywhere else (Dark pixel-parity, Light/System unchanged), but in
   Party it should follow the same gold --color-live-border the round-badge-
   live pill already uses, not stay stuck on an untouched color. */
[data-appearance="party"] .game-live { border-color: var(--color-live-border) !important; }
[data-appearance="party"] .game-live {
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--color-live-border) 45%, transparent),
    0 0 22px -10px color-mix(in srgb, var(--color-live-border) 70%, transparent);
}

.no-scrollbar {
  -ms-overflow-style: none;
  scrollbar-width: none;
}

.no-scrollbar::-webkit-scrollbar {
  display: none;
}

/* Emoji picker (rules editor) — only what utility classes can't express
   cleanly: the native <dialog> backdrop, its viewport-constrained size, the
   scrolling choice grid, and a visually stable tile size for ~1913 buttons. */
.emoji-picker-dialog {
  margin: auto;
  padding: 0;
  border: 1px solid var(--color-border);
  color: var(--color-text);
  width: min(28rem, calc(100vw - 2rem));
  max-height: min(32rem, calc(100vh - 4rem));
}

.emoji-picker-dialog::backdrop {
  background: rgb(0 0 0 / 0.5);
}

.emoji-picker-dialog-inner {
  max-height: inherit;
  min-height: 0;
}

.emoji-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.75rem, 1fr));
  gap: 0.375rem;
  max-height: 14rem;
  overflow-y: auto;
  padding: 0.25rem;
  border: 1px solid var(--color-border);
  border-radius: 0.5rem;
}

.emoji-picker-choice {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.125rem;
  min-height: 2.75rem;
  padding: 0.25rem;
  border-radius: 0.375rem;
  border: 1px solid transparent;
  background: transparent;
  cursor: pointer;
  transition: background-color 0.1s ease, border-color 0.1s ease;
}

.emoji-picker-choice:hover,
.emoji-picker-choice:focus-visible {
  background: var(--color-surface-raised);
  border-color: var(--color-border-strong);
  outline: none;
}

.emoji-picker-choice-glyph {
  font-size: 1.25rem;
  line-height: 1;
}

.emoji-picker-choice-code {
  font-size: 0.55rem;
  font-family: "DM Mono", ui-monospace, monospace;
  color: var(--color-text-dim);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (max-width: 480px) {
  .emoji-picker-dialog {
    width: calc(100vw - 1.5rem);
    max-height: calc(100vh - 3rem);
  }

  .emoji-picker-grid {
    max-height: 50vh;
  }
}
