:root {
  --bg: #14100c;
  --panel: #211a13;
  --surface: var(--panel);
  --surface-elevated: #2a2115;
  --border: #7a5c2e;
  --gold: #d9b25c;
  --gold-bright: #f0d896;
  --magic: #8a6fd8;
  /* Fluid width for the rankings/music side docks (see the @media blocks
     using it below) — scales down smoothly from the original fixed 320px
     as the viewport narrows, instead of an all-or-nothing cutoff, so both
     docks keep working on ordinary laptop screens (1350px+) rather than
     only on very wide monitors. 474 = half the centered #lobby column
     (450) + the dock's own 1.5rem edge margin (24px); floors at 200px,
     the narrowest width its own contents (table columns, music buttons)
     still comfortably fit at. */
  --dock-width: clamp(200px, calc(50vw - 474px), 320px);
  --success: #6fd66a;
  --error: #e05c52;
  --warning: #e0a94d;
  --info: #5cb3e0;
  --text: #ecdfc4;
  --text-secondary: #a5947a;
  --dim: #a5947a;
  --disabled: #6b6156;
  --focus-ring: var(--gold);
  --hp: #b5322f;
  --mana: #3c7fd0;
  --vanguard: #3a2c1a;
  --rearguard: #241c12;
  --font-display: 'Cinzel', Georgia, 'Times New Roman', serif;
  /* System font stack — no external request, always available, reads as a
     modern UI face. Nothing set this before, so body/UI text silently fell
     back to the Georgia serif stack meant only for decorative headings. */
  --font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  /* Global multiplier for card name/ability text size — adjustable via the
     stepper in the HUD-editing toolbar (applyCardTextScale() in client.js),
     persisted to localStorage and, when logged in, the player's account.
     Left at 1 (no-op) until JS applies a saved preference. */
  --card-text-scale: 1;

  /* Faction accents — the 10 hex values .faction-card[data-faction] already
     used as bare literals, named so they can be referenced elsewhere too. */
  --faction-beast: #4f8a3a;
  --faction-clock: #c17a2e;
  --faction-damned: #8a1f2b;
  --faction-dwarf: #d4a017;
  --faction-dynasty: #2f9e6e;
  --faction-elf: #3f9eae;
  --faction-fallen: #7a4fc4;
  --faction-human: #3c6fd0;
  --faction-orc: #b5432e;
  --faction-undead: #8a9e4a;

  /* Spacing scale — 4/8/12/16/24/32px. Applied to components touched in
     this pass, not a file-wide rewrite of every existing margin/padding. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;

  /* Type scale — clamp()-based so it's fluid across viewport widths rather
     than jumping at breakpoints. Applied to lobby headings/section titles
     in this pass; the ~55 pre-existing one-off font-size values elsewhere
     (card text, HUD text tuned against real layout constraints) are left
     alone rather than force-fit into this scale. */
  --text-xs: clamp(0.7rem, 0.67rem + 0.15vw, 0.78rem);
  --text-sm: clamp(0.82rem, 0.78rem + 0.2vw, 0.92rem);
  --text-base: clamp(0.95rem, 0.9rem + 0.2vw, 1.05rem);
  --text-md: clamp(1.05rem, 1rem + 0.3vw, 1.2rem);
  --text-lg: clamp(1.2rem, 1.1rem + 0.4vw, 1.4rem);
  --text-xl: clamp(1.5rem, 1.3rem + 0.8vw, 1.85rem);
  --text-2xl: clamp(1.9rem, 1.6rem + 1.4vw, 2.5rem);
  --text-3xl: clamp(2.3rem, 1.9rem + 2vw, 3.2rem);
}

* { box-sizing: border-box; }

/* A visible focus ring matching the theme for every interactive element —
   nothing suppresses the browser default elsewhere in this file, but the
   default ring reads inconsistently against a dark parchment/gold UI. */
button:focus-visible, input:focus-visible, select:focus-visible, a:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

body {
  margin: 0;
  position: relative;
  background:
    repeating-linear-gradient(135deg, rgba(255,255,255,0.015) 0 2px, transparent 2px 6px),
    radial-gradient(ellipse 900px 600px at 15% 85%, #8a6fd81a 0%, transparent 60%),
    radial-gradient(ellipse at top, #241c12 0%, var(--bg) 70%),
    radial-gradient(ellipse at 50% 50%, transparent 45%, #000 105%);
  color: var(--text);
  font-family: var(--font-body);
  min-height: 100vh;
  /* A UX deterrent against casual Ctrl+A/click-drag selection, not real
     protection — anyone can still bypass this via DevTools. Re-enabled
     below on every actual text-entry element so typing/selecting in forms,
     chat, and search boxes is completely unaffected. */
  user-select: none;
  -webkit-user-select: none;
}
input, textarea, [contenteditable] {
  user-select: text;
  -webkit-user-select: text;
}

/* Visually hidden but still reachable by screen readers/keyboard focus —
   used for form labels where a visible label would clutter an already-
   clear layout (login form, chat). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
/* A handful of slow-drifting motes for a faint sense of ambient magic —
   a separate fixed layer so it can animate independently of the static
   background gradients above, and be switched off entirely for users who
   asked for reduced motion. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  background-image:
    radial-gradient(2px 2px at 20% 30%, #f0d89670, transparent),
    radial-gradient(2px 2px at 75% 60%, #8a6fd870, transparent),
    radial-gradient(1.5px 1.5px at 50% 80%, #f0d89650, transparent);
  background-repeat: no-repeat;
  animation: drift-motes 40s ease-in-out infinite;
}
@keyframes drift-motes {
  0%, 100% { background-position: 0 0, 0 0, 0 0; }
  50% { background-position: 0 -18px, 0 14px, 0 -10px; }
}
@media (prefers-reduced-motion: reduce) {
  body::after { animation: none; }
  #arrowPath { animation: none; }
  /* Continuous/looping decorative pulses only — the state they signal
     (whose turn, legal target, playable slot, the front-line divider)
     still reads from the static color/border/outline underneath, so
     turning off just the looping animation loses nothing functional.
     Brief, one-shot feedback (combat hits, card-enter, coin flip) stays
     as-is since those convey a discrete event, not ambient motion. */
  .hp-panel.legal-target,
  .hp-panel.active-turn,
  .slot.legal-target,
  .slot.can-play,
  .slot.spell-target,
  .lane-divider,
  .turn-timer.timer-critical,
  .plan-castle-lethal {
    animation: none;
  }
}

.hidden { display: none !important; }

body.in-game {
  height: 100vh;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.top-right-bar {
  /* Positioned relative to body (position:relative, set above), not the
     viewport — pins it near the top of the PAGE (same spot as a real
     fixed bar on load) but lets it scroll away with the rest of the lobby
     instead of persistently floating over content further down, like the
     SEO section near the bottom of a long page. */
  position: absolute;
  top: 0.8rem;
  right: 0.8rem;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.lang-picker {
  display: flex;
  gap: 0.3rem;
  background: #00000040;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.2rem;
}
.lang-btn {
  background: transparent;
  border: none;
  color: var(--dim);
  font-family: var(--font-display);
  font-size: 0.7rem;
  letter-spacing: 1px;
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.lang-btn:hover {
  color: var(--text);
}
.lang-btn.active {
  background: var(--gold);
  color: var(--bg);
}
.coin-badge {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  background: #00000040;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.3rem 0.7rem;
  font-family: var(--font-display);
  font-size: 0.8rem;
  color: var(--gold-bright);
  white-space: nowrap;
}
.coin-badge-icon { font-size: 0.9rem; line-height: 1; }
.coin-badge.coin-badge--guest { color: var(--dim); font-size: 0.72rem; letter-spacing: 0.5px; }
.buy-coins-btn {
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 50%;
  background: transparent;
  border: 1px solid var(--gold);
  color: var(--gold);
  font-family: var(--font-display);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.buy-coins-btn:hover { background: linear-gradient(var(--gold-bright), var(--gold)); color: #1a140d; }
/* The battle screen's own fixed top-right controls (Back to Lobby / Edit
   HUD Layout) occupy this exact corner at a higher z-index — keeping this
   bar there too would sit invisibly behind them and steal their clicks. */
body.in-game .top-right-bar {
  display: none;
}

/* ---------- Partner banner (Castle Battle cross-promo) ---------- */
.partner-banner {
  /* See .top-right-bar's comment — same reasoning, same fix. */
  position: absolute;
  top: 0.8rem;
  left: 0.8rem;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  max-width: 280px;
  background: #00000040;
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.45rem 0.8rem 0.45rem 0.5rem;
  text-decoration: none;
  transition: border-color 0.15s, background 0.15s, transform 0.15s;
}
.partner-banner:hover {
  border-color: var(--gold);
  background: #00000066;
  transform: translateY(-1px);
}
.partner-banner-logo { width: 32px; height: 32px; flex-shrink: 0; }
.partner-banner-text { display: flex; flex-direction: column; line-height: 1.25; }
.partner-banner-text strong {
  font-family: var(--font-display);
  font-size: 0.82rem;
  color: var(--gold-bright);
  letter-spacing: 0.3px;
}
.partner-banner-text small { font-size: 0.68rem; color: var(--dim); }
body.in-game .partner-banner { display: none; }
@media (max-width: 760px) {
  .partner-banner { max-width: 180px; padding: 0.4rem 0.6rem 0.4rem 0.4rem; }
  .partner-banner-logo { width: 26px; height: 26px; }
  .partner-banner-text small { display: none; }
  /* All three pieces (coin badge, buy button, lang picker) crammed onto
     one row read as one glued-together cluster at this width, and pushing
     the whole row down to clear the banner (an earlier attempt) instead
     ate into the logo below it. Split into two right-aligned rows —
     language picker on top, coins + buy button underneath — so each row
     is narrow enough on its own to never reach across into the banner's
     space, with no extra vertical push needed at all. */
  .top-right-bar { flex-wrap: wrap; justify-content: flex-end; gap: 0.4rem; row-gap: 0.35rem; }
  /* flex-basis:100% goes on this plain, unstyled wrapper (to force the
     line break onto its own row) rather than on .lang-picker itself —
     .lang-picker carries its own pill background/border, so giving IT
     flex-basis:100% stretched that visible pill across the full row
     width behind the three buttons instead of just breaking the line,
     exactly the "bar is way too long" glue-together look reported. The
     wrapper takes the full-width slot; .lang-picker inside it stays
     sized to its own content and gets pushed to the right edge by the
     wrapper's own justify-content. */
  .top-right-bar .lang-picker-row { order: -1; flex-basis: 100%; display: flex; justify-content: flex-end; }
}

.logo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  margin: 2rem 0 0.2rem;
}
.logo-sigil {
  width: 48px;
  height: 48px;
  filter: drop-shadow(0 0 8px #d9b25c60);
}
/* Real, always-visible entry points into the full SEO content pages —
   part of .logo itself so it inherits whichever grid-area "logo" gets in
   every admin layout, guaranteeing zero-scroll visibility everywhere,
   rather than depending on how far down .seo-home-content ends up. Opens
   in a new tab since navigating away in this tab would lose any
   in-progress login/match-setup state. */
.home-content-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 0.2rem;
}
.home-content-nav a {
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.78rem;
  letter-spacing: 0.3px;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: #00000030;
  transition: border-color 0.15s, color 0.15s;
}
.home-content-nav a:hover {
  color: var(--gold-bright);
  border-color: var(--gold);
}
h1 {
  text-align: center;
  font-family: var(--font-display);
  font-weight: 700;
  background: linear-gradient(180deg, var(--gold-bright), var(--gold));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  font-size: var(--text-3xl);
  letter-spacing: 3px;
  margin: 0;
  text-shadow: 0 2px 10px #00000090;
}
h1 span {
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-sm);
  color: var(--dim);
  letter-spacing: 5px;
  text-transform: uppercase;
  -webkit-text-fill-color: var(--dim);
}

/* ---------- Buttons ---------- */
/* One shared primary/gold treatment, used everywhere a call-to-action
   button appears (lobby, account form, game controls, game-over overlay)
   instead of the same gradient/border/shadow declared five separate times.
   Context-specific selectors below only adjust sizing where needed. */
.btn {
  background: linear-gradient(#e7c877, #b8912f);
  border: 1px solid #5c4419;
  box-shadow: inset 0 1px 0 #ffe9b380, 0 3px 8px #0006;
  color: #241c12;
  font-family: inherit;
  font-weight: bold;
  padding: 0.6rem 1.2rem;
  border-radius: 6px;
  font-size: 1rem;
  cursor: pointer;
  transition: box-shadow 0.15s, transform 0.15s;
}
.btn:hover:not(:disabled) {
  box-shadow: inset 0 1px 0 #ffe9b380, 0 3px 10px #0007, 0 0 12px color-mix(in srgb, var(--gold) 50%, transparent);
}
.btn:active:not(:disabled) { transform: translateY(1px); box-shadow: inset 0 1px 3px #00000060; }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn.btn-danger {
  background: linear-gradient(#d65d59, var(--hp));
  border-color: #4a1210;
  color: #fff2f0;
}
.btn.btn-danger:hover:not(:disabled) {
  box-shadow: inset 0 1px 0 #ffffff40, 0 3px 10px #0007, 0 0 12px color-mix(in srgb, var(--hp) 60%, transparent);
}

/* ---------- Lobby ---------- */
#lobby {
  max-width: 900px;
  margin: 0 auto;
  padding: 1rem 1.5rem 3rem;
  background: radial-gradient(ellipse at 50% 0%, #2a2013a0 0%, transparent 60%);
}

/* ---------- Lobby layouts (admin-controlled, see Admin Panel) ---------- */
/* Site-wide, applied via #lobby[data-layout] set from /api/site-settings on
   load (client.js's applyLobbyLayout()). "classic" (the default/fallback) is
   simply the absence of a [data-layout] override above, i.e. today's plain
   block-flow stack — nothing to define for it.

   Each variant below repositions the SAME existing top-level lobby children
   via CSS Grid named areas — no markup changes, no duplicated content. Since
   .lobby-actions and .ai-match-panel are mutually exclusive (client.js toggles
   one to display:none, which removes it from grid layout entirely), it's safe
   for them to share one "play" area — only whichever is visible ever actually
   occupies it. */
#lobby[data-layout="sidebar"],
#lobby[data-layout="compact"],
#lobby[data-layout="showcase"],
#lobby[data-layout="sidebar-hud"] {
  display: grid;
  row-gap: 0;
}
#lobby[data-layout="sidebar"] > .logo,
#lobby[data-layout="compact"] > .logo,
#lobby[data-layout="showcase"] > .logo,
#lobby[data-layout="sidebar-hud"] > .logo { grid-area: logo; }
#lobby[data-layout="sidebar"] > .account-panel,
#lobby[data-layout="compact"] > .account-panel,
#lobby[data-layout="showcase"] > .account-panel,
#lobby[data-layout="sidebar-hud"] > .account-panel { grid-area: acct; }
#lobby[data-layout="sidebar"] > .faction-picker,
#lobby[data-layout="compact"] > .faction-picker,
#lobby[data-layout="showcase"] > .faction-picker,
#lobby[data-layout="sidebar-hud"] > .faction-picker { grid-area: fact; }
#lobby[data-layout="sidebar"] > #myCustomDecksPanel,
#lobby[data-layout="compact"] > #myCustomDecksPanel,
#lobby[data-layout="showcase"] > #myCustomDecksPanel,
#lobby[data-layout="sidebar-hud"] > #myCustomDecksPanel { grid-area: cust; }
#lobby[data-layout="sidebar"] > .deck-compare-trigger,
#lobby[data-layout="compact"] > .deck-compare-trigger,
#lobby[data-layout="showcase"] > .deck-compare-trigger,
#lobby[data-layout="sidebar-hud"] > .deck-compare-trigger { grid-area: dcmp; }
#lobby[data-layout="sidebar"] > .lobby-hud-preview,
#lobby[data-layout="compact"] > .lobby-hud-preview,
#lobby[data-layout="showcase"] > .lobby-hud-preview,
#lobby[data-layout="sidebar-hud"] > .lobby-hud-preview { grid-area: hud; }
#lobby[data-layout="sidebar"] > .mode-tabs,
#lobby[data-layout="compact"] > .mode-tabs,
#lobby[data-layout="showcase"] > .mode-tabs,
#lobby[data-layout="sidebar-hud"] > .mode-tabs { grid-area: modes; }
#lobby[data-layout="sidebar"] > .lobby-actions,
#lobby[data-layout="sidebar"] > .ai-match-panel,
#lobby[data-layout="compact"] > .lobby-actions,
#lobby[data-layout="compact"] > .ai-match-panel,
#lobby[data-layout="showcase"] > .lobby-actions,
#lobby[data-layout="showcase"] > .ai-match-panel,
#lobby[data-layout="sidebar-hud"] > .lobby-actions,
#lobby[data-layout="sidebar-hud"] > .ai-match-panel { grid-area: play; }
#lobby[data-layout="sidebar"] > .lobby-status,
#lobby[data-layout="compact"] > .lobby-status,
#lobby[data-layout="showcase"] > .lobby-status,
#lobby[data-layout="sidebar-hud"] > .lobby-status { grid-area: status; }
#lobby[data-layout="sidebar"] > #deckPicker,
#lobby[data-layout="compact"] > #deckPicker,
#lobby[data-layout="showcase"] > #deckPicker,
#lobby[data-layout="sidebar-hud"] > #deckPicker { grid-area: deckpick; }
#lobby[data-layout="sidebar"] > #boardModePicker,
#lobby[data-layout="compact"] > #boardModePicker,
#lobby[data-layout="showcase"] > #boardModePicker,
#lobby[data-layout="sidebar-hud"] > #boardModePicker { grid-area: boardmode; }
#lobby[data-layout="showcase"] > .rankings-panel { grid-area: rank; }
/* Neither of these had a grid-area at all originally, which meant both
   were silently pushed to a brand-new implicit row appended AFTER the
   entire named template above — regardless of where in the DOM they sit.
   .lobby-tutorial-entry ("Learn to Play") gets an explicit spot right
   after "acct", ahead of the faction picker and match setup — new
   players who just registered should see it before anything else, not
   after they've already started picking a deck. .seo-home-content stays
   right after "status" (see each layout's grid-template-areas below),
   which still guarantees it renders before the rankings table and the
   Sidebar+HUD widgets in every layout. */
#lobby[data-layout="sidebar"] > .lobby-tutorial-entry,
#lobby[data-layout="compact"] > .lobby-tutorial-entry,
#lobby[data-layout="showcase"] > .lobby-tutorial-entry,
#lobby[data-layout="sidebar-hud"] > .lobby-tutorial-entry { grid-area: tutorial; }
#lobby[data-layout="sidebar"] > .seo-home-content,
#lobby[data-layout="compact"] > .seo-home-content,
#lobby[data-layout="showcase"] > .seo-home-content,
#lobby[data-layout="sidebar-hud"] > .seo-home-content { grid-area: seo; }

/* Sidebar: same width/centering as Classic at every screen size (see the
   note on Compact below — splitting #lobby into real grid columns is what
   previously shoved and shrank the main content instead of leaving it
   alone). Rankings has no grid-area here, so it just auto-places at the end
   of the single column exactly like Classic, and only pops out to a fixed
   dock on the right edge once the viewport is comfortably wide enough. */
#lobby[data-layout="sidebar"] {
  grid-template-areas: "logo" "acct" "tutorial" "fact" "cust" "dcmp" "hud" "modes" "play" "deckpick" "boardmode" "status" "seo";
}
@media (min-width: 1350px) {
  #lobby[data-layout="sidebar"] > .rankings-panel {
    position: fixed;
    margin: 0;
    top: 6rem;
    right: 1.5rem;
    width: var(--dock-width);
  }
}

/* Sidebar + HUD: same single-column, unshifted width as every other layout.
   Play options move up ahead of the faction picker (same reasoning as
   Compact), and the two decorative compartments (see
   renderSidebarPreview()/renderSidebarHighlights() in client.js) sit inline
   near the end, right before the full rankings panel — always part of the
   normal reading column, never docked, so there's no fragile stacking of
   multiple fixed-position panels to get right. Rankings alone still gets the
   same wide-screen fixed dock as Sidebar/Compact. */
#lobby[data-layout="sidebar-hud"] {
  grid-template-areas: "logo" "acct" "tutorial" "modes" "play" "deckpick" "boardmode" "fact" "cust" "dcmp" "hud" "status" "seo" "preview" "highlights" "rank";
}
#lobby[data-layout="sidebar-hud"] > #lobbySidebarPreview { grid-area: preview; }
#lobby[data-layout="sidebar-hud"] > #lobbySidebarHighlights { grid-area: highlights; }
#lobby[data-layout="sidebar-hud"] > .rankings-panel { grid-area: rank; }
#lobby[data-layout="sidebar-hud"] .sidebar-widget { display: block; }
@media (min-width: 1350px) {
  #lobby[data-layout="sidebar-hud"] > .rankings-panel {
    position: fixed;
    margin: 0;
    top: 6rem;
    right: 1.5rem;
    width: var(--dock-width);
  }
}

.sidebar-widget {
  display: none; /* only ever shown by the sidebar-hud rule above */
  background: linear-gradient(180deg, #2a2115, var(--panel));
  border: 2px solid color-mix(in srgb, var(--gold) 45%, var(--border));
  border-radius: 10px;
  padding: 0.9rem 1rem;
  margin-bottom: 1.2rem;
  box-shadow: inset 0 0 20px #00000060, 0 6px 16px #0007;
}
.sidebar-widget h4 {
  margin: 0 0 0.6rem;
  color: var(--gold);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.sidebar-preview .sidebar-matchup { display: flex; align-items: center; justify-content: center; gap: 0.5rem; margin-bottom: 0.7rem; }
.sidebar-preview .sidebar-matchup-vs { color: var(--dim); font-family: var(--font-display); font-size: var(--text-xs); }
.sidebar-preview .sidebar-faction-badge {
  flex: 1;
  text-align: center;
  font-size: var(--text-xs);
  font-weight: bold;
  padding: 0.3rem 0.4rem;
  border-radius: 6px;
  border: 1px solid var(--border);
  color: var(--text-secondary);
}
.sidebar-preview .sidebar-card-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.6rem; }
.sidebar-highlights .sidebar-highlight-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid #4a3a2250;
  font-size: var(--text-sm);
}
.sidebar-highlights .sidebar-highlight-row:last-child { border-bottom: none; }
.sidebar-highlights .sidebar-highlight-rank {
  flex: 0 0 auto;
  width: 1.6em;
  height: 1.6em;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: linear-gradient(#3a2c14, #241c12);
  border: 1px solid var(--gold);
  color: var(--gold);
  font-weight: bold;
  font-size: var(--text-xs);
}
.sidebar-highlights .sidebar-highlight-name { flex: 1; color: var(--text); }
.sidebar-highlights .sidebar-highlight-value { color: var(--text-secondary); font-size: var(--text-xs); }
.sidebar-widget .admin-loading { padding: 0.3rem 0; font-size: var(--text-xs); }

/* Compact: play options move up ahead of the faction picker — get into a
   match fast without scrolling past deck browsing you may not need again.
   Everything here stays exactly as wide/centered as Classic — max-width and
   grid-template-columns are deliberately NOT overridden, unlike the other
   grid-based layouts, because widening/splitting #lobby itself is exactly
   what shifted the centered content left last time. Rankings has no
   grid-area assigned here, so it just falls back to plain auto-placement at
   the end of the single column (same spot as Classic) by default. */
#lobby[data-layout="compact"] {
  grid-template-areas: "logo" "acct" "tutorial" "modes" "play" "deckpick" "boardmode" "fact" "cust" "dcmp" "hud" "status" "seo";
}
/* Only on screens wide enough that a 300px dock on the right edge can't
   possibly overlap the still-centered 900px column (comfortable margin
   included) does rankings actually get pulled out of the flow above and
   pinned to the edge, next to the scrollbar — exactly what was asked for,
   without ever touching or squeezing the main column at any width. */
@media (min-width: 1350px) {
  #lobby[data-layout="compact"] > .rankings-panel {
    position: fixed;
    margin: 0;
    top: 6rem;
    right: 1.5rem;
    width: var(--dock-width);
  }
}

/* Showcase: faction picker is the hero, right under the logo and enlarged;
   the account panel shrinks to a slim strip below it. */
#lobby[data-layout="showcase"] {
  grid-template-columns: 1fr;
  grid-template-areas: "logo" "tutorial" "fact" "cust" "acct" "dcmp" "hud" "modes" "play" "deckpick" "boardmode" "status" "seo" "rank";
}
#lobby[data-layout="showcase"] > .account-panel { max-width: 640px; padding: 0.6rem 1rem; }
#lobby[data-layout="showcase"] .faction-card .thumb { width: 100px; height: 150px; }

/* Below the lobby's own mobile breakpoint, every layout collapses back to a
   plain block-flow stack — grid-area is meaningless once display:block takes
   over, so this naturally renders in DOM order (Classic's order) regardless
   of which layout the admin picked. Four bespoke mobile arrangements aren't
   worth the complexity for a pick-an-example feature. */
@media (max-width: 720px) {
  #lobby[data-layout="sidebar"],
  #lobby[data-layout="compact"],
  #lobby[data-layout="showcase"],
  #lobby[data-layout="sidebar-hud"] {
    display: block;
  }
  #lobby[data-layout="showcase"] > .account-panel { max-width: 420px; padding: 0.9rem 1.1rem; }
  /* The two decorative compartments are a desktop affordance, same as every
     other layout choice — hidden below the breakpoint rather than adding a
     5th bespoke mobile arrangement. */
  #lobby[data-layout="sidebar-hud"] .sidebar-widget { display: none; }
}

.faction-picker {
  display: grid;
  /* minmax(0,1fr), not bare 1fr: a grid track's implicit min is auto (its
     content's min-content size) unless overridden — .deck-badge's
     white-space:nowrap pills forced tracks wider than their fair 1fr share
     on narrow viewports, overflowing the page horizontally. minmax(0,*)
     lets the track actually shrink to the space it's given. */
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 12px;
  margin: 2rem 0;
}
.faction-card {
  --accent: var(--gold);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  padding: 1rem 0.5rem;
  background: linear-gradient(#2a2115, var(--panel));
  border: 2px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  font-family: inherit;
  font-size: 1rem;
  cursor: pointer;
  transition: transform 0.15s, border-color 0.15s, box-shadow 0.15s;
}
/* One shared component, themed per faction via a custom property keyed
   off the same data-faction attribute already used to pick a deck. */
.faction-card[data-faction="beast"] { --accent: #4f8a3a; }
.faction-card[data-faction="clock"] { --accent: #c17a2e; }
.faction-card[data-faction="damned"] { --accent: #8a1f2b; }
.faction-card[data-faction="dwarf"] { --accent: #d4a017; }
.faction-card[data-faction="dynasty"] { --accent: #2f9e6e; }
.faction-card[data-faction="elf"] { --accent: #3f9eae; }
.faction-card[data-faction="fallen"] { --accent: #7a4fc4; }
.faction-card[data-faction="human"] { --accent: #3c6fd0; }
.faction-card[data-faction="orc"] { --accent: #b5432e; }
.faction-card[data-faction="undead"] { --accent: #8a9e4a; }
.faction-card:hover {
  transform: translateY(-3px);
  border-color: var(--accent);
  box-shadow: 0 6px 16px #0008, 0 0 14px color-mix(in srgb, var(--accent) 45%, transparent);
}
.faction-card.selected {
  border-color: var(--accent);
  background: linear-gradient(#3a2c14, #241c12);
  box-shadow: 0 0 12px #00000080 inset, 0 0 14px color-mix(in srgb, var(--accent) 60%, transparent);
}
.faction-card.faction-card--locked {
  cursor: not-allowed;
  opacity: 0.55;
}
.faction-card.faction-card--locked:hover {
  transform: none;
  border-color: var(--border);
  box-shadow: none;
}
.faction-card .thumb {
  position: relative;
  width: 84px;
  height: 126px;
  border-radius: 8px;
  overflow: hidden;
  background: #0f0c08;
  border: 2px solid var(--border);
  box-shadow: inset 0 0 10px #000a;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.faction-card:hover .thumb, .faction-card.selected .thumb {
  border-color: var(--accent);
  box-shadow: 0 0 12px color-mix(in srgb, var(--accent) 55%, transparent);
}
.faction-card .thumb img { width: 100%; height: 100%; object-fit: contain; }
.faction-card .label { font-family: var(--font-display); font-weight: 600; font-size: 0.85rem; text-align: center; letter-spacing: 0.5px; }

.deck-badge-row { display: flex; gap: 4px; justify-content: center; flex-wrap: wrap; }
.deck-badge {
  font-size: 0.62rem;
  font-family: var(--font-display);
  letter-spacing: 0.3px;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 1px 6px;
  color: var(--dim);
  background: #00000030;
  white-space: nowrap;
}
.faction-card .deck-badge { color: var(--gold); border-color: color-mix(in srgb, var(--accent) 50%, var(--border)); }
.deck-badge-difficulty[data-difficulty="Beginner"] { color: var(--success); border-color: var(--success); }
.deck-badge-difficulty[data-difficulty="Intermediate"] { color: var(--gold-bright); border-color: var(--gold); }
.deck-badge-difficulty[data-difficulty="Advanced"], .deck-badge-difficulty[data-difficulty="Expert"] { color: #e0897a; border-color: var(--hp); }
.deck-badge-status[data-status="owned"] { color: var(--success); border-color: var(--success); }
.deck-badge-status[data-status="locked"] { color: var(--gold-bright); border-color: var(--gold); }
.deck-badge-status[data-status="guest-restricted"] { color: var(--dim); border-color: var(--border); }
.faction-card .buy-deck-btn {
  font-family: inherit;
  font-size: 0.68rem;
  padding: 2px 10px;
  border: 1px solid var(--gold);
  background: transparent;
  color: var(--gold-bright);
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.faction-card .buy-deck-btn:hover { background: linear-gradient(var(--gold-bright), var(--gold)); color: #1a140d; }
.faction-card .deck-tagline {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text);
  text-align: center;
  line-height: 1.35;
  min-height: 1.6em;
}
.faction-card .deck-action-row { display: flex; gap: 6px; margin-top: 2px; }
.faction-card .view-deck-btn, .faction-card .build-deck-btn {
  font-family: inherit;
  font-size: 0.68rem;
  padding: 2px 10px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--gold);
  border-radius: 6px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.faction-card .view-deck-btn:hover, .faction-card .build-deck-btn:hover { border-color: var(--gold); color: var(--gold-bright); }
.faction-card .view-deck-btn:active, .faction-card .build-deck-btn:active { background: color-mix(in srgb, var(--gold) 15%, transparent); }

.deck-compare-trigger {
  text-align: center;
  margin: 0.4rem 0 1.4rem;
  padding: 0.8rem 1rem;
  border: 1px dashed color-mix(in srgb, var(--gold) 40%, var(--border));
  border-radius: 10px;
  background: #1c150d40;
}
.deck-compare-trigger p { margin: 0.5rem 0 0; color: var(--dim); font-size: 0.85rem; }

.my-custom-decks-panel {
  margin: 0 0 1.4rem;
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: #1c150d40;
}
.my-custom-decks-panel h3 {
  margin: 0 0 0.6rem;
  color: var(--gold);
  font-family: var(--font-display);
  font-size: 0.95rem;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.my-custom-decks-empty { margin: 0; color: var(--dim); font-size: 0.88rem; }
.my-custom-decks-list { display: flex; flex-direction: column; gap: 8px; }
.my-custom-deck-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #00000030;
}
.my-custom-deck-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.my-custom-deck-name { font-family: var(--font-display); color: var(--text); font-size: 0.95rem; }
.my-custom-deck-meta { color: var(--dim); font-size: 0.78rem; }
.my-custom-deck-actions { display: flex; gap: 0.9rem; flex-shrink: 0; }
.my-custom-deck-delete-btn.confirm-armed { color: var(--hp); }

.lobby-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
}
.lobby-panel {
  position: relative;
  background: linear-gradient(180deg, #2a2115, var(--panel));
  border: 2px solid color-mix(in srgb, var(--gold) 45%, var(--border));
  border-radius: 10px;
  padding: 1.2rem 1.4rem;
  box-shadow: inset 0 0 24px #00000060, 0 6px 18px #0007;
}
.lobby-panel::before, .lobby-panel::after,
.account-panel::before, .account-panel::after,
.rankings-panel::before, .rankings-panel::after {
  content: '';
  position: absolute;
  width: 9px;
  height: 9px;
  background: var(--gold);
  opacity: 0.7;
  transform: rotate(45deg);
  box-shadow: 0 0 6px #d9b25c90;
  pointer-events: none;
}
.lobby-panel::before, .account-panel::before, .rankings-panel::before { top: -4px; left: -4px; }
.lobby-panel::after, .account-panel::after, .rankings-panel::after { bottom: -4px; right: -4px; }
.lobby-panel h2 { margin-top: 0; color: var(--gold); font-family: var(--font-display); font-weight: 600; font-size: var(--text-lg); letter-spacing: 0.5px; }
.lobby-panel p { color: var(--text-secondary); font-size: var(--text-sm); }
.lobby-panel input {
  width: 100%;
  padding: 0.6rem;
  margin-bottom: 0.6rem;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: #0f0c08;
  color: var(--text);
  font-size: 1.1rem;
  letter-spacing: 3px;
  text-align: center;
  text-transform: uppercase;
}
.room-code {
  margin-top: 0.8rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.6rem;
  letter-spacing: 5px;
  color: var(--gold-bright);
  text-align: center;
  background: radial-gradient(ellipse at center, #3a2c1480, #1c150d80);
  border: 1px solid var(--gold);
  box-shadow: 0 0 16px color-mix(in srgb, var(--gold) 35%, transparent) inset, 0 0 10px color-mix(in srgb, var(--gold) 25%, transparent);
  padding: 0.6rem 0.8rem;
  border-radius: 8px;
}
.room-code .copy-code-btn {
  flex-shrink: 0;
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--dim);
  font-size: 0.85rem;
  letter-spacing: 0;
  padding: 0.3rem 0.5rem;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
}
.room-code .copy-code-btn:hover { border-color: var(--gold); color: var(--gold); }
.room-code .copy-code-btn.copied { border-color: var(--success); color: var(--success); }
.lobby-status { text-align: center; margin-top: 1.5rem; color: var(--dim); min-height: 1.5em; }

/* Ranked vs AI Match — deliberately the most visually distinct choice in the
   lobby (see the master prompt's "make the difference impossible to
   misunderstand"). Same active/hidden toggle idiom as .ranking-tab below,
   just wider so the always-visible one-line consequence copy fits without
   truncating. */
.mode-tabs { display: flex; gap: 0.6rem; margin: 0.4rem 0 1rem; }
.mode-tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  align-items: flex-start;
  text-align: left;
  background: linear-gradient(var(--surface-elevated), var(--panel));
  border: 2px solid var(--border);
  border-radius: 8px;
  padding: 0.7rem 0.9rem;
  cursor: pointer;
  font-family: inherit;
  transition: border-color 0.15s, background 0.15s;
}
.mode-tab-title { font-family: var(--font-display); font-weight: 600; color: var(--text-secondary); font-size: var(--text-base); letter-spacing: 0.3px; }
.mode-tab-desc { color: var(--text-secondary); font-size: var(--text-xs); line-height: 1.35; }
.mode-tab:hover:not(.active) { border-color: color-mix(in srgb, var(--gold) 45%, var(--border)); }
.mode-tab.active { border-color: var(--gold); background: linear-gradient(#3a2c14, #241c12); }
.mode-tab.active .mode-tab-title { color: var(--gold); }
.mode-tab.active .mode-tab-desc { color: var(--text); }

.ai-match-panel {
  position: relative;
  background: linear-gradient(180deg, #2a2115, var(--panel));
  border: 2px solid color-mix(in srgb, var(--gold) 45%, var(--border));
  border-radius: 10px;
  padding: 1.2rem 1.4rem;
  box-shadow: inset 0 0 24px #00000060, 0 6px 18px #0007;
}
.ai-match-panel h2 { margin-top: 0; color: var(--gold); font-family: var(--font-display); font-weight: 600; font-size: var(--text-lg); letter-spacing: 0.5px; }
.ai-match-panel p { color: var(--text-secondary); font-size: var(--text-sm); margin-top: 0; }
.difficulty-picker { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.6rem; margin: 0.8rem 0 1rem; }
.difficulty-btn {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  background: #0f0c08;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.6rem 0.5rem;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  transition: border-color 0.15s, color 0.15s;
}
.difficulty-name { font-weight: bold; color: var(--text-secondary); font-size: var(--text-sm); }
.difficulty-desc { color: var(--text-secondary); font-size: var(--text-xs); line-height: 1.3; }
.difficulty-btn:hover:not(.active) { border-color: color-mix(in srgb, var(--gold) 50%, var(--border)); }
.difficulty-btn.active { border-color: var(--gold); background: linear-gradient(#3a2c14, #241c12); }
.difficulty-btn.active .difficulty-name { color: var(--gold); }
.difficulty-btn.active .difficulty-desc { color: var(--text); }

/* ---------- 2v2 Team Battle setup (lobby) ---------- */
.team-match-panel {
  position: relative;
  background: linear-gradient(180deg, #2a2115, var(--panel));
  border: 2px solid color-mix(in srgb, var(--gold) 45%, var(--border));
  border-radius: 10px;
  padding: 1.2rem 1.4rem;
  box-shadow: inset 0 0 24px #00000060, 0 6px 18px #0007;
}
.team-match-panel h2 { margin-top: 0; color: var(--gold); font-family: var(--font-display); font-weight: 600; font-size: var(--text-lg); letter-spacing: 0.5px; }
.team-match-panel p { color: var(--text-secondary); font-size: var(--text-sm); margin-top: 0; }
.team-seat-list { display: flex; flex-direction: column; gap: 0.7rem; margin: 0.8rem 0 1rem; }
.team-seat-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0.7rem;
  border-radius: 8px;
  background: #0f0c08;
  border: 1px solid var(--border);
}
.team-seat-label { font-family: var(--font-display); font-size: var(--text-sm); color: var(--text-secondary); min-width: 7rem; }
.team-seat-row[data-seat="seatB"] .team-seat-label { color: var(--gold-bright); }
.team-seat-toggle { display: flex; gap: 0.3rem; }
.team-seat-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--dim);
  font-family: inherit;
  font-size: var(--text-xs);
  padding: 0.35rem 0.6rem;
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.team-seat-btn:hover:not(.active) { border-color: color-mix(in srgb, var(--gold) 50%, var(--border)); color: var(--text); }
.team-seat-btn.active { border-color: var(--gold); background: linear-gradient(#3a2c14, #241c12); color: var(--gold); }
.team-seat-difficulty { display: flex; gap: 0.3rem; margin-left: auto; }
.team-seat-diff-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--dim);
  font-family: inherit;
  font-size: var(--text-xs);
  padding: 0.3rem 0.55rem;
  border-radius: 6px;
  cursor: pointer;
}
.team-seat-diff-btn.active { border-color: var(--gold); color: var(--gold); }
.team-code-hint { font-size: var(--text-xs); color: var(--dim); margin: 0.4rem 0 0; }

.ai-stats-line { color: var(--text-secondary); font-size: var(--text-sm); }

.board-mode-picker { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.6rem; margin: 0.8rem 0 1.2rem; }
.board-mode-btn {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  background: #0f0c08;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.6rem 0.5rem;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  transition: border-color 0.15s, color 0.15s;
}
.board-mode-name { font-weight: bold; color: var(--text-secondary); font-size: var(--text-sm); }
.board-mode-desc { color: var(--text-secondary); font-size: var(--text-xs); line-height: 1.3; }
.board-mode-btn:hover:not(.active) { border-color: color-mix(in srgb, var(--gold) 50%, var(--border)); }
.board-mode-btn.active { border-color: var(--gold); background: linear-gradient(#3a2c14, #241c12); }
.board-mode-btn.active .board-mode-name { color: var(--gold); }
.board-mode-btn.active .board-mode-desc { color: var(--text); }
.game-over-ai-note { color: var(--text-secondary); font-size: var(--text-sm); max-width: 26em; }
.game-over-reward-note { color: var(--gold-bright); font-family: var(--font-display); font-size: var(--text-sm); max-width: 26em; }

.lobby-hud-preview {
  text-align: center;
  margin: 0.4rem 0 1.4rem;
  padding: 0.8rem 1rem;
  border: 1px dashed color-mix(in srgb, var(--gold) 40%, var(--border));
  border-radius: 10px;
  background: #1c150d40;
}
.lobby-hud-preview p { margin: 0.5rem 0 0; color: var(--dim); font-size: 0.85rem; }
.btn-secondary {
  background: linear-gradient(#3a2c14, #241c12);
  border: 1px solid var(--gold);
  color: var(--gold);
  font-family: inherit;
  font-weight: bold;
  padding: 0.55rem 1.1rem;
  border-radius: 6px;
  font-size: 0.95rem;
  cursor: pointer;
}
.btn-secondary:hover:not(:disabled) { background: linear-gradient(var(--gold-bright), var(--gold)); color: #1a140d; }
.btn-secondary:active:not(:disabled) { transform: translateY(1px); }
.btn-secondary:disabled { opacity: 0.4; cursor: not-allowed; color: var(--disabled); border-color: var(--disabled); }

/* ---------- Account panel ---------- */
.account-panel {
  position: relative;
  max-width: 420px;
  margin: 1.2rem auto 0;
  background: linear-gradient(180deg, #2a2115, var(--panel));
  border: 2px solid color-mix(in srgb, var(--gold) 45%, var(--border));
  border-radius: 10px;
  padding: 0.9rem 1.1rem;
  box-shadow: inset 0 0 20px #00000060, 0 6px 16px #0007;
}
.account-view { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; }
.account-view.hidden { display: none; }
.account-form { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; width: 100%; }
.account-form input {
  flex: 1 1 140px;
  padding: 0.5rem 0.7rem;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: #0f0c08;
  color: var(--text);
  font-family: inherit;
  font-size: 0.95rem;
}
.account-form .btn { padding: 0.5rem 1.1rem; font-size: 0.9rem; }
.link-btn {
  background: none;
  border: none;
  color: var(--gold);
  text-decoration: underline;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.85rem;
  padding: 2px 0;
  transition: color 0.15s;
}
.link-btn:hover:not(:disabled) { color: var(--gold-bright); }
.link-btn:active:not(:disabled) { color: var(--gold); }
.link-btn:disabled { color: var(--disabled); cursor: not-allowed; text-decoration: none; }
.account-status { color: var(--hp); font-size: 0.85rem; min-height: 1.2em; text-align: center; }
#accountLoggedIn { flex-direction: row; }
#accountLoggedIn strong { color: var(--gold); }

/* ---------- Rankings panel ---------- */
.rankings-panel {
  position: relative;
  max-width: 560px;
  margin: 1.5rem auto 0;
  background: linear-gradient(180deg, #2a2115, var(--panel));
  border: 2px solid color-mix(in srgb, var(--gold) 45%, var(--border));
  border-radius: 10px;
  padding: 1rem 1.2rem;
  box-shadow: inset 0 0 20px #00000060, 0 6px 16px #0007;
}
.ranking-tabs { display: flex; gap: 0.5rem; margin-bottom: 0.8rem; }
.ranking-tab {
  flex: 1;
  background: linear-gradient(var(--surface-elevated), var(--panel));
  border: 1px solid var(--border);
  color: var(--text-secondary);
  font-family: inherit;
  font-weight: bold;
  font-size: var(--text-sm);
  padding: var(--space-2);
  border-radius: 6px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.ranking-tab:hover:not(.active) { border-color: color-mix(in srgb, var(--gold) 50%, var(--border)); color: var(--text); }
.ranking-tab:active:not(.active) { background: linear-gradient(#1c150d, #150f0a); }
.ranking-tab.active { border-color: var(--gold); color: var(--gold); background: linear-gradient(#3a2c14, #241c12); }
/* table-layout:fixed + explicit column widths make the Players/Decks
   rankings tables strictly honor their container's width (never wider) at
   any size, including the narrow fixed-dock sidebar — the 2nd column
   (Player/Faction) gets the most room and truncates gracefully instead of
   forcing the whole table to overflow. Scoped away from .deck-compare-table,
   which has 5 columns and its own wider min-width layout (below). */
.ranking-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.ranking-table:not(.deck-compare-table) { table-layout: fixed; }
.ranking-table:not(.deck-compare-table) th,
.ranking-table:not(.deck-compare-table) td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ranking-table:not(.deck-compare-table) th:first-child,
.ranking-table:not(.deck-compare-table) td:first-child { width: 10%; }
.ranking-table:not(.deck-compare-table) th:nth-child(2),
.ranking-table:not(.deck-compare-table) td:nth-child(2) { width: 46%; }
.ranking-table:not(.deck-compare-table) th:nth-child(3),
.ranking-table:not(.deck-compare-table) td:nth-child(3) { width: 22%; }
.ranking-table:not(.deck-compare-table) th:nth-child(4),
.ranking-table:not(.deck-compare-table) td:nth-child(4) { width: 22%; }
.ranking-table th { color: var(--gold); text-align: left; border-bottom: 1px solid var(--border); padding: 0.4rem 0.5rem; }
.ranking-table td { padding: 0.4rem 0.5rem; border-bottom: 1px solid #4a3a2250; color: var(--text); }
.ranking-table tbody tr:last-child td { border-bottom: none; }
.ranking-table .few-games { color: var(--text-secondary); font-size: var(--text-xs); }
.ranking-table .empty-row td, .ranking-table .loading-row td { text-align: center; color: var(--text-secondary); padding: 0.8rem; }

/* ---------- Game ---------- */
/* A fluid grid, not a fixed-resolution canvas scaled to fit — each row takes
   only the height it actually needs, so the board (the one flexible row)
   claims whatever is left over, at any viewport shape or aspect ratio. */
#game {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 8px var(--hand-col-width, 280px);
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  grid-template-areas:
    "topbar   topbar topbar"
    "board    handle hand"
    "board    handle hand"
    "controls handle hand";
  width: min(98vw, 1900px);
  height: min(96vh, 1080px);
  max-width: 100%;
  max-height: 100%;
  overflow: hidden;
  gap: clamp(4px, 0.6vh, 8px);
  padding: clamp(4px, 0.5vh, 10px) clamp(6px, 0.8vw, 14px);
  background:
    radial-gradient(ellipse 900px 700px at 50% 45%, #2e2313 0%, transparent 65%),
    radial-gradient(ellipse at 50% 50%, transparent 55%, #000 100%);
}
#topBar { grid-area: topbar; }
.board-wrap { grid-area: board; min-height: 0; }
.controls { grid-area: controls; }
#hand { grid-area: hand; min-height: 0; }

/* ---------- HUD layout manager ---------- */
/* Once initHudLayout() (client.js) runs, every one of the 11 movable panels
   gets an inline position:absolute + left/top/width/height, which wins over
   the grid-area rules above (grid-area on an absolutely-positioned grid
   child is only consulted for its static-position fallback, never reached
   once explicit inset values are set) — no !important needed anywhere here. */
.hud-layout-controls {
  position: fixed;
  top: 12px;
  right: 12px;
  z-index: 500;
  display: flex;
  gap: 8px;
}
.hud-edit-toggle, .hud-reset-btn {
  background: linear-gradient(#3a2c14, #241c12);
  border: 1px solid var(--gold);
  border-radius: 8px;
  color: var(--gold);
  font-family: inherit;
  font-weight: bold;
  padding: 0.4rem 0.7rem;
  font-size: 0.8rem;
  cursor: pointer;
  box-shadow: 0 4px 14px #000a;
  transition: background 0.15s, color 0.15s, transform 0.15s;
}
.hud-edit-toggle:hover:not(.active), .hud-reset-btn:hover { background: linear-gradient(#4a3a1c, #2e2416); }
.hud-edit-toggle:active, .hud-reset-btn:active { transform: translateY(1px); }
.hud-edit-toggle.active { background: linear-gradient(var(--gold-bright), var(--gold)); color: #1a140d; }
.hud-edit-toggle.active:hover { background: linear-gradient(var(--gold-bright), var(--gold-bright)); }

/* Card text size stepper — same toolbar, only shown alongside Reset Layout
   while HUD editing is active (toggled in toggleHudEditing(), client.js). */
.hud-font-control {
  display: flex;
  align-items: center;
  gap: 6px;
  background: linear-gradient(#3a2c14, #241c12);
  border: 1px solid var(--gold);
  border-radius: 8px;
  padding: 0.3rem 0.6rem;
  box-shadow: 0 4px 14px #000a;
}
.hud-font-label { color: var(--gold); font-size: 0.75rem; font-weight: bold; white-space: nowrap; }
.hud-font-btn {
  background: linear-gradient(#4a3a1c, #2e2416);
  border: 1px solid var(--gold);
  border-radius: 6px;
  color: var(--gold);
  font-weight: bold;
  width: 22px;
  height: 22px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s, opacity 0.15s;
}
.hud-font-btn:hover:not(:disabled) { background: linear-gradient(var(--gold-bright), var(--gold)); color: #1a140d; }
.hud-font-btn:disabled { opacity: 0.4; cursor: default; }
.hud-font-readout { color: var(--text); font-size: 0.75rem; font-weight: bold; min-width: 2.6em; text-align: center; }

/* Keybind rebind panel — same toolbar chrome as .hud-font-control, but a
   column of rows (one per action) instead of a single inline stepper. */
.keybind-control {
  display: flex;
  flex-direction: column;
  gap: 4px;
  background: linear-gradient(#3a2c14, #241c12);
  border: 1px solid var(--gold);
  border-radius: 8px;
  padding: 0.3rem 0.6rem;
  box-shadow: 0 4px 14px #000a;
}
.keybind-rows { display: flex; flex-direction: column; gap: 4px; }
.keybind-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.keybind-row-label { color: var(--gold); font-size: 0.7rem; font-weight: bold; white-space: nowrap; }
.keybind-key-btn {
  background: linear-gradient(#4a3a1c, #2e2416);
  border: 1px solid var(--gold);
  border-radius: 6px;
  color: var(--text);
  font-family: inherit;
  font-size: 0.7rem;
  font-weight: bold;
  padding: 0.15rem 0.5rem;
  min-width: 3.4em;
  text-align: center;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.keybind-key-btn:hover { background: linear-gradient(var(--gold-bright), var(--gold)); color: #1a140d; }
.keybind-key-btn.listening { background: linear-gradient(var(--gold-bright), var(--gold-bright)); color: #1a140d; animation: keybind-pulse 1s ease-in-out infinite; }
@keyframes keybind-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
.keybind-clear-btn {
  background: none;
  border: none;
  color: var(--gold);
  font-size: 0.75rem;
  cursor: pointer;
  opacity: 0.7;
  padding: 0 2px;
}
.keybind-clear-btn:hover { opacity: 1; }

/* Quick-command ping marker — a short-lived pulsing ring positioned via
   left/top (viewport coordinates from getBoundingClientRect, same space
   #targetArrow above already uses) rather than as a child of the slot, so
   it survives that slot being re-rendered/removed mid-animation. */
.ping-marker {
  position: fixed;
  width: 46px;
  height: 46px;
  margin-left: -23px;
  margin-top: -23px;
  border: 3px solid var(--gold-bright);
  border-radius: 50%;
  box-shadow: 0 0 16px 4px #f4c04580;
  pointer-events: none;
  z-index: 900;
  animation: ping-marker-pulse 2.5s ease-out forwards;
}
@keyframes ping-marker-pulse {
  0% { transform: scale(0.4); opacity: 1; }
  40% { transform: scale(1.15); opacity: 0.9; }
  100% { transform: scale(1.35); opacity: 0; }
}

/* Outlines the actual board element a quick-command targets (a unit's
   slot, the castle panel, or the whole enemy row for Clear Board) — an
   outline rather than a border so it never shifts layout, and generic
   enough to apply cleanly to elements of very different shapes/sizes. */
.ping-highlight {
  outline: 3px solid var(--gold-bright);
  outline-offset: 3px;
  animation: ping-highlight-fade 2.5s ease-out forwards;
}
@keyframes ping-highlight-fade {
  0%   { outline-color: var(--gold-bright); box-shadow: 0 0 24px 6px #f4c045a0; }
  60%  { outline-color: var(--gold-bright); box-shadow: 0 0 18px 4px #f4c04580; }
  100% { outline-color: transparent; box-shadow: 0 0 0 0 transparent; }
}

/* Quick-command banner — the readable "what did my ally just ping" text,
   deliberately separate from .top-notice (shared by many unrelated one-off
   messages elsewhere) so its timing/prominence can be tuned just for this:
   a slower fade than .top-notice's 3s and a brighter, more attention-
   grabbing treatment, since a teammate mid-combat needs a real beat to
   notice and read it. */
.command-banner {
  position: fixed;
  top: 64px;
  left: 50%;
  transform: translate(-50%, -12px) scale(0.92);
  z-index: 960;
  background: linear-gradient(180deg, #4a3a1e, #241c10);
  border: 2px solid var(--gold-bright);
  color: var(--gold-bright);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(0.95rem, 1.8vw, 1.25rem);
  letter-spacing: 0.3px;
  padding: 0.75rem 1.8rem;
  border-radius: 10px;
  box-shadow: 0 10px 30px #000d, 0 0 30px #f4c04570;
  pointer-events: none;
  opacity: 0;
  max-width: 92vw;
  text-align: center;
}
.command-banner.showing {
  animation: command-banner-cycle 4s ease-out;
}
@keyframes command-banner-cycle {
  0%   { opacity: 0; transform: translate(-50%, -12px) scale(0.9); }
  8%   { opacity: 1; transform: translate(-50%, 0) scale(1.04); }
  16%  { transform: translate(-50%, 0) scale(1); }
  78%  { opacity: 1; transform: translate(-50%, 0) scale(1); }
  100% { opacity: 0; transform: translate(-50%, 6px) scale(0.97); }
}

/* Right-click radial quick-command menu — a decorative hub circle at the
   click point with option "petals" arranged around it (see
   showQuickCommandMenu() in client.js), replacing the native context menu
   the game already suppresses everywhere. */
.qc-hub {
  position: fixed;
  width: 40px;
  height: 40px;
  margin-left: -20px;
  margin-top: -20px;
  border-radius: 50%;
  background: radial-gradient(circle, #4a3a1cd0, #1a140dd0 75%);
  border: 2px solid var(--gold);
  box-shadow: 0 0 18px #000c;
  z-index: 950;
  pointer-events: none;
  animation: qc-hub-in 0.12s ease-out;
}
.qc-option {
  position: fixed;
  transform: translate(-50%, -50%);
  background: linear-gradient(#4a3a1c, #2e2416);
  border: 1px solid var(--gold);
  border-radius: 8px;
  color: var(--gold);
  font-family: inherit;
  font-size: 0.68rem;
  font-weight: bold;
  padding: 0.35rem 0.55rem;
  cursor: pointer;
  white-space: nowrap;
  z-index: 951;
  box-shadow: 0 3px 12px #000a;
  animation: qc-option-in 0.15s ease-out;
}
.qc-option:hover { background: linear-gradient(var(--gold-bright), var(--gold)); color: #1a140d; }
@keyframes qc-hub-in { from { transform: scale(0.4); opacity: 0; } to { transform: scale(1); opacity: 1; } }
/* Must restate the base rule's translate(-50%,-50%) here too — an
   animation's own transform value replaces the cascaded one for its
   duration, so omitting it would make the button jump into its centered
   position only once the (very short) animation ends. */
@keyframes qc-option-in {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.6); }
  to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

#hudOverlayLayer { position: absolute; inset: 0; pointer-events: none; z-index: 60; }
.hud-panel-overlay { position: absolute; display: none; }
#game.hud-editing .hud-panel-overlay { display: block; outline: 2px dashed var(--gold); outline-offset: 2px; }

.hud-drag-handle {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-size: 0.65rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--gold-bright);
  background: linear-gradient(180deg, #3a2c14ee, #241c12cc);
  border: 1px solid var(--gold);
  border-radius: 4px 4px 0 0;
  cursor: move;
  pointer-events: auto;
  user-select: none;
}
.hud-resize-grip {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 16px;
  height: 16px;
  cursor: nwse-resize;
  pointer-events: auto;
  background: linear-gradient(135deg, transparent 50%, var(--gold) 50%);
}

#topBar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.3rem 1rem;
  background: linear-gradient(180deg, #2a2013c0, #1a140dc0);
  border: 1px solid #4a3a2270;
  border-radius: 10px;
  box-shadow: 0 3px 12px #0007;
  margin-bottom: 4px;
}
/* This panel is one of the HUD layout manager's independently drag-resizable
   panels (client.js HUD_PANELS, minW:150) — its rendered width is whatever
   the player dragged it to, continuously, not one of a few viewport
   breakpoints. A container query (keyed off the panel's OWN width, like
   .card's container-type:size for card text) is what actually tracks that,
   where @media never could. Flanks the turn indicator inside #topBar by
   default (an earlier attempt anchored these to the board's corners
   instead, but that visually collided with the side HP/deck/grave stat
   panels, which already occupy that same space) — still independently
   draggable via Edit HUD Layout if a player wants it elsewhere. */
.hp-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: 190px;
  border: 2px solid transparent;
  border-radius: 10px;
  padding: 3px 10px;
  /* No negative margin here any more: it used to offset this panel's own
     padding so a hover/active border could expand outward without shifting
     surrounding flex-flow layout — but now that oppInfo/youInfo are
     absolutely positioned to an exact box computed in initHudLayout(), that
     same negative margin just shifted the visible border 6px above the
     panel's actual top edge, clipped by #game's overflow:hidden (the
     "border cut off on top" bug). */
  background: linear-gradient(#1a130c40, #1a130c70);
  transition: border-color 0.2s, box-shadow 0.2s;
  container-type: inline-size;
  container-name: hp-panel;
}
/* Name and faction sit side by side in one row instead of the faction
   wrapping onto its own line below — that kept the full faction name
   readable but made the whole panel noticeably taller. The name gets
   priority for space (flex-grow) and shrinks last; the faction tag now
   also has a shrink/ellipsis fallback of its own (rather than a rigid
   flex-shrink:0) so an unusually long faction name can't force the
   player's own name down to just a couple of characters — a longer
   faction name gives way first, since the player's name is what the
   user actually needs legible. */
.hp-panel .name-row { display: flex; align-items: center; justify-content: center; gap: 5px; max-width: 100%; }
.hp-panel .name {
  flex: 1 1 auto;
  font-weight: bold;
  color: var(--gold-bright);
  font-size: clamp(0.75rem, 10.5cqw, 1.4rem);
  line-height: 1.2;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* A small "name tag" band, the way most TCGs set the player's name off
     from the board art behind it, rather than loose gold text floating on
     a busy background. */
  background: linear-gradient(#00000060, #00000040);
  border: 1px solid #ffffff20;
  border-radius: 6px;
  padding: 2px 10px;
}
.hp-panel .faction-tag {
  flex: 0 1 auto;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--font-display);
  font-size: clamp(0.6rem, 7cqw, 0.9rem);
  color: var(--gold);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 1px 8px;
  background: #00000040;
}
.hp-panel .panel-row { display: flex; align-items: center; justify-content: center; gap: clamp(4px, 3cqw, 12px); flex-wrap: wrap; max-width: 100%; }
/* Covers both the top-bar (.hp-panel) and side-panel (.side-panel) shapes of
   .castle-panel — the actual castle-attack hit target is #oppPanel now (the
   crest banner), but this stays generic so it's correct wherever the class
   ends up toggled. */
.castle-panel.legal-target { border-color: var(--success); cursor: pointer; animation: pulse-green 1.3s ease-in-out infinite; }
/* Whose turn it is, at a glance — a slow gold pulse on that player's own
   HP panel, reusing the same "your available action" gold language
   already used for playable hand cards and legal-move slots. */
.hp-panel.active-turn { border-color: var(--gold); animation: pulse-gold 2.2s ease-in-out infinite; }

/* ---------- Castle (base HP) ---------- */
.castle-wrap { position: relative; width: clamp(34px, 3.6vw, 52px); height: clamp(34px, 3.6vw, 52px); flex-shrink: 0; }
/* #oppInfo/#youInfo (the top bar) carry the same .castle-wrap/.castle-img/
   .castle-hp-value markup as the side panels, shown only on mobile as a
   secondary display when the side-panel crest can get squeezed off-screen.
   The actual attack-castle click/drag target is #oppPanel (see
   onCastleClick()'s binding and castleCenter()) — this top-bar copy is
   purely decorative, not interactive. Hidden by default since the side
   panel already covers this on desktop. */
.hp-panel .castle-wrap, .hp-panel .castle-hp-value { display: none; }
.castle-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: saturate(1) brightness(1) drop-shadow(0 2px 4px #0009);
  transition: filter 0.4s ease;
}
.castle-panel.castle-damaged .castle-img { filter: saturate(0.7) brightness(0.85) sepia(0.15) drop-shadow(0 2px 4px #0009); }
.castle-panel.castle-critical .castle-img { filter: saturate(0.4) brightness(0.6) sepia(0.3) hue-rotate(-10deg) drop-shadow(0 2px 4px #0009); }
.castle-panel.castle-destroyed .castle-img { filter: grayscale(1) brightness(0.35); opacity: 0.5; }
.castle-hp-block { display: flex; flex-direction: column; align-items: center; gap: 2px; width: 100%; min-width: 0; }
.castle-owner-label {
  font-size: 0.62rem;
  font-weight: bold;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dim);
  text-shadow: 0 1px 2px #000;
  text-align: center;
}
/* Side panel only: the HP number lives directly on top of the crest
   (.castle-wrap already has position:relative). Deliberately much larger
   than the crest's default icon size (see the .side-panel .castle-wrap
   override below) so it reads as the reference image's "banner with your
   HP on it" rather than a small stat readout. */
.castle-hp-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display), Georgia, serif;
  font-weight: bold;
  font-size: clamp(1.3rem, 3.2vw, 2.2rem);
  color: #fff8e7;
  /* A dark outline (not a gold one) so the number stays legible over any
     faction's crest color — a gold stroke disappears against Dwarf's own
     gold-toned shield, for instance. The fill color below is what actually
     carries the HP-state signal. */
  -webkit-text-stroke: 2.5px #1a1410;
  paint-order: stroke fill;
  text-shadow: 0 2px 4px #000c;
  pointer-events: none;
}
/* Same escalation the removed bar's gradient used to carry, moved onto the
   number's fill color so low HP still reads clearly without a bar — the
   dark outline above stays constant for contrast regardless of state. */
.castle-panel.castle-damaged .castle-hp-overlay { color: #ffcf80; }
.castle-panel.castle-critical .castle-hp-overlay { color: #ff8f7a; }
.castle-panel.castle-destroyed .castle-hp-overlay { color: #9a8f80; }
/* Was the bar-fill's own hit-flash; now flashes the number instead since
   the bar is gone — same class/keyframe, new target. */
.castle-hp-overlay.bar-hit { animation: bar-hit-flash 0.4s ease-out; }
@keyframes bar-hit-flash {
  0% { filter: brightness(1); }
  30% { filter: brightness(1.8); }
  100% { filter: brightness(1); }
}
/* Side panel's crest is a real focal element (per the reference image), not
   the small icon-sized default .castle-wrap the top-bar panel still uses. */
.side-panel .castle-wrap { width: clamp(56px, 5.5vw, 92px); height: clamp(56px, 5.5vw, 92px); }
.castle-hp-value {
  font-weight: bold;
  color: #ffdede;
  font-size: 0.7rem;
  text-shadow: 0 1px 3px #000;
  white-space: nowrap;
}
.castle-wrap.castle-hit { animation: castle-hit-flash 0.6s ease-out; }
@keyframes castle-hit-flash {
  0% { filter: drop-shadow(0 0 0 #ff4d4d00); }
  15% { filter: drop-shadow(0 0 14px #ff4d4dcc); }
  100% { filter: drop-shadow(0 0 0 #ff4d4d00); }
}
.castle-panel.castle-destroyed .castle-wrap { animation: castle-crumble 0.9s ease-out forwards; }
@keyframes castle-crumble {
  0% { transform: scale(1) rotate(0deg); opacity: 1; }
  60% { transform: scale(0.92) rotate(-3deg); opacity: 0.7; }
  100% { transform: scale(0.85) rotate(-5deg); opacity: 0.45; }
}

.mana-crystals { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(2px, 1.5cqw, 6px); max-width: 100%; }
.crystal {
  width: clamp(8px, 7cqw, 22px);
  height: clamp(8px, 7cqw, 22px);
  border-radius: 2px;
  transform: rotate(45deg);
  display: inline-block;
  flex-shrink: 0;
}
.crystal.filled {
  background: radial-gradient(circle at 30% 30%, #a9d8ff, var(--mana) 70%);
  box-shadow: 0 0 7px #3c7fd0e0;
}
.crystal.spent { background: #1a2433; border: 1px solid #2c3b52; }
.mana { color: #bfe0ff; font-weight: bold; font-size: clamp(0.7rem, 8.5cqw, 1.5rem); text-shadow: 0 1px 3px #000; white-space: nowrap; }
.turn-indicator {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  font-size: 1.2rem;
  color: var(--gold);
  text-align: center;
  flex: 1;
  text-shadow: 0 2px 6px #000;
  letter-spacing: 1px;
}
.turn-timer {
  font-size: 0.85rem;
  font-weight: bold;
  letter-spacing: 0.5px;
  color: var(--dim);
}
.turn-timer.timer-critical {
  color: var(--hp);
  animation: pulse-timer-critical 1s ease-in-out infinite;
}
@keyframes pulse-timer-critical {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}
.sfx-toggle-btn {
  background: none;
  border: none;
  color: var(--dim);
  font-size: 0.85rem;
  line-height: 1;
  padding: 2px;
  cursor: pointer;
  opacity: 0.75;
  transition: opacity 0.15s, color 0.15s;
}
.sfx-toggle-btn:hover { opacity: 1; color: var(--gold); }

.board-wrap {
  display: flex;
  flex-direction: row;
  flex: 1;
  min-height: 0;
  align-items: stretch;
  gap: 8px;
}
/* Was flex:1 — sharing board-wrap's width equally with .board-area meant
   these two thin stat-pill columns were claiming a full quarter of the
   board's width each. They only need enough room for .panel-stat itself. */
.side-panel {
  flex: 0 0 auto;
  width: clamp(70px, 6vw, 110px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 10px 4px;
  border-radius: 14px;
  background: radial-gradient(ellipse at center, #1c150d60 0%, transparent 70%);
  border: 1px solid #4a3a2250;
}
.panel-stat {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 70px;
  padding: 8px 14px;
  border-radius: 10px;
  background: linear-gradient(#2a2115, var(--panel));
  border: 1px solid var(--border);
  box-shadow: inset 0 0 10px #0007, 0 2px 6px #0007;
  font-size: 1.1rem;
  font-weight: bold;
  color: var(--text);
}
.panel-icon { font-size: 1.2rem; }
.board-area {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  max-width: min(92vw, 1650px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 6px;
  min-height: 0;
  padding: 10px 8px;
  border-radius: 16px;
  background: radial-gradient(ellipse at center, #1c150d 0%, #100c07 100%);
  box-shadow: inset 0 0 60px #000d, inset 0 0 0 1px #6b5330, 0 10px 34px #000a;
  border: 2px solid #7a5c2e80;
}
/* Small gold corner ornaments (diagonal corners only — a single element only
   has two pseudo-elements to spend), echoing the diamond mana-crystal motif
   already used in #topBar, so the board frame reads as a "war table" rather
   than a plain box. */
.board-area::before, .board-area::after {
  content: '';
  position: absolute;
  width: 10px;
  height: 10px;
  background: var(--gold);
  opacity: 0.7;
  transform: rotate(45deg);
  box-shadow: 0 0 6px #d9b25c90;
  pointer-events: none;
}
.board-area::before { top: -5px; left: -5px; }
.board-area::after { bottom: -5px; right: -5px; }
.lane-row {
  position: relative;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 4px;
  border-radius: 10px;
  width: 100%;
  flex: 1;
  min-height: 0;
}
#oppRearguard, #youRearguard {
  background: linear-gradient(180deg, var(--rearguard), transparent);
}
#oppVanguard, #youVanguard {
  background: linear-gradient(180deg, var(--vanguard), transparent);
}
/* Row labels as an absolutely-positioned overlay rather than a layout row —
   Vanguard/Rearguard clarity without spending any of the board's hard-won
   vertical space on it. Also names the baseline row passive (rules.js
   effectivePower()/grantVanguardBonusIfNeeded()) here, since it applies to
   every unit regardless of card text and has no other on-screen home. */
#oppRearguard::before, #youRearguard::before { content: 'Rearguard +1 DMG'; }
#oppVanguard::before, #youVanguard::before { content: 'Vanguard +1 Max HP'; }
#oppRearguard::before, #youRearguard::before, #oppVanguard::before, #youVanguard::before {
  position: absolute;
  top: 3px;
  left: 8px;
  font-family: var(--font-display);
  font-size: clamp(0.5rem, 0.85vw, 0.62rem);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--dim);
  opacity: 0.55;
  pointer-events: none;
  z-index: 1;
}
/* Single Row board mode: no Rearguard, no row labels (neither passive
   applies on a plain board). Each .lane-row already carries flex: 1;
   min-height: 0, so hiding Rearguard lets Vanguard fill the freed space
   automatically — no other sizing rule needs to change. */
#game.single-row-mode #oppRearguard, #game.single-row-mode #youRearguard { display: none; }
#game.single-row-mode #oppVanguard::before, #game.single-row-mode #youVanguard::before { content: none; }
/* The actual front line where the two sides meet — given a slow pulse
   (reusing the existing pulse-gold keyframes at low intensity) so it reads
   as the battle line rather than a static rule between two boxes. */
.lane-divider {
  height: 3px;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
  margin: 2px 0;
  opacity: 0.75;
  box-shadow: 0 0 10px var(--gold);
  animation: pulse-divider 3s ease-in-out infinite;
}
@keyframes pulse-divider {
  0%, 100% { box-shadow: 0 0 8px var(--gold); opacity: 0.65; }
  50% { box-shadow: 0 0 16px var(--gold); opacity: 0.9; }
}

.slot {
  position: relative;
  height: 100%;
  max-height: 260px;
  min-height: 60px;
  aspect-ratio: 2 / 3;
  width: auto;
  flex-shrink: 1;
  border: 1px solid #4a3a22;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(ellipse at center, rgba(255,255,255,0.02), rgba(0,0,0,0.3));
  box-shadow: inset 0 0 10px #00000090;
}
.slot::before, .slot::after {
  content: '';
  position: absolute;
  width: 12px;
  height: 12px;
  border: 2px solid var(--border);
  opacity: 0.7;
  pointer-events: none;
}
.slot::before { top: -1px; left: -1px; border-right: none; border-bottom: none; }
.slot::after { bottom: -1px; right: -1px; border-left: none; border-top: none; }
.slot.legal-target { border-color: var(--success); cursor: pointer; animation: pulse-green 1.3s ease-in-out infinite; }
.slot.legal-target::before, .slot.legal-target::after { border-color: var(--success); }
.slot.can-play { border-color: var(--gold); cursor: pointer; animation: pulse-gold 1.3s ease-in-out infinite; }
.slot.can-play::before, .slot.can-play::after { border-color: var(--gold); }
.slot.spell-target { border-color: var(--magic); cursor: pointer; animation: pulse-magic 1.3s ease-in-out infinite; }
.slot.spell-target::before, .slot.spell-target::after { border-color: var(--magic); }

@keyframes pulse-magic {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--magic) 55%, transparent); }
  50% { box-shadow: 0 0 10px 3px color-mix(in srgb, var(--magic) 55%, transparent); }
}

@keyframes pulse-gold {
  0%, 100% { box-shadow: inset 0 0 10px #00000090, 0 0 6px 0 #d9b25c80; }
  50% { box-shadow: inset 0 0 10px #00000090, 0 0 16px 4px #d9b25ccc; }
}
@keyframes pulse-green {
  0%, 100% { box-shadow: inset 0 0 10px #00000090, 0 0 6px 0 #6fd66a80; }
  50% { box-shadow: inset 0 0 10px #00000090, 0 0 16px 4px #6fd66acc; }
}

.card {
  position: relative;
  width: 100%;
  aspect-ratio: 2 / 3;
  height: auto;
  border-radius: 8px;
  overflow: hidden;
  /* Lets text inside (name plate, ability lines, stat badges) size itself
     via cqw/cqh — relative to THIS card's own rendered box — instead of
     vw (relative to the viewport). vw ties font size to browser width, so
     the exact same card renders at an unrelated font size in a small board
     slot vs. a full hand card vs. the hover preview at the same viewport
     width; cqw/cqh scale correctly with however big this particular card
     actually is. Safe to apply here specifically because .card's own box
     is already fully self-determined (width 100% of its parent + a fixed
     aspect-ratio) and never depends on its children's size. */
  container-type: size;
  container-name: card;
  border: 2px solid #1a140c;
  box-shadow: 0 2px 6px #000a;
  cursor: pointer;
  flex-shrink: 0;
  background-color: #100c08;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.card.face-down { background: linear-gradient(135deg, #2a2015, #1a1209); border-color: #5c4419; }
.card.hand-card:not(.unaffordable):hover {
  transform: translateY(-14px) scale(1.08);
  box-shadow: 0 12px 20px #000c, 0 0 14px var(--gold);
  z-index: 5;
}
/* Board cards get the same "hover lifts it" language as hand cards, just
   without the vertical lift (which would look odd pinned inside a fixed
   slot) — a slight scale and glow is enough to feel consistently tactile
   everywhere a card appears. */
.slot .card:hover {
  transform: scale(1.06);
  box-shadow: 0 6px 16px #000c, 0 0 12px var(--gold);
  z-index: 3;
}
@keyframes card-pop {
  0% { transform: scale(0.7); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}
.card.card-enter { animation: card-pop 0.25s ease-out; }
.card.selected { outline: 3px solid var(--gold); }
.card.sick { filter: grayscale(0.6) brightness(0.7); }
.card.attacked { filter: brightness(0.55); }
.card.unaffordable { filter: grayscale(0.8) brightness(0.5); cursor: not-allowed; }
.card.has-taunt { box-shadow: 0 0 0 2px var(--success), 0 2px 6px #000a; }
.card.spell-card { box-shadow: 0 0 0 2px var(--magic), 0 2px 6px #000a; }
.card.spell-card.hand-card:not(.unaffordable):hover {
  box-shadow: 0 12px 20px #000c, 0 0 14px var(--magic);
}
/* A quiet, static outline (not box-shadow, so it layers cleanly with
   .has-taunt/.selected instead of overriding them) marking every unit of
   yours that could currently act — left unanimated since several of these
   can be visible at once and a field of pulsing cards would be noisy. */
.card.attack-ready { outline: 2px solid color-mix(in srgb, var(--gold) 65%, transparent); outline-offset: 1px; }
/* touch-action:none so a finger-drag on a board card is delivered to us as
   pointermove events instead of being claimed by the browser as a page-pan
   gesture — without this, the attack drag-arrow never receives movement on
   touch devices (see onCardPointerDown in client.js). */
.slot .card { width: 100%; height: 100%; touch-action: none; }

.card-badge {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  /* cqw (this .card's own rendered width), not vw (the viewport) — a badge
     on a small board-slot card now scales down with that card instead of
     staying viewport-sized regardless of how small the card actually is. */
  min-width: clamp(10px, 12cqw, 28px);
  height: clamp(10px, 12cqw, 28px);
  padding: 0 3px;
  border-radius: clamp(5px, 6cqw, 14px);
  font-family: Georgia, serif;
  font-weight: bold;
  font-size: clamp(7px, 7.5cqw, 16px);
  color: #fff;
  text-shadow: 0 1px 2px #000;
  border: 1.5px solid var(--gold);
  box-shadow: 0 1px 4px #000a;
  pointer-events: none;
  z-index: 2;
}
.cost-badge { top: 3px; right: 3px; background: radial-gradient(circle at 30% 30%, #6fa8e0, #2a5d94); }
.dmg-badge { bottom: 3px; left: 3px; background: radial-gradient(circle at 30% 30%, #d8d8d8, #6b6b6b); }
.hp-badge { bottom: 3px; right: 3px; background: radial-gradient(circle at 30% 30%, #ff8b83, var(--hp)); }
.countdown-badge { top: 3px; left: 50%; transform: translateX(-50%); background: radial-gradient(circle at 30% 30%, #d8c9a0, #7a6a3f); }

/* ---------- Live template card renderer (card-render migration) ---------- */
/* Card-render migration Phase 3: composites newcardtemplate.png as the card's
   own background (never a separate DOM layer, so it needs no z-index of its
   own) plus three absolutely-positioned regions — art, name, rules text —
   whose percentages were measured directly against the 1024x1536 template
   (see cardsource/extract_art.ps1's header comment for the matching art-crop
   rectangle). These are a first pass tuned by eye against a live render;
   nudge them here if a future template revision shifts the frame's regions.
   Font sizing uses cqh (this .card's own rendered HEIGHT, the binding
   constraint for these short horizontal bands) — this is Phase 5's
   container-query mechanism, pulled forward into Phase 3 after a real hand
   card (~80x120px in testing) exposed vw-based sizing as a genuine
   readability bug, not just a future nice-to-have: at that size a
   viewport-relative font stayed near its ~13-16px ceiling regardless of the
   card being far too small to hold it, clipping most of the rules text
   inside its own (safely non-spilling, `overflow:hidden`) box. */
.card-templated { background-image: url('assets/cards/newcardtemplate.png'); background-size: 100% 100%; background-repeat: no-repeat; }
.card-art-frame { position: absolute; left: 20%; top: 20%; width: 58%; height: 38%; overflow: hidden; pointer-events: none; }
/* This frame is ~1.02:1 (near-square) and the source art is a square
   327x326 canvas, so object-fit:cover has almost no crop slack to give
   object-position — the crop those two properties can do here is tiny.
   The transform:scale below is the one lever that actually crops, and it
   zooms symmetrically around the frame's own center regardless of where a
   given character's head/weapon/wings/crest happen to sit. A previous pass
   used scale(1.5) (33% of the image trimmed, 16.7% off each edge) tuned
   against 4 sampled arts; a full-set visual audit this session (every
   faction, cheap/mid/expensive units) found several other cards' helmet
   spikes, wingtips and crests landing flush against that cropped edge —
   a blind uniform zoom can't be safe for 374 differently-posed arts at
   once. Keep this scale conservative: it should trim only the outermost
   dead vignette ring baked into the old template's compositing, not eat
   into character extremities. Re-check any increase against a full-set
   render grid (see the card-art audit technique used to find this bug),
   not just a handful of samples — that's exactly what missed it last time. */
.card-art { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 20%; transform: scale(1.1); pointer-events: none; }
.card-name-plate {
  position: absolute;
  left: 18%; top: 8%; width: 64%; height: 4%;
  display: flex; align-items: center; justify-content: center; text-align: center;
  overflow: hidden;
  color: #3a2510;
  font-family: var(--font-display);
  font-weight: bold;
  line-height: 1.05;
  font-size: calc(clamp(4px, 2.8cqh, 20px) * var(--card-text-scale, 1));
  letter-spacing: 0.02em;
  pointer-events: none;
}
.card-rules-panel {
  position: absolute;
  left: 13%; top: 69%; width: 74%; height: 20%;
  display: flex; flex-direction: column; justify-content: center; align-items: center;
  gap: 3%;
  overflow: hidden;
  color: #241a0e;
  text-align: center;
  line-height: 1.15;
  font-size: calc(clamp(4px, 2.6cqh, 15px) * var(--card-text-scale, 1));
}
/* A 2-ability card needs to fit one more line in the same panel height —
   cheap, fully covers the current corpus (max 2 ability lines today) with
   zero per-render measurement cost. See Phase 5 for the general case
   (arbitrary future line counts / unusually long text). */
.card-rules-panel:has(.ability-line:nth-child(2)) { font-size: calc(clamp(4px, 1.9cqh, 12px) * var(--card-text-scale, 1)); }
.ability-line { font-weight: 600; }
.ability-footer { font-style: italic; opacity: 0.7; font-size: 0.85em; }

/* ---------- Combat feedback effects (floating numbers / flashes) ---------- */
.fx-float {
  position: absolute;
  left: 50%;
  top: 30%;
  transform: translateX(-50%);
  font-family: Georgia, serif;
  font-weight: 800;
  font-size: 15px;
  pointer-events: none;
  text-shadow: 0 1px 3px #000, 0 0 6px #000;
  animation: fx-float-up 1s ease-out forwards;
  z-index: 50;
  white-space: nowrap;
}
.fx-float.fx-damage { color: #ff5c5c; }
.fx-float.fx-heal { color: #5cff8f; }
.fx-float.fx-buff { color: #ffd24d; }
/* Offset above the base 30% so this doesn't land exactly on top of the
   -N damage number spawned at the same anchor for the same hit — both
   need to stay independently readable, not stacked in the same pixels. */
.fx-float.fx-spell-impact { top: 15%; color: var(--magic); font-size: 20px; }
@keyframes fx-float-up {
  0% { opacity: 0; transform: translate(-50%, 0); }
  15% { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -34px); }
}
.fx-hit { animation: fx-hit-flash 0.4s ease-out; }
@keyframes fx-hit-flash {
  0% { filter: none; }
  30% { filter: brightness(1.7) saturate(1.4); }
  100% { filter: none; }
}
.fx-death { animation: fx-death-fade 0.5s ease-out; }
@keyframes fx-death-fade {
  0% { filter: brightness(2) saturate(0); opacity: 1; }
  100% { filter: none; opacity: 1; }
}

/* A damage spell's board-wide flourish — layered on top of the normal
   per-slot unit-damage flashes, not replacing them, so it reads as "a
   spell went off across this row" rather than just several coincidental
   hits. Uses --magic (the game's existing "this is a spell" color, see
   .spell-target's pulse-magic) to read as a distinct visual language from
   the red/green/gold damage/heal/buff palette above. */
.spell-aoe-flash { animation: spell-aoe-pulse 0.6s ease-out; }
@keyframes spell-aoe-pulse {
  0% { box-shadow: inset 0 0 0 0 color-mix(in srgb, var(--magic) 70%, transparent); }
  40% { box-shadow: inset 0 0 40px 10px color-mix(in srgb, var(--magic) 70%, transparent); }
  100% { box-shadow: inset 0 0 0 0 color-mix(in srgb, var(--magic) 70%, transparent); }
}
.spell-aoe-burst {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 40px;
  height: 40px;
  margin: -20px 0 0 -20px;
  border-radius: 50%;
  background: radial-gradient(circle, color-mix(in srgb, var(--magic) 80%, transparent) 0%, transparent 70%);
  pointer-events: none;
  /* One above .card-reveal-overlay's z-index:100 — the reveal popup fires
     on every card play (including this same spell), sits at this exact
     center point, and would otherwise fully hide the ring underneath it.
     The ring scales past the reveal card's own footprint, so it still
     reads as emanating from behind/around the reveal art. */
  z-index: 101;
  animation: spell-aoe-burst 0.6s ease-out forwards;
}
@keyframes spell-aoe-burst {
  0% { transform: scale(0.3); opacity: 0.9; }
  100% { transform: scale(8); opacity: 0; }
}

/* A plain combat attack landing — deliberately sharper and much faster
   than the magic-purple spell-aoe-burst above, so a sword/arrow hit reads
   as a distinct, more physical kind of impact than a spell resolving. */
.combat-impact-burst {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff6d8 0%, #ffb04d 45%, transparent 75%);
  pointer-events: none;
  z-index: 50;
  animation: combat-impact-burst 0.3s ease-out forwards;
}
@keyframes combat-impact-burst {
  0% { transform: scale(0.4); opacity: 1; }
  100% { transform: scale(3.5); opacity: 0; }
}
.combat-impact-shake { animation: combat-impact-shake 0.25s ease-out; }
@keyframes combat-impact-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-4px); }
  40% { transform: translateX(3px); }
  60% { transform: translateX(-2px); }
  80% { transform: translateX(1px); }
}

.controls { display: flex; flex-wrap: wrap; gap: 1rem; justify-content: center; padding: 0.35rem; }
.controls .btn { padding: 0.4rem 1.4rem; font-size: 0.95rem; }

.hand {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
  align-content: start;
  gap: 8px;
  padding: 10px;
  border-radius: 14px;
  background: radial-gradient(ellipse at center, #1c150d 0%, #100c07 100%);
  border: 2px solid #7a5c2e80;
  box-shadow: inset 0 0 30px #000c;
  overflow-y: auto;
}
/* Reserve room for the chat panel only while it's actually open — otherwise
   its expanded window (up to ~340px tall) would sit on top of whichever
   hand card happens to be at the bottom of this dock. */
body:has(#chatPanel:not(.collapsed)) .hand {
  padding-bottom: 340px;
}

.log {
  max-height: 240px;
  overflow-y: auto;
  font-size: 0.85rem;
  color: var(--dim);
  background: #0000004d;
  padding: 8px 10px;
}
.log div { padding: 1px 0; }

.overlay {
  position: absolute; inset: 0;
  background: #000c;
  display: flex; align-items: center; justify-content: center;
}
.overlay-box {
  background: var(--panel);
  border: 2px solid var(--gold);
  border-radius: 10px;
  padding: 2rem 3rem;
  text-align: center;
}
.overlay-box h2 { color: var(--gold); }
.overlay-box .btn { margin-top: 1rem; padding: 0.6rem 1.4rem; }

#coinFlipOverlay { position: fixed; z-index: 600; }
/* The HUD panels (client.js applyPanelGeometry) carry an explicit z-index
   so the battlefield can never bury another dragged-over panel — without a
   z-index of its own here, this end-of-match dialog (z-index:auto) painted
   BELOW all of them and was invisible even once its `hidden` class was
   removed. */
#gameOverOverlay { z-index: 600; }
.coin-flip-box { min-width: 260px; }
.coin-flip-coin {
  font-size: 4rem;
  margin: 1rem 0;
  animation: coin-flip-spin 1.4s ease-out;
}
@keyframes coin-flip-spin {
  0% { transform: rotateY(0deg) scale(1); }
  70% { transform: rotateY(1440deg) scale(1.15); }
  100% { transform: rotateY(1800deg) scale(1); }
}
.coin-flip-result { color: var(--text); font-size: 1.1rem; margin: 0; }

#discoverOverlay { position: fixed; z-index: 600; }
.discover-box { min-width: 320px; }
.discover-waiting { color: var(--text); font-size: 1.05rem; margin: 0.5rem 0 0; }
.discover-options { display: flex; gap: 16px; justify-content: center; margin-top: 1rem; }
.discover-option { width: 160px; }

/* ---------- Deck preview modal ---------- */
#deckPreviewOverlay, #deckCompareOverlay, #adminOverlay, #starterDeckOverlay, #buyCoinsOverlay, #tutorialWelcomeOverlay { position: fixed; z-index: 600; }

/* ---------- Tutorial selection menu (Learn to Play) ---------- */
.tutorial-select-modal { width: min(640px, 92vw); max-height: 88vh; overflow-y: auto; text-align: left; }
.tutorial-select-modal h2 { text-align: center; }
.tutorial-select-modal > p { color: var(--text-secondary); text-align: center; margin: 0 0 1.2rem; }
.tutorial-select-list { display: flex; flex-direction: column; gap: 0.7rem; }
.tutorial-select-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.9rem 1.1rem;
  background: linear-gradient(#2a2115, var(--panel));
  border: 2px solid var(--border);
  border-radius: 10px;
}
.tutorial-select-card-info h3 { margin: 0 0 0.25rem; color: var(--gold-bright); font-family: var(--font-display); font-size: 1rem; }
.tutorial-select-card-info p { margin: 0; color: var(--text-secondary); font-size: var(--text-sm); }
.tutorial-select-card-status {
  margin-left: 0.5rem;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--success, #4caf50) 25%, transparent);
  color: var(--success, #4caf50);
  font-size: 0.7rem;
  font-family: var(--font-body, inherit);
  vertical-align: middle;
}
.tutorial-select-card .btn { margin-top: 0; padding: 0.5rem 1.1rem; white-space: nowrap; flex-shrink: 0; }
#tutorialWelcomeSkipBtn { display: block; margin: 1rem auto 0; }

/* On-screen replacement for the browser's native confirm() dialog — a
   single shared overlay reused by every yes/no prompt in the app (deck
   purchase, surrender, skip tutorial, unsaved-changes, admin actions),
   driven by the customConfirm() Promise-based helper in client.js. Sits
   above the 600-tier overlay stack (so it can confirm an action taken
   from inside another overlay, e.g. deleting an account from the admin
   panel) and above the tutorial panel/spotlight (650/640), so a surrender
   confirmation mid-lesson is never obscured. */
#customConfirmOverlay { position: fixed; z-index: 660; }
.custom-confirm-box { max-width: 420px; }
#customConfirmMessage { color: var(--text); font-size: 1rem; line-height: 1.4; margin: 0; white-space: pre-line; }
.custom-confirm-actions { display: flex; gap: 0.8rem; justify-content: center; margin-top: 1.2rem; flex-wrap: wrap; }
.custom-confirm-actions .btn, .custom-confirm-actions .btn-secondary { margin-top: 0; padding: 0.6rem 1.4rem; }

/* ---------- Tutorial (in-match) ---------- */
/* z-index placement: above every board/HUD element and above the 600-tier
   .overlay modals, but below #cardPreview (700) and both drag-arrow layers
   (895/900) — so inspecting a card or dragging an attack arrow mid-lesson
   is never obscured by the dialogue panel or the dim spotlight. */
/* Docked top-left rather than bottom-center — the bottom of the screen is
   the game's own controls bar (Go to Combat/Attack All/End Turn/Surrender),
   so a bottom-docked panel both covers those buttons (making the very
   actions a lesson asks for unclickable) and hides the spotlight's dim
   "hole" behind itself whenever the highlighted element is also down there.
   Top-left is empty in the existing HUD layout. */
.tutorial-panel {
  position: fixed;
  left: 12px;
  top: 12px;
  z-index: 650;
  max-width: 300px;
  width: calc(100vw - 24px);
  background: var(--panel);
  border: 2px solid var(--gold);
  border-radius: 10px;
  padding: 0.7rem 1rem;
  font-size: 0.88rem;
  box-shadow: 0 8px 24px #000c;
}
.tutorial-panel-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.4rem; gap: 0.6rem; }
.tutorial-step-badge { color: var(--gold); font-family: var(--font-display); font-size: 0.75rem; letter-spacing: 0.05em; text-transform: uppercase; }
#tutorialDialogueText { margin: 0; color: var(--text); font-size: 0.95rem; line-height: 1.4; }
#tutorialNextBtn { margin-top: 0.7rem; padding: 0.45rem 1.2rem; }

.tutorial-spotlight {
  position: fixed;
  pointer-events: none;
  z-index: 640;
  border: 2px solid var(--gold);
  border-radius: 8px;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
  transition: top 0.25s ease, left 0.25s ease, width 0.25s ease, height 0.25s ease;
  animation: tutorial-spotlight-pulse 2.2s ease-in-out infinite;
}
/* A separate outline-opacity pulse rather than reusing pulse-gold's own
   box-shadow keyframe directly — the spotlight's box-shadow is already
   doing double duty as the fixed dim "moat", so animating it too would
   fight this element's own static shadow instead of layering with it. */
@keyframes tutorial-spotlight-pulse {
  0%, 100% { border-color: color-mix(in srgb, var(--gold) 70%, transparent); }
  50% { border-color: var(--gold-bright); }
}
@media (prefers-reduced-motion: reduce) {
  .tutorial-spotlight { animation: none; transition: none; }
}
@media (max-width: 760px) {
  .tutorial-panel { top: 8px; left: 8px; padding: 0.7rem 1rem; font-size: 0.9rem; max-width: calc(100vw - 16px); }
}

.tutorial-completion-list { list-style: none; padding: 0; margin: 1rem 0; text-align: left; display: inline-block; }
.tutorial-completion-list li { color: var(--success); font-size: 0.9rem; padding: 0.15rem 0; }
.tutorial-completion-list li::before { content: '✓ '; }

/* Distinct from the plain .btn:disabled dimmed look — this reads as
   "do the lesson's action first", not "unavailable right now". */
#endTurnBtn.tutorial-locked:disabled {
  opacity: 0.55;
  border-color: var(--gold);
  box-shadow: 0 0 8px color-mix(in srgb, var(--gold) 45%, transparent);
}
#endTurnBtn.tutorial-locked:disabled::after { content: ' 🔒'; }


.lobby-tutorial-entry { text-align: center; margin: 0.4rem 0 1.4rem; }
.lobby-tutorial-entry p { margin: 0.5rem 0 0; color: var(--dim); font-size: 0.85rem; }

/* ---------- Starter deck selection ---------- */
.starter-deck-modal { width: min(760px, 92vw); max-height: 88vh; overflow-y: auto; text-align: left; }
.starter-deck-modal h2 { text-align: center; }
.starter-deck-desc { color: var(--text-secondary); text-align: center; margin: 0 0 1rem; }
.starter-deck-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 0.6rem;
}
.starter-deck-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  padding: 0.7rem 0.5rem;
  background: linear-gradient(#2a2115, var(--panel));
  border: 2px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  font-family: var(--font-display);
  font-size: 0.82rem;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.starter-deck-card:hover { border-color: var(--gold); }
.starter-deck-card.selected {
  border-color: var(--gold);
  background: linear-gradient(#3a2c14, #241c12);
  box-shadow: 0 0 12px color-mix(in srgb, var(--gold) 45%, transparent);
  color: var(--gold-bright);
}
.starter-deck-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin-top: 1.2rem;
}
.starter-deck-count { color: var(--dim); font-family: var(--font-display); font-size: 0.9rem; }
.starter-deck-status { min-height: 1.4em; color: var(--warning); font-size: var(--text-sm); text-align: center; margin: 0.6rem 0 0; }

/* ---------- Buy Coins panel (stub — see economy.js COIN_PACKS) ---------- */
.buy-coins-modal { width: min(560px, 92vw); max-height: 88vh; overflow-y: auto; text-align: left; }
.buy-coins-disclaimer { color: var(--text-secondary); font-size: var(--text-sm); margin: 0 0 1rem; }
.buy-coins-body { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 0.7rem; }
.coin-pack-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  padding: 1rem 0.6rem;
  background: linear-gradient(#2a2115, var(--panel));
  border: 2px solid var(--border);
  border-radius: 10px;
}
.coin-pack-amount { font-family: var(--font-display); font-size: 1.15rem; color: var(--gold-bright); }
.coin-pack-price { color: var(--text-secondary); font-size: 0.85rem; }
.buy-coins-guest-note { color: var(--dim); text-align: center; padding: 1rem 0; }
.deck-preview-modal {
  width: min(1000px, 92vw);
  max-height: 88vh;
  padding: 1.4rem 1.6rem;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  /* Without this, a flex column's children default to min-height:auto (their
     natural content size), so the max-height above has nothing to actually
     constrain — the body never shrinks enough to trigger its own
     overflow-y:auto, and everything past it (including the footer button)
     renders outside the fixed overlay's bounds, unreachable by scrolling. */
  overflow: hidden;
}
.deck-preview-header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-shrink: 0; }
.deck-preview-heading { display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap; }
.deck-preview-heading h2 { margin: 0; color: var(--gold); font-family: var(--font-display); font-size: 1.6rem; }
.deck-preview-heading .deck-badge { font-size: 0.8rem; padding: 3px 9px; }
.deck-preview-close {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 6px;
  width: 32px;
  height: 32px;
  font-size: 1rem;
  cursor: pointer;
  flex-shrink: 0;
}
.deck-preview-close:hover { border-color: var(--gold); color: var(--gold); }
.deck-preview-body {
  display: grid;
  grid-template-columns: 320px 1fr;
  gap: 1.5rem;
  overflow-y: auto;
  padding-right: 4px;
  flex: 1 1 auto;
  min-height: 0;
}
.deck-preview-description { color: var(--text); font-size: 1.05rem; line-height: 1.55; }
.deck-preview-block { margin-bottom: 1.1rem; }
.deck-preview-block h3 {
  margin: 0 0 0.35rem;
  color: var(--gold);
  font-family: var(--font-display);
  font-size: 0.95rem;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.deck-preview-block p { margin: 0.15rem 0; color: var(--text); font-size: 0.98rem; line-height: 1.5; }
.deck-preview-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.deck-preview-list { margin: 0; padding-left: 1.2rem; font-size: 0.95rem; color: var(--text); line-height: 1.55; }
.deck-preview-list-pro li::marker { color: var(--success); }
.deck-preview-list-con li::marker { color: var(--hp); }
.deck-mechanic-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.deck-mechanic-chip {
  font-size: 0.82rem;
  font-family: var(--font-display);
  letter-spacing: 0.3px;
  border: 1px solid var(--magic);
  color: var(--magic);
  background: color-mix(in srgb, var(--magic) 12%, transparent);
  border-radius: 10px;
  padding: 3px 9px;
}
.deck-stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
  gap: 0.6rem;
  text-align: center;
  margin-bottom: 0.6rem;
}
.deck-stats-grid > div { background: #00000030; border-radius: 6px; padding: 0.4rem 0.2rem; }
.deck-stats-grid span { display: block; font-family: var(--font-display); font-size: 1.3rem; color: var(--gold-bright); }
.deck-stats-grid label { display: block; font-size: 0.7rem; color: var(--dim); text-transform: uppercase; letter-spacing: 0.3px; }
.mana-curve-chart { display: flex; align-items: flex-end; gap: 4px; height: 80px; }
.mana-curve-col { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; gap: 2px; }
.mana-curve-bar { width: 100%; background: linear-gradient(#e7c877, #b8912f); border-radius: 2px 2px 0 0; min-height: 2px; }
.mana-curve-label { font-size: 0.7rem; color: var(--dim); }

.deck-preview-cardlist { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.deck-filter-bar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 0.6rem; }
.deck-filter-bar input, .deck-filter-bar select {
  background: #0000004d;
  border: 1px solid var(--border);
  color: var(--text);
  font-family: inherit;
  font-size: 0.9rem;
  padding: 6px 9px;
  border-radius: 5px;
}
.deck-filter-bar input { flex: 1; min-width: 120px; }
.deck-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 12px;
  max-height: 58vh;
  overflow-y: auto;
  padding-right: 4px;
  align-content: start;
  /* Without this, Grid's default stretch fights .card's aspect-ratio-driven
     auto height (.card has no in-flow children to establish a height on its
     own — name plate/art/rules panel are all position:absolute) — some
     engines resolve that circular dependency by collapsing the row to a
     sliver instead of respecting the aspect-ratio. */
  align-items: start;
}
/* These tiles are much bigger than a live hand/board card, so they can
   afford a richer cqh ratio than the shared .card-name-plate/.card-rules-panel
   rules (tuned against ~80px-wide real hand cards, where a bigger ratio
   would overflow) — scoped here rather than raised globally. */
.deck-card-grid .card-name-plate { font-size: calc(clamp(6px, 3.4cqh, 24px) * var(--card-text-scale, 1)); }
.deck-card-grid .card-rules-panel { font-size: calc(clamp(6px, 3.1cqh, 18px) * var(--card-text-scale, 1)); }
.deck-card-grid .card-rules-panel:has(.ability-line:nth-child(2)) { font-size: calc(clamp(6px, 2.3cqh, 15px) * var(--card-text-scale, 1)); }
.deck-card-grid-empty { color: var(--dim); text-align: center; grid-column: 1 / -1; padding: 1.5rem 0; }
/* Classic intrinsic-ratio box: padding-top:150% gives an exact 2:3 height
   derived purely from width (0 content height + 150% padding), with none of
   the aspect-ratio + Grid + container-type:size ambiguity that collapsed
   .card directly as a grid item in at least one real browser — see
   buildDeckPreviewCardEl() in client.js. */
.deck-card-tile { position: relative; width: 100%; padding-top: 150%; cursor: pointer; }
.deck-card-tile .card { position: absolute; inset: 0; width: 100%; height: 100%; }
.deck-card-qty {
  position: absolute;
  bottom: 4px;
  right: 4px;
  background: #14100cd0;
  border: 1px solid var(--gold);
  color: var(--gold-bright);
  font-size: 0.75rem;
  font-family: var(--font-display);
  border-radius: 8px;
  padding: 2px 7px;
  pointer-events: none;
}
.deck-preview-footer { display: flex; justify-content: flex-end; padding-top: 0.4rem; border-top: 1px solid var(--border); flex-shrink: 0; }

/* ---------- Deck Builder ---------- */
/* Shares .deck-preview-modal/.deck-preview-body/.deck-preview-cardlist/
   .deck-filter-bar/.deck-card-grid/.deck-card-tile wholesale with the
   read-only "View Deck" preview above — only the left info pane (deck-in-
   progress instead of kingdom lore) and each card tile's stepper (instead
   of a static ×N badge) are specific to the builder. */
.deck-builder-info { display: flex; flex-direction: column; min-height: 0; gap: 0.7rem; }
.deck-builder-name-input {
  background: #0000004d;
  border: 1px solid var(--border);
  color: var(--text);
  font-family: inherit;
  font-size: 1rem;
  padding: 7px 10px;
  border-radius: 6px;
}
.deck-builder-summary-bar { display: flex; align-items: center; justify-content: space-between; gap: 0.8rem; }
.deck-builder-count-readout { font-family: var(--font-display); font-size: 1.1rem; color: var(--dim); }
.deck-builder-count-readout.at-target { color: var(--success); }
.deck-builder-count-readout.over-target { color: var(--hp); }
.deck-builder-errors { display: flex; flex-direction: column; gap: 3px; }
.deck-builder-error { margin: 0; color: var(--hp); font-size: 0.8rem; }
.deck-builder-list {
  flex: 1 1 auto;
  min-height: 80px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-right: 4px;
}
.deck-builder-list-empty { color: var(--dim); font-size: 0.85rem; text-align: center; padding: 1rem 0; }
.deck-builder-list-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 4px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: #00000030;
  font-size: 0.85rem;
  cursor: pointer;
}
.deck-builder-list-name { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Same dec/count/inc button look as the card grid's own .deck-builder-stepper
   (client.js:buildBuilderCardEl), just laid out inline instead of floated
   absolutely over a card tile — a list row has no card face to float over. */
.deck-builder-list-stepper { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }

/* Floats over the bottom of the card face, same convention as
   .deck-card-qty above — the tile's aspect ratio never needs to change to
   make room for it. */
.deck-builder-stepper {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  pointer-events: none;
}
.deck-builder-stepper-dec, .deck-builder-stepper-inc {
  pointer-events: auto;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--gold);
  background: #14100cd0;
  color: var(--gold-bright);
  font-family: var(--font-display);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
}
.deck-builder-stepper-dec:disabled, .deck-builder-stepper-inc:disabled { border-color: var(--disabled); color: var(--disabled); cursor: not-allowed; }
.deck-builder-stepper-dec:hover:not(:disabled), .deck-builder-stepper-inc:hover:not(:disabled) { border-color: var(--gold-bright); }
.deck-builder-stepper-count {
  pointer-events: auto;
  min-width: 18px;
  text-align: center;
  background: #14100cd0;
  border: 1px solid var(--gold);
  color: var(--gold-bright);
  font-family: var(--font-display);
  font-size: 0.8rem;
  border-radius: 8px;
  padding: 2px 5px;
}

.deck-compare-modal { width: min(900px, 92vw); max-height: 85vh; text-align: left; }
.deck-compare-modal h2 { color: var(--gold); text-align: center; }
.deck-compare-table-wrap { max-height: 65vh; overflow: auto; margin: 1rem 0; }
.deck-compare-table-wrap .ranking-table { min-width: 640px; }

/* ---------- Music player (lobby) ---------- */
/* Shown in every layout, independent of [data-layout] — a normal card in the
   document flow (auto-places at the end of the column, same fallback every
   other "extra" lobby element already uses) until the viewport is wide
   enough to dock it to the left edge without any risk of overlapping the
   still-centered 900px column. Visual treatment matches .sidebar-widget
   (same card look used in the Sidebar + HUD layout) but isn't gated by it. */
.music-player-lobby {
  background: linear-gradient(180deg, #2a2115, var(--panel));
  border: 2px solid color-mix(in srgb, var(--gold) 45%, var(--border));
  border-radius: 10px;
  padding: 0.9rem 1rem;
  margin: 1.2rem 0;
  box-shadow: inset 0 0 20px #00000060, 0 6px 16px #0007;
}
.music-player-lobby h4 {
  margin: 0 0 0.6rem;
  color: var(--gold);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.music-now-playing {
  color: var(--text);
  font-size: var(--text-sm);
  margin-bottom: 0.7rem;
  min-height: 1.3em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.music-controls { display: flex; align-items: center; justify-content: center; gap: 0.7rem; margin-bottom: 0.8rem; }
.music-ctrl-btn {
  background: linear-gradient(#3a2c14, #241c12);
  border: 1px solid var(--gold);
  color: var(--gold);
  border-radius: 50%;
  width: 2.2rem;
  height: 2.2rem;
  font-size: 1rem;
  cursor: pointer;
  transition: background 0.15s, transform 0.1s;
}
.music-ctrl-btn:hover { background: linear-gradient(var(--gold-bright), var(--gold)); color: #1a140d; }
.music-ctrl-btn:active { transform: translateY(1px); }
.music-ctrl-btn-main { width: 2.8rem; height: 2.8rem; font-size: 1.2rem; }
.music-volume-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.8rem; font-size: 0.9rem; }
.music-volume-row input[type="range"] { flex: 1; accent-color: var(--gold); }
.music-track-list {
  max-height: 220px;
  overflow-y: auto;
  border-top: 1px solid var(--border);
  padding-top: 0.5rem;
  /* Themed scrollbar (Firefox via scrollbar-color, everything else via the
     ::-webkit-scrollbar pseudo-elements below) instead of the browser's
     default gray, matching the site's gold-on-dark-brown palette. */
  scrollbar-width: thin;
  scrollbar-color: var(--gold) #1c150d;
}
.music-track-list::-webkit-scrollbar { width: 8px; }
.music-track-list::-webkit-scrollbar-track { background: #1c150d; border-radius: 8px; }
.music-track-list::-webkit-scrollbar-thumb { background: linear-gradient(var(--gold-bright), var(--gold)); border-radius: 8px; border: 1px solid #1c150d; }
.music-track-list::-webkit-scrollbar-thumb:hover { background: var(--gold-bright); }
.music-track-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.5rem;
  border-radius: 6px;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.music-track-row:hover { background: #ffffff10; color: var(--text); }
.music-track-row.active { color: var(--gold); font-weight: bold; background: color-mix(in srgb, var(--gold) 12%, transparent); }

/* Only once there's guaranteed room on BOTH sides of the still-centered
   900px column (rankings already docks right at this same threshold) does
   this pop out to the left edge — each dock only ever needs its own side's
   margin, so the two never compete for space. */
@media (min-width: 1350px) {
  .music-player-lobby {
    position: fixed;
    margin: 0;
    top: 6rem;
    left: 1.5rem;
    width: var(--dock-width);
  }
}

/* ---------- Music player (in-match mini) ---------- */
/* Hidden outside a live match — body.in-game is toggled by showGame()/
   exitHudPreview() in client.js, the same class the HUD editor already
   relies on. */
#musicPlayerGame { display: none; position: fixed; z-index: 450; top: 110px; right: 16px; }
body.in-game #musicPlayerGame { display: block; }
.music-mini-toggle {
  display: none;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: linear-gradient(#3a2c14, #241c12);
  border: 1px solid var(--gold);
  color: var(--gold);
  font-size: 1.1rem;
  cursor: pointer;
  box-shadow: 0 4px 14px #000a;
}
#musicPlayerGame.collapsed .music-mini-toggle { display: block; }
#musicPlayerGame.collapsed .music-mini-panel { display: none; }
.music-mini-panel {
  background: linear-gradient(#3a2c14, #241c12);
  border: 1px solid var(--gold);
  border-radius: 8px;
  padding: 0.4rem 0.5rem;
  box-shadow: 0 4px 14px #000a;
  width: 240px;
}
.music-mini-bar {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  cursor: grab;
  touch-action: none;
}
.music-mini-bar:active { cursor: grabbing; }
.music-mini-title { flex: 1; color: var(--text); font-size: 0.8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.music-mini-controls {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-top: 0.35rem;
  padding-top: 0.35rem;
  border-top: 1px solid #ffffff20;
}
.music-mini-vol-icon { font-size: 0.75rem; flex: 0 0 auto; }
.music-mini-volume { flex: 1; min-width: 0; accent-color: var(--gold); height: 4px; }
.music-mini-btn {
  flex: 0 0 auto;
  background: none;
  border: none;
  color: var(--gold);
  font-size: 0.95rem;
  cursor: pointer;
  padding: 2px 4px;
}
.music-mini-btn:hover { color: var(--gold-bright); }

/* ---------- Admin panel ---------- */
.admin-modal { width: min(560px, 92vw); max-height: 85vh; text-align: left; display: flex; flex-direction: column; gap: 1.1rem; overflow-y: auto; }
.admin-modal-header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; }
.admin-modal-header h2 { margin: 0; color: var(--gold); font-family: var(--font-display); font-size: 1.4rem; }
.admin-section h3 { margin: 0 0 0.4rem; color: var(--gold); font-family: var(--font-display); font-size: var(--text-base); }
.admin-section p { margin: 0 0 0.6rem; color: var(--text-secondary); font-size: var(--text-xs); }
.layout-picker { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.6rem; }
.layout-btn {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  background: #0f0c08;
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.6rem 0.7rem;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
  transition: border-color 0.15s, color 0.15s;
}
.layout-name { font-weight: bold; color: var(--text-secondary); font-size: var(--text-sm); }
.layout-desc { color: var(--text-secondary); font-size: var(--text-xs); line-height: 1.3; }
.layout-btn:hover:not(.active) { border-color: color-mix(in srgb, var(--gold) 50%, var(--border)); }
.layout-btn.active { border-color: var(--gold); background: linear-gradient(#3a2c14, #241c12); }
.layout-btn.active .layout-name { color: var(--gold); }
.layout-btn.active .layout-desc { color: var(--text); }
.admin-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.admin-account-list { max-height: 220px; overflow-y: auto; border: 1px solid var(--border); border-radius: 6px; }
.admin-account-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.5rem 0.7rem;
  border-bottom: 1px solid #4a3a2250;
  font-size: var(--text-sm);
}
.admin-account-row:last-child { border-bottom: none; }
.admin-delete-btn {
  background: transparent;
  border: 1px solid var(--error);
  color: var(--error);
  font-family: inherit;
  font-size: var(--text-xs);
  padding: 0.3rem 0.6rem;
  border-radius: 5px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.admin-delete-btn:hover { background: var(--error); color: var(--bg); }
.admin-loading { padding: 0.7rem; color: var(--text-secondary); font-size: var(--text-sm); }
.admin-status { min-height: 1.4em; color: var(--gold); font-size: var(--text-sm); text-align: center; }

/* ---------- Card play reveal ---------- */
/* Fires once per server-signaled play (client.js computeEffects/spawnEffect,
   keyed on game.lastPlayedCard.seq) -- a single one-shot animation handles
   the whole fade-in/hold/fade-out lifecycle, so pulseClass() restarting it
   on a second rapid play is all the JS needs to do; no timers to clean up. */
.card-reveal-overlay {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  pointer-events: none;
  opacity: 0;
}
.card-reveal-overlay.reveal-active {
  animation: card-reveal-cycle 2s ease-out;
}
@keyframes card-reveal-cycle {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.7); }
  12%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  78%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(0.96); }
}
.card-reveal-img {
  width: clamp(140px, 16vw, 220px);
  height: auto;
  border-radius: 14px;
  border: 3px solid var(--gold);
  box-shadow: 0 14px 40px #000f, 0 0 32px #d9b25c90;
  /* background-color, NOT the `background` shorthand — the shorthand resets
     every unlisted sub-property to its initial value, including
     background-image, which would silently wipe out .card-templated's
     newcardtemplate.png (this exact bug shipped once already: the frame
     never rendered on the reveal popup, leaving art+text with no visible
     boundary between them). */
  background-color: #100c08;
  display: block;
}
.card-reveal-label {
  font-family: var(--font-display);
  font-weight: bold;
  font-size: clamp(0.75rem, 1.2vw, 0.95rem);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--gold);
  text-shadow: 0 2px 6px #000d, 0 0 10px #000a;
  background: #100c08cc;
  padding: 2px 10px;
  border-radius: 6px;
  border: 1px solid var(--gold);
}
.card-reveal-overlay.reveal-opponent .card-reveal-img { border-color: var(--hp); box-shadow: 0 14px 40px #000f, 0 0 32px #b5322f90; }
.card-reveal-overlay.reveal-opponent .card-reveal-label { color: var(--hp); border-color: var(--hp); }

/* ---------- Top notice (e.g. "pick a faction first") ---------- */
/* Fixed to the viewport, not #lobbyStatus's spot far down the page — the
   one thing players kept missing entirely. Auto-dismissed by JS (see
   showTopNotice() in client.js); the class toggle here only drives the
   animation, not visibility timing. */
.top-notice {
  position: fixed;
  top: 14px;
  left: 50%;
  transform: translate(-50%, -140%);
  z-index: 550;
  background: linear-gradient(180deg, #4a3a1e, #2a2014);
  border: 2px solid var(--warning);
  color: var(--warning);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(0.8rem, 1.4vw, 1rem);
  letter-spacing: 0.3px;
  padding: 0.6rem 1.4rem;
  border-radius: 8px;
  box-shadow: 0 10px 28px #000c, 0 0 24px #e0a94d50;
  pointer-events: none;
  opacity: 0;
  max-width: 90vw;
  text-align: center;
}
.top-notice.showing {
  animation: top-notice-cycle 3s ease-out;
}
@keyframes top-notice-cycle {
  0%   { opacity: 0; transform: translate(-50%, -140%); }
  10%  { opacity: 1; transform: translate(-50%, 0); }
  85%  { opacity: 1; transform: translate(-50%, 0); }
  100% { opacity: 0; transform: translate(-50%, -20%); }
}

/* ---------- Your Turn / Opponent's Turn banner ---------- */
/* Deliberately viewport-fixed (not board-relative like
   .card-reveal-overlay) so it reads as a full-screen moment, same idea as
   Magic Arena/Hearthstone's own turn-transition banners. Sits above the
   card-reveal overlay (z-index 100) since both can plausibly fire in the
   same instant at a turn boundary, but below the 600-tier modals (coin
   flip, game over, discover) — those still take priority if one is up. */
.turn-banner {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2rem, 6vw, 4rem);
  letter-spacing: 3px;
  text-transform: uppercase;
  text-shadow: 0 4px 16px #000f, 0 0 40px #0009;
}
.turn-banner.showing {
  animation: turn-banner-cycle 2.5s ease-out;
}
.turn-banner.turn-you { color: var(--gold-bright); }
.turn-banner.turn-opponent { color: var(--hp); }
@keyframes turn-banner-cycle {
  0%   { opacity: 0; transform: scale(0.85); }
  15%  { opacity: 1; transform: scale(1); }
  75%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.05); }
}

/* ---------- Card hover preview ---------- */
#cardPreview {
  position: fixed;
  left: 16px;
  top: 50%;
  /* Above every 600-tier overlay (.overlay-based modals: coin flip, discover,
     deck preview/compare) — otherwise hovering a card inside one of those
     modals renders this preview UNDER the modal's own semi-transparent
     backdrop, which is what actually caused the "grayed out" look (the art
     itself was fine; it was being seen through a dark veil). pointer-events
     is none below, so this can never intercept clicks meant for what's
     underneath. */
  z-index: 700;
  pointer-events: none;
  /* aspect-ratio + width:auto (not an independent width/height/max-height
     triplet) so whichever dimension actually binds — the ideal size below,
     or the viewport-height cap on a short screen — the other always derives
     from it and the box stays exactly 2:3. The old fixed width alongside a
     height-only max-height let a short viewport squish the box (349x301,
     ratio 0.86, measured on a 1366x650 window) while every percentage-
     positioned child inside (art frame, name plate, rules panel) kept
     assuming a proper 2:3 box — that mismatch was the actual "card art
     looks broken" bug, not the art itself. */
  aspect-ratio: 2 / 3;
  width: auto;
  /* Centered via top:50%/translateY(-50%), so capping height alone keeps it
     off the log panel at the bottom-left on short viewports — a shorter box
     just pulls both edges in symmetrically toward center. */
  height: min(540px, calc(100vh - 340px));
  border-radius: 16px;
  overflow: hidden;
  border: 3px solid var(--gold);
  box-shadow: 0 14px 40px #000f, 0 0 32px #d9b25c70;
  /* background-color, not the `background` shorthand — see the identical
     comment on .card-reveal-img for why the shorthand silently breaks the
     template background image. */
  background-color: #100c08;
  opacity: 0;
  transform: translateY(-50%) scale(0.97);
  transition: opacity 0.08s, transform 0.08s;
}
#cardPreview.visible { opacity: 1; transform: translateY(-50%) scale(1); }
/* Pops up on whichever side the hovered card isn't on (set in
   showPreview() in client.js), so it never covers the card — or the board
   behind it — you're actually looking at. */
#cardPreview.preview-right { left: auto; right: 16px; }

/* ---------- Drag-to-target attack arrow ---------- */
/* Fixed, full-viewport, no viewBox — its coordinate space is plain CSS
   pixels matching getBoundingClientRect() directly, so the arrow can be
   drawn straight from real card geometry with no transform step. */
#targetArrow {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: 900;
  pointer-events: none;
  overflow: visible;
}
#targetArrow.hidden { display: none; }
#arrowPath {
  fill: none;
  stroke: var(--gold);
  stroke-width: 5;
  stroke-linecap: round;
  filter: drop-shadow(0 0 6px #d9b25c90);
  /* Energy flowing toward the target rather than a static line — a dashed
     stroke sliding along the path, cheap (no filter/layout work per frame). */
  stroke-dasharray: 10 8;
  animation: arrow-energy-flow 0.5s linear infinite;
}
@keyframes arrow-energy-flow {
  to { stroke-dashoffset: -36; }
}
#arrowHead { fill: var(--gold); filter: drop-shadow(0 0 6px #d9b25c90); }
#targetArrow.arrow-illegal #arrowPath { stroke: var(--hp); filter: drop-shadow(0 0 6px #b5322f90); }
#targetArrow.arrow-illegal #arrowHead { fill: var(--hp); filter: drop-shadow(0 0 6px #b5322f90); }

/* ---------- Attack Planning ---------- */
/* A separate, persistent sibling to #targetArrow (never modified) — one
   dashed arrow per queued attack, distinct from the live drag arrow's solid
   animated stroke so the two are never confused at a glance. Sits just
   below #targetArrow's z-index so the live drag/selection arrow always
   draws on top of any already-queued plan arrows. */
#attackPlanArrows {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  z-index: 895;
  pointer-events: none;
  overflow: visible;
}
#attackPlanArrows.hidden { display: none; }
.plan-arrow-path {
  fill: none;
  stroke: var(--gold);
  stroke-width: 4;
  stroke-linecap: round;
  stroke-dasharray: 7 7;
  opacity: 0.8;
}
.plan-arrow-path.plan-arrow-lethal { stroke: var(--hp); }
.plan-arrow-path.plan-arrow-rejected { stroke: var(--disabled); opacity: 0.5; }
/* 2v2 only: a teammate's own live plan, relayed for visibility — a
   distinct blue (matching .card.owner-ally's language) so it reads as
   "not mine, but visible" at a glance, never confused with your own
   gold/red arrows. */
.plan-arrow-path.plan-arrow-ally { stroke: #3a7bd5; stroke-dasharray: 4 4; }
.plan-arrow-head { fill: var(--gold); opacity: 0.8; }
.plan-arrow-head.plan-arrow-lethal { fill: var(--hp); }
.plan-arrow-head.plan-arrow-rejected { fill: var(--disabled); opacity: 0.5; }
.plan-arrow-head.plan-arrow-ally { fill: #3a7bd5; }
.plan-order-badge-bg { fill: var(--gold); stroke: #1a1410; stroke-width: 1.5; }
.plan-order-badge-bg.plan-arrow-lethal { fill: var(--hp); }
.plan-order-badge-bg.plan-arrow-rejected { fill: var(--disabled); }
.plan-order-badge-bg.plan-arrow-ally { fill: #3a7bd5; }
.plan-order-badge { font: bold 11px Georgia, serif; fill: #1a1410; }

/* Death-preview overlay — reuses the same "this is a prediction, not the
   real state" visual language as .plan-hp-badge below: obviously distinct
   from the game's other death/damage feedback (unit-death, combat-impact),
   which only ever plays once a real attack actually lands. */
.card.plan-death-preview { filter: grayscale(0.85) brightness(0.55); }
.card.plan-death-preview::after {
  content: '☠';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2em;
  color: #fff;
  text-shadow: 0 0 6px #000;
  pointer-events: none;
}

/* Castle equivalent of .plan-death-preview — no card element to grey out,
   so the castle panel itself gets a pulsing red outline instead. */
.plan-castle-lethal { outline: 2px solid var(--hp); outline-offset: 2px; animation: plan-castle-lethal-pulse 1s ease-in-out infinite; }
@keyframes plan-castle-lethal-pulse {
  0%, 100% { outline-color: color-mix(in srgb, var(--hp) 60%, transparent); }
  50% { outline-color: var(--hp); }
}

/* Ghost predicted-HP badge — deliberately NOT styled like the real
   .hp-badge (dashed border, muted gold, offset above the card) so it can
   never be mistaken for the unit's actual, already-changed HP. */
.plan-hp-badge {
  position: absolute;
  top: -10px;
  right: -6px;
  z-index: 3;
  background: rgba(20, 16, 12, 0.85);
  border: 1px dashed var(--gold);
  color: var(--gold-bright);
  font: bold 11px Georgia, serif;
  border-radius: 8px;
  padding: 1px 5px;
  pointer-events: none;
}

#attackPlanSummary {
  align-self: center;
  color: var(--gold-bright);
  font-size: 0.9rem;
}

/* ---------- Log panel ---------- */
/* Same collapsible-floating-panel pattern as #chatPanel below, mirrored to
   the left edge instead of the right. */
#logPanel { position: fixed; left: 16px; bottom: 16px; z-index: 400; width: 300px; max-width: calc(100vw - 32px); }
.log-toggle { display: flex; align-items: center; gap: 6px; width: 100%; background: linear-gradient(#3a2c14, #241c12); border: 1px solid var(--gold); border-radius: 8px; color: var(--gold); font-family: inherit; font-weight: bold; padding: 0.5rem 0.8rem; cursor: pointer; box-shadow: 0 4px 14px #000a; transition: background 0.15s; }
.log-toggle:hover { background: linear-gradient(#4a3a1c, #2e2416); }
.log-toggle:active { transform: translateY(1px); }
.log-body { margin-top: 6px; background: var(--panel); border: 1px solid var(--gold); border-radius: 8px; box-shadow: 0 8px 24px #000c; overflow: hidden; }
#logPanel.collapsed .log-body { display: none; }

/* ---------- Chat panel ---------- */
#chatPanel { position: fixed; right: 16px; bottom: 16px; z-index: 400; width: 300px; max-width: calc(100vw - 32px); }
.chat-toggle { display: flex; align-items: center; gap: 6px; width: 100%; background: linear-gradient(#3a2c14, #241c12); border: 1px solid var(--gold); border-radius: 8px; color: var(--gold); font-family: inherit; font-weight: bold; padding: 0.5rem 0.8rem; cursor: pointer; box-shadow: 0 4px 14px #000a; transition: background 0.15s; }
.chat-toggle:hover { background: linear-gradient(#4a3a1c, #2e2416); }
.chat-toggle:active { transform: translateY(1px); }
.chat-badge { margin-left: auto; background: var(--hp); color: #fff; border-radius: 999px; font-size: 0.7rem; padding: 1px 7px; font-weight: bold; }
.chat-body { margin-top: 6px; background: var(--panel); border: 1px solid var(--gold); border-radius: 8px; box-shadow: 0 8px 24px #000c; overflow: hidden; }
#chatPanel.collapsed .chat-body { display: none; }
.chat-messages { max-height: 240px; overflow-y: auto; padding: 8px 10px; font-size: 0.85rem; color: var(--text); }
.chat-line { margin-bottom: 6px; line-height: 1.35; }
.chat-line .who { font-weight: bold; color: var(--gold); margin-right: 4px; }
.chat-line.mine .who { color: var(--mana); }
.chat-form { display: flex; border-top: 1px solid var(--border); }
.chat-form input { flex: 1; background: #0000004d; border: none; color: var(--text); font-family: inherit; font-size: 0.85rem; padding: 8px 10px; }
.chat-form button { background: linear-gradient(#e7c877, #b8912f); border: none; padding: 0 14px; font-weight: bold; cursor: pointer; color: #241c12; transition: filter 0.15s; }
.chat-form button:hover { filter: brightness(1.08); }
.chat-form button:active { filter: brightness(0.94); }

@media (max-width: 980px) {
  /* Tablet range (roughly 721-980px, e.g. a portrait iPad): 5 columns here
     left each faction card too narrow for its badge row/tagline/View Deck
     button — the same crowding problem the 720px breakpoint below already
     fixes for phones, just one range up. 4 columns gives real width back. */
  .faction-picker { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
  .faction-card { padding: 0.6rem 0.4rem; gap: 0.4rem; }
  .faction-card .thumb { width: 60px; height: 90px; }
  .faction-card .label { font-size: 0.75rem; }
  .deck-preview-body { grid-template-columns: 260px 1fr; }
}

@media (max-width: 720px) {
  #lobby { padding: 0.5rem 0.8rem 2rem; }
  /* Jump straight to "start a match" after logging in, instead of making
     a phone user scroll past the full 10-faction picker first — Create/
     Join Match (and the Ranked/AI tabs that gate which one shows) used
     to sit below the deck grid, custom-decks panel, compare-decks
     trigger, HUD preview and tutorial entry, pushing actual play actions
     off the bottom of a typical phone screen. Reordering only, via flex
     `order` — no markup change, and harmless for the admin-configurable
     grid layouts (#lobby[data-layout="..."]) below: their own `display:
     grid` wins on specificity (ID+attribute beats this bare ID selector),
     and `order` has no effect on children an explicit `grid-area` already
     places. Picking a faction is still just a scroll away, and Create/
     Join still correctly prompts for one if none is selected yet. */
  #lobby { display: flex; flex-direction: column; }
  #lobby > .logo { order: 1; }
  #lobby > .account-panel { order: 2; }
  /* New players who just registered should see "Learn to Play" before
     anything else — right after login, ahead of match setup and the
     faction picker. */
  #lobby > .lobby-tutorial-entry { order: 3; }
  #lobby > .mode-tabs { order: 4; }
  #lobby > .lobby-actions,
  #lobby > .ai-match-panel { order: 5; }
  /* Deck/board-mode pickers (#deckPicker, #boardModePicker) — same
     "configure and start a match" cluster as the block above, sharing
     one class. */
  #lobby > .board-mode-picker { order: 6; }
  #lobby > .lobby-status { order: 7; }
  #lobby > .faction-picker { order: 8; }
  #lobby > .my-custom-decks-panel { order: 9; }
  #lobby > .deck-compare-trigger { order: 10; }
  #lobby > .lobby-hud-preview { order: 11; }
  #lobby > .seo-home-content { order: 12; }
  #lobby > .rankings-panel { order: 13; }
  #lobby > #lobbySidebarPreview,
  #lobby > #lobbySidebarHighlights { order: 14; }
  #lobby > #musicPlayerLobby { order: 15; }
  h1 { font-size: 1.8rem; margin: 1rem 0 0.1rem; }
  .lobby-actions { grid-template-columns: 1fr; gap: 0.8rem; }
  .mode-tabs { flex-direction: column; }
  .difficulty-picker { grid-template-columns: 1fr; }
  .board-mode-picker { grid-template-columns: 1fr; }
  .layout-picker { grid-template-columns: 1fr; }
  /* Fewer, wider columns here (not the desktop 5-across grid) — the deck
     face now carries a badge row, tagline and View Deck button that need
     real width to stay legible, per the master prompt's "stack deck info
     vertically on mobile" requirement. */
  .faction-picker { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin: 1rem 0; }
  .faction-card { padding: 0.5rem 0.3rem; gap: 0.3rem; }
  .faction-card .thumb { width: 52px; height: 78px; }
  .faction-card .label { font-size: 0.68rem; line-height: 1.1; }
  #cardPreview { height: min(390px, calc(100vh - 340px)); }
  .deck-preview-modal { width: 96vw; max-height: 92vh; }
  .deck-preview-body { grid-template-columns: 1fr; }
  .deck-preview-columns { grid-template-columns: 1fr; }
  .deck-card-grid { grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); max-height: 44vh; }
  .deck-builder-list { max-height: 30vh; }
  /* .deck-builder-body reuses .deck-preview-body's single-column stack
     above rather than a separate drawer/bottom-sheet component — the one
     addition a builder needs on top of the read-only preview is this: the
     card count and Save button pinned in place while the (now much
     taller, single-column) card grid scrolls underneath them. */
  .deck-builder-summary-bar {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--panel);
    padding: 6px 0;
    margin-top: -6px;
  }
}

@media (min-width: 1400px) {
  #cardPreview { height: min(600px, calc(100vh - 340px)); }
}

@media (max-width: 480px) {
  .faction-picker { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .faction-card { padding: 0.5rem 0.4rem; }
  .faction-card .thumb { width: 64px; height: 96px; }
  .faction-card .label { font-size: 0.72rem; }
  #cardPreview { height: min(315px, calc(100vh - 340px)); }
  .deck-preview-header { flex-direction: column; align-items: stretch; gap: 0.5rem; }
  .deck-card-grid { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
}

/* ---------- Mobile game screen ---------- */
/* A genuinely reflowed layout, not the desktop grid shrunk down — the
   desktop HUD system's own configured minimums (boardArea 320px + hand
   160px) already exceed a phone's entire width, so squeezing it further was
   never going to work. Below this breakpoint, initHudLayout()/
   applyPanelGeometry() (client.js) stop applying position:absolute to every
   panel and let them fall back to this grid's grid-area placement instead —
   the exact same fallback mechanism every panel already has for the one
   frame before the desktop JS takes over. */
@media (max-width: 760px) {
  /* #langPicker sits fixed at the top-right corner too — on a screen this
     narrow the centered notice's width reaches far enough right to
     overlap it, so it drops below instead. */
  .top-notice { top: 54px; }
  #game {
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr) auto auto;
    grid-template-areas: "topbar" "board" "hand" "controls";
    width: 100vw;
    height: 100vh;
    padding: env(safe-area-inset-top, 4px) env(safe-area-inset-right, 6px)
      env(safe-area-inset-bottom, 4px) env(safe-area-inset-left, 6px);
  }
  /* Extra top clearance only while the HUD preview's "Back to Lobby" button
     is showing — it's position:fixed in that same corner regardless of this
     grid, and would otherwise sit on top of the topbar's own content
     instead of above it. A real match never shows that button (edit/reset
     are hidden on mobile too), so it isn't worth reserving this space then. */
  #game:has(#hudPreviewExitBtn:not(.hidden)) {
    padding-top: max(env(safe-area-inset-top, 4px), 48px);
  }

  /* --- Top bar: name/mana panels fall back to a plain flex row instead of
     the desktop's centered-cluster math, which assumed real width either
     side of the turn indicator. --- */
  #topBar { flex-wrap: wrap; gap: 4px; padding: 4px 6px; }
  .hp-panel { min-width: 0; flex: 1 1 auto; }
  .hp-panel .name { font-size: clamp(0.65rem, 3.5vw, 0.9rem); }
  .turn-indicator { font-size: 0.85rem; flex: 0 0 100%; order: 3; }

  /* --- Mobile castle-attack fix: the side-panel castle (below, in the
     board row) can get squeezed off the visible viewport on a tall board,
     but #oppInfo/#youInfo here in the topbar row never do — and #oppInfo is
     already the actual attack-castle tap target (see onCastleClick() in
     client.js). Show the icon+HP that were hidden by default so mobile
     players have something to see and tap. --- */
  .hp-panel .castle-wrap { display: flex; width: clamp(24px, 8vw, 36px); height: clamp(24px, 8vw, 36px); margin: 0 auto; }
  .hp-panel .castle-hp-value { display: block; text-align: center; }

  /* --- Vitals strip: opponent above the board, you below it (right above
     the hand) — the side rails the desktop layout uses have no room next
     to 5 lane slots on a phone, so each becomes a full-width horizontal
     strip instead via flex-direction, not a new component. --- */
  .board-wrap { flex-direction: column; gap: 4px; }
  .side-panel {
    width: 100%;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    gap: 10px;
    padding: 4px 10px;
    border-radius: 10px;
  }
  .castle-hp-block { flex-direction: row; width: auto; flex: 1 1 auto; gap: 6px; }
  .castle-owner-label { display: none; }
  .side-panel .castle-wrap { width: clamp(40px, 12vw, 56px); height: clamp(40px, 12vw, 56px); }
  .side-panel .castle-hp-overlay { font-size: clamp(1rem, 6vw, 1.5rem); }
  .castle-hp-value { white-space: nowrap; }
  .panel-stat { min-width: 0; padding: 4px 8px; font-size: 0.85rem; gap: 4px; }
  .panel-icon { font-size: 0.95rem; }

  /* --- Board: full width, tighter gaps so 5 slots per lane actually fit. --- */
  .board-area { max-width: 100%; padding: 6px 4px; gap: 3px; }
  .lane-row { gap: 4px; padding: 2px; }
  /* Row labels shrink to icon+number on a phone — the full "Vanguard +1 Max
     HP" wording has no room next to slots this small and would overlap
     the first card's art. */
  #oppRearguard::before, #youRearguard::before { content: '🏹 +1'; }
  #oppVanguard::before, #youVanguard::before { content: '🛡 +1'; }
  .slot { min-height: 40px; }

  /* --- Hand: a horizontal scroll strip, never a multi-row wrap that would
     push the board out of view — tap-to-preview (client.js) covers reading
     a card's full text since there's no room to show it inline. --- */
  #hand {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    gap: 6px;
    padding: 6px;
    -webkit-overflow-scrolling: touch;
  }
  .hand-card { flex: 0 0 76px; }

  /* --- Controls: full-width bottom bar, comfortable thumb-sized buttons. --- */
  .controls { padding: 6px; gap: 0.5rem; }
  .controls .btn { padding: 0.6rem 1rem; font-size: 0.85rem; min-height: 44px; }

  /* Free-form HUD customization can't work with a fingertip on a screen
     this size (see isMobileLayout() in client.js) — only the always-needed
     "Back to Lobby" control (part of the same container) stays. */
  #hudEditToggle, #hudResetLayout { display: none; }

  /* --- Log/Chat: collapsed to small circular icon buttons pinned to the
     side edges, well clear of the bottom controls bar, instead of the
     desktop's full-width "📜 Log"/"💬 Chat" pills — those were both up to
     300px wide anchored at the SAME bottom-16px corner pair, which
     overlapped each other on anything narrower than ~650px and could sit
     on top of a wrapped 4th control button (Surrender) underneath. Expanded
     panels still grow upward from this anchor exactly as before (bottom
     stays fixed, height added pushes the top edge up) — only the collapsed
     resting position and size change. --- */
  #logPanel, #chatPanel {
    bottom: max(calc(env(safe-area-inset-bottom, 0px) + 128px), 128px);
  }
  #logPanel { left: 6px; }
  #chatPanel { right: 6px; }
  /* #logPanel/#chatPanel (ID selectors, specificity 1-0-0) already set
     width:300px in the base stylesheet — a plain .log-panel.collapsed class
     override (0-2-0) can never win that fight regardless of source order,
     so these repeat the ID to actually take effect. */
  #logPanel.collapsed, #chatPanel.collapsed { width: 46px; max-width: 46px; }
  #logPanel:not(.collapsed), #chatPanel:not(.collapsed) {
    width: min(280px, calc(100vw - 20px));
  }
  #logPanel.collapsed .log-toggle, #chatPanel.collapsed .chat-toggle {
    position: relative;
    width: 46px;
    height: 46px;
    padding: 0;
    justify-content: center;
    border-radius: 50%;
  }
  #logPanel.collapsed .toggle-label, #chatPanel.collapsed .toggle-label { display: none; }
  .toggle-icon { font-size: 1.1rem; line-height: 1; }
  /* Corner badge instead of an inline margin-left:auto pill — there's no
     "far right of a wide button" left once the button is a small circle. */
  #chatPanel.collapsed .chat-badge { position: absolute; top: -2px; right: -2px; margin-left: 0; }

  /* --- Card preview, in-match only: docked to the bottom edge instead of
     vertically centered, and shrunk further. Centered-and-tall (the base
     rule further up, shared with the lobby's deck browser, which has no
     board to hide behind it) sat on top of a meaningful chunk of the board
     on a phone — exactly the space a small screen can least afford to
     lose. Gated on body.in-game since #cardPreview is one shared element
     used by both screens (it lives outside both #lobby and #game in the
     markup), so the lobby's own preview is untouched. --- */
  body.in-game #cardPreview {
    left: 50%;
    right: auto;
    top: auto;
    bottom: max(env(safe-area-inset-bottom, 8px), 8px);
    transform: translateX(-50%) scale(0.97);
    height: min(200px, 30vh);
  }
  body.in-game #cardPreview.visible { transform: translateX(-50%) scale(1); }
}

/* ---------- 2v2 Team Battle board ---------- */
/* A completely separate screen from #game (see index.html) — every rule
   here is scoped to #game2v2/.team2v2-*, so nothing below can ever affect
   1v1's board. Reuses .lane-row/.slot/.card/.mana-crystals/.castle-wrap
   wholesale from the 1v1 board's own CSS above (identical box model, card
   art, hover states, .attack-ready glow) — only the outer quadrant/grid
   layout and the MY/ALLY/ENEMY team-color language below are new.
   Deliberately a plain vertical flex stack, not #game's resizable-hand-
   column HUD grid — the HUD drag/resize manager (initHudLayout()) isn't
   extended to 2v2 in this pass, a disclosed simplification. */
#game2v2 {
  display: flex;
  flex-direction: column;
  width: min(98vw, 1900px);
  height: min(96vh, 1080px);
  max-width: 100%;
  max-height: 100%;
  margin: 0 auto;
  padding: clamp(4px, 0.5vh, 10px) clamp(6px, 0.8vw, 14px);
  gap: clamp(4px, 0.6vh, 8px);
  box-sizing: border-box;
  overflow: hidden;
}
#team2v2TopBar { display: flex; justify-content: center; flex: 0 0 auto; }
/* Smaller than 1v1's own .turn-indicator (which sits in a wider, roomier
   top bar) — this stacked turn-text/timer/sfx column was eating more
   vertical budget here than it needs to, at the direct expense of the
   hand strip below actually having room to show full cards. */
#team2v2TopBar .turn-indicator { font-size: 0.95rem; gap: 0; }

/* One "side" per TEAM (not per player) — a header (shared castle + both
   players' compact info strips) plus one shared 6-slot row. The enemy
   side's row sits above its header (units near the middle of the
   screen, castle at the outer top edge); the self side mirrors that
   (row nearest the middle, castle/header at the outer bottom edge) —
   same top=enemy/bottom=you convention 1v1 already uses. */
.team2v2-side {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.team2v2-side.enemy { flex-direction: column; }
.team2v2-side.self { flex-direction: column-reverse; }
/* The base .castle-wrap size (clamp 34-52px) is tuned for the small
   top-bar badge context — a real focal-element castle needs the same
   bump up 1v1's own side panel gives it (.side-panel .castle-wrap
   further up), or it reads as a barely-visible smudge next to the HP
   number instead of actual castle art. */
.team2v2-side-header .castle-wrap { width: clamp(56px, 5vw, 90px); height: clamp(56px, 5vw, 90px); }
.team2v2-side-header {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  gap: 0.6rem;
  padding: 0.2rem 0.6rem;
  border-radius: 10px;
  background: radial-gradient(ellipse at center, #1c150d 0%, #100c07 100%);
  border: 2px solid var(--border);
}
.team2v2-side.enemy .team2v2-side-header { border-color: #b3382c; }
.team2v2-side.self .team2v2-side-header { border-color: var(--gold); }
.team2v2-side-info { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; flex: 1; }
.team2v2-side-title {
  font-family: var(--font-display);
  font-size: var(--text-sm);
  letter-spacing: 1px;
  color: var(--text);
}
.team2v2-side.enemy .team2v2-side-title { color: #ec9a8e; }
.team2v2-side.self .team2v2-side-title { color: var(--gold); }
/* Each player's own compact strip (name/faction/mana) — the SAME visual
   owner-color language the shared-row units use below, so a player's
   strip and their units on the board read as the same person at a
   glance. Enemy1/enemy2 intentionally share one color (attacking
   targets the shared enemy board as a whole, not "enemy 1's board"),
   self/ally get their own distinct colors. */
.team2v2-player-strip {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: var(--text-xs);
  padding: 0.1rem 0.4rem;
  border-radius: 6px;
  border-left: 3px solid var(--border);
}
.team2v2-player-strip[data-role="self"] { border-left-color: var(--gold); }
.team2v2-player-strip[data-role="ally"] { border-left-color: #3a7bd5; }
.team2v2-player-strip[data-role="enemy1"], .team2v2-player-strip[data-role="enemy2"] { border-left-color: #b3382c; }
.team2v2-player-strip.active-turn { background: #ffd76a1a; animation: pulse-gold 2.2s ease-in-out infinite; }
.team2v2-player-strip.knocked-out { opacity: 0.5; text-decoration: line-through; }
.team2v2-player-name { font-family: var(--font-display); color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 9em; }
.team2v2-player-faction { color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.team2v2-player-mana { color: var(--gold-bright); font-family: var(--font-display); margin-left: auto; }
.team2v2-player-strip .mana-crystals { display: flex; gap: 1px; }
.team2v2-player-strip .mana-crystals .crystal { width: 7px; height: 7px; }

/* The shared 6-slot row itself — same box model/background as 1v1's
   .lane-row, and deliberately the SAME sizing approach too: slots are
   NOT flex:1 (that stretches every card into a flat, squeezed rectangle
   the moment the row is wider than 6 real card-shaped slots need — which
   it almost always is, this row spans nearly the full board width).
   Letting each .slot size itself from the row's height via its own
   aspect-ratio (see the base .slot rule) and centering the row with
   justify-content is what keeps them genuine card proportions, same as
   every other board in this game. */
.team2v2-shared-row {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 4px;
  border-radius: 10px;
}
.team2v2-side.enemy .team2v2-shared-row { background: linear-gradient(180deg, var(--vanguard), transparent); }
.team2v2-side.self .team2v2-shared-row { background: linear-gradient(0deg, var(--vanguard), transparent); }
/* Per-unit ownership border — MY unit / ALLY unit / ENEMY unit, the same
   3-color language as the player strips above, applied directly to the
   card so it reads correctly even once units from both teammates are
   mixed together in one row. */
.card.owner-self { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold) inset; }
.card.owner-ally { border-color: #3a7bd5; box-shadow: 0 0 0 1px #3a7bd5 inset; }
.card.owner-enemy { border-color: #b3382c; box-shadow: 0 0 0 1px #b3382c inset; }

#controls2v2 { display: flex; align-items: center; justify-content: center; gap: 0.6rem; flex-wrap: wrap; }
/* auto-fit (not auto-fill) so a half-empty hand's cards actually grow to
   fill the row instead of leaving empty ghost columns to their right —
   noticeably wrong at this hand's full-width, only-a-few-cards common
   case. Taller + a wider per-card cap than 1v1's narrow sidebar hand
   (the base .hand rule further up), since this hand strip spans nearly
   the full board width and has real room to spend on bigger cards —
   also absorbs the vertical space freed by not having a second always-
   open log panel down here anymore (see renderLog2v2() in client.js). */
#hand2v2 {
  flex: 0 0 auto;
  /* Sized to actually fit a full card at the grid's own max column width
     below (140px wide -> 210px tall via the 2:3 aspect-ratio, +20px for
     .hand's own top/bottom padding = 230px) with a little headroom — a
     mismatch here is exactly what was forcing a vertical scrollbar
     instead of showing the hand's cards fully. */
  height: clamp(170px, 25vh, 240px);
  grid-template-columns: repeat(auto-fit, minmax(100px, 140px));
  justify-content: center;
}

@media (max-width: 900px) {
  /* A single shared row per side (6 slots, not 2x2 quadrants) fits a
     phone far better than the old per-player-board layout did — no
     quadrant-focus/tab-switcher needed at all, every slot just gets
     smaller, same approach 1v1's own board already uses at this width. */
  .team2v2-side-header { padding: 0.25rem 0.4rem; gap: 0.4rem; }
  .team2v2-side-info { gap: 0.1rem; }
  .team2v2-player-strip { flex-wrap: wrap; font-size: 0.65rem; padding: 0.05rem 0.3rem; }
  .team2v2-player-name { max-width: 6em; }
  .team2v2-shared-row { gap: 4px; padding: 3px; }
  /* Width is the scarce resource here, not height — 8 cards must fit a
     ~360px-wide phone screen. max-height caps how tall (and via
     aspect-ratio, how WIDE) each card can grow to, since the row itself
     may have plenty of vertical room to give on a tall phone; without
     this cap the base .slot rule's 260px desktop ceiling lets 8 cards
     blow way past the viewport width. ~57px tall -> ~38px wide each,
     8 of them + 7 gaps comfortably fits 360px. */
  .team2v2-shared-row .slot { min-height: 30px; max-height: 57px; }
  /* Smaller cards fit more per row on a narrow phone (3 instead of 2 at
     the desktop min-width), which meaningfully cuts down how often a
     typical mid-size hand needs a 2nd row/scrolling at all — a maxed-out
     10-card hand will still scroll on any phone regardless of sizing,
     that's inherent to the hand cap, not something to chase pixel-
     perfect here. */
  #hand2v2 { grid-template-columns: repeat(auto-fit, minmax(78px, 105px)); gap: 6px; height: clamp(150px, 22vh, 190px); }
}

/* ---------- Landscape phone (height-constrained, not width-constrained)
   ---------- */
/* A phone turned sideways is wide but short — the opposite resource
   constraint from the max-width:760px block above, which assumes width is
   scarce and height is plentiful. Most modern phones in landscape are
   WIDER than 760px (iPhone 12 mini/14/15 land at 780-930px), so without a
   height-based breakpoint they'd fall straight into the desktop layout
   instead, which has no height floor at all: .slot's hard min-height:60px
   (below) alone exceeds the ~200-230px a ~375-430px-tall viewport actually
   has for 4 rows once the topbar/controls are subtracted, and #game/
   body.in-game both clip overflow rather than scroll it. isMobileLayout()
   in client.js needs this same height threshold, or the HUD manager's
   free-form inline positioning (which always wins over stylesheet rules)
   would silently override everything below.
   Placed after the max-width block so it wins the cascade for a phone
   that's BOTH narrow and short (e.g. iPhone SE landscape, 667x375). 500px
   comfortably covers every real phone in landscape while excluding
   tablets (~768px+ even sideways) and ordinary desktop windows — as a
   bonus this also fixes #cardPreview's existing short-window formula
   further down, which its own comment already flags as never having been
   tuned this short. */
@media (max-height: 500px) {
  #game {
    grid-template-columns: minmax(0, 1fr) 6px minmax(88px, 15vw);
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "topbar   topbar topbar"
      "board    handle hand"
      "controls handle hand";
    width: 100vw;
    height: 100dvh;
    padding: env(safe-area-inset-top, 2px) env(safe-area-inset-right, 4px)
      env(safe-area-inset-bottom, 2px) env(safe-area-inset-left, 4px);
    gap: 3px;
  }
  #game:has(#hudPreviewExitBtn:not(.hidden)) {
    padding-top: max(env(safe-area-inset-top, 2px), 40px);
  }

  /* --- Top bar: same flex-wrap fallback as portrait mobile, just tighter
     since there's a fraction of the vertical room to spend on it. --- */
  #topBar { flex-wrap: wrap; gap: 3px; padding: 2px 4px; }
  .hp-panel { min-width: 0; flex: 1 1 auto; }
  .hp-panel .name { font-size: clamp(0.6rem, 2.5vw, 0.8rem); }
  .turn-indicator { font-size: 0.75rem; flex: 0 0 100%; order: 3; }
  .hp-panel .castle-wrap { display: flex; width: clamp(18px, 4vh, 28px); height: clamp(18px, 4vh, 28px); margin: 0 auto; }
  .hp-panel .castle-hp-value { display: block; text-align: center; }

  /* --- Board + side-panels: back to a row, like desktop, not portrait
     mobile's stacked horizontal strips — width is abundant here, height
     isn't, so wasting a whole horizontal band on each side-panel would be
     the wrong trade in this orientation. --- */
  .board-wrap { flex-direction: row; gap: 4px; }
  .side-panel {
    width: clamp(44px, 9vw, 72px);
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 4px;
    padding: 3px;
    border-radius: 8px;
  }
  .castle-hp-block { flex-direction: column; width: 100%; gap: 2px; }
  .castle-owner-label { display: none; }
  .side-panel .castle-wrap { width: clamp(32px, 7vh, 52px); height: clamp(32px, 7vh, 52px); }
  .side-panel .castle-hp-overlay { font-size: clamp(0.8rem, 4vh, 1.2rem); }
  .panel-stat { min-width: 0; padding: 2px 4px; font-size: 0.7rem; gap: 2px; }
  .panel-icon { font-size: 0.8rem; }

  /* --- Board: every pixel of row height is scarcer here than even on
     portrait mobile (which at least had a tall, narrow viewport to work
     with) — shrink the slot floor further than that breakpoint's 40px. --- */
  .board-area { padding: 3px; gap: 2px; }
  .lane-row { gap: 3px; padding: 1px; }
  .slot { min-height: 28px; }

  /* --- Hand: a side column (like desktop), not a bottom strip — a bottom
     strip would spend ~100px of the one dimension this orientation can't
     spare. Narrower than desktop's default column and scrollable, since
     only a card or two is visible at once at this width. --- */
  #hand {
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    overflow-x: hidden;
    gap: 4px;
    padding: 3px;
    -webkit-overflow-scrolling: touch;
  }
  .hand-card { flex: 0 0 auto; }

  /* --- Controls: compact, still thumb-usable. --- */
  .controls { padding: 3px; gap: 0.3rem; }
  .controls .btn { padding: 0.35rem 0.6rem; font-size: 0.7rem; min-height: 32px; }

  /* Free-form HUD customization can't work here either — see
     isMobileLayout() in client.js, which now also checks height. */
  #hudEditToggle, #hudResetLayout { display: none; }

  /* --- Card preview: the existing calc(100vh - 340px) formula (further
     down) was tuned for a short DESKTOP window, not a ~375-430px-tall
     phone — it collapses to an unreadable sliver there. --- */
  #cardPreview { height: min(280px, calc(100dvh - 40px)); }
  /* In-match only (see the portrait breakpoint's identical comment) —
     smaller still and bottom-docked, so it doesn't sit over the board in
     an orientation where height is already the scarce resource. */
  body.in-game #cardPreview {
    left: 50%;
    right: auto;
    top: auto;
    bottom: max(env(safe-area-inset-bottom, 4px), 4px);
    transform: translateX(-50%) scale(0.97);
    height: min(160px, 45vh);
  }
  body.in-game #cardPreview.visible { transform: translateX(-50%) scale(1); }

  /* --- Log/Chat: same collapsed-icon treatment portrait mobile uses,
     anchored tighter since there's no bottom hand strip to clear here —
     the controls bar is the only thing below them — and height-capped so
     an expanded panel can't grow past what's actually on screen. --- */
  #logPanel, #chatPanel {
    bottom: max(calc(env(safe-area-inset-bottom, 0px) + 40px), 40px);
  }
  #logPanel { left: 4px; }
  #chatPanel { right: 4px; }
  #logPanel.collapsed, #chatPanel.collapsed { width: 38px; max-width: 38px; }
  #logPanel:not(.collapsed), #chatPanel:not(.collapsed) {
    width: min(240px, calc(100vw - 16px));
    max-height: min(240px, calc(100dvh - 60px));
  }
  #logPanel.collapsed .log-toggle, #chatPanel.collapsed .chat-toggle {
    position: relative;
    width: 38px;
    height: 38px;
    padding: 0;
    justify-content: center;
    border-radius: 50%;
  }
  #logPanel.collapsed .toggle-label, #chatPanel.collapsed .toggle-label { display: none; }
  #chatPanel.collapsed .chat-badge { position: absolute; top: -2px; right: -2px; margin-left: 0; }
}
