/* ── Menu Screens ────────────────────────────────────────────── */

.menu-screen {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* 100dvh (dynamic viewport) tracks the actual visible area on mobile —
     100vh includes the slice behind the address bar, which pushed the
     vertically-centered content off-screen at the bottom. */
  min-height: 100dvh;
  padding: 16px 16px;
  gap: 8px;
  background:
    radial-gradient(ellipse at 50% 20%, rgba(15, 52, 96, 0.25) 0%, transparent 50%),
    radial-gradient(ellipse at 50% 80%, rgba(233, 69, 96, 0.06) 0%, transparent 40%);
}

.menu-title {
  font-size: 42px;
  font-weight: 900;
  color: var(--text-primary);
  letter-spacing: 4px;
  text-transform: uppercase;
  text-shadow: 0 0 40px rgba(233, 69, 96, 0.2), 0 2px 4px rgba(0, 0, 0, 0.5);
}

.menu-subtitle {
  color: var(--text-secondary);
  font-size: 16px;
  margin-bottom: 16px;
  letter-spacing: 0.5px;
}

.menu-btn {
  position: relative;
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-primary);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 8px;
  padding: 10px 24px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;
}

/* Notification pip — small red dot in the upper-right corner of a
   menu button, drawing the eye to actionable content (e.g. the
   Campaign button when stages are available but unfinished). */
.menu-btn .menu-pip {
  position: absolute;
  top: -5px;
  right: -5px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg-primary);
  box-shadow: 0 0 6px rgba(233, 69, 96, 0.7);
}

.menu-btn:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.12);
  border-color: rgba(255, 255, 255, 0.3);
  transform: translateY(-1px);
}

.menu-btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: white;
  box-shadow: 0 4px 16px rgba(233, 69, 96, 0.3);
}

.menu-btn.primary-blue {
  background: var(--spirit-blue);
  border-color: var(--spirit-blue);
  color: white;
  box-shadow: 0 4px 16px rgba(78, 168, 222, 0.3);
}
.menu-btn.primary-blue:hover:not(:disabled) {
  background: #3b8abf;
  box-shadow: 0 6px 20px rgba(78, 168, 222, 0.4);
  transform: translateY(-1px);
}

/* Purple variant used by tournament CTAs — distinct from primary
 * (red, Play) and primary-blue (campaign) so the player learns the
 * mode at a glance. Shares the same shadow / hover treatment for
 * visual consistency. */
.menu-btn.primary-purple {
  background: #8b5cf6;
  border-color: #8b5cf6;
  color: white;
  box-shadow: 0 4px 16px rgba(139, 92, 246, 0.3);
}
.menu-btn.primary-purple:hover:not(:disabled) {
  background: #7c3aed;
  box-shadow: 0 6px 20px rgba(139, 92, 246, 0.4);
  transform: translateY(-1px);
}

/* Green variant used by store buy buttons that spend real money —
 * green reads as "go / buy" and is more inviting than the red accent
 * for opt-in spend. Same hue family as the FREE pack-tile ribbon so
 * the player learns to associate green with positive transactions. */
.menu-btn.primary-green {
  background: #22c55e;
  border-color: #22c55e;
  color: white;
  box-shadow: 0 4px 16px rgba(34, 197, 94, 0.3);
}
.menu-btn.primary-green:hover:not(:disabled) {
  background: #16a34a;
  box-shadow: 0 6px 20px rgba(34, 197, 94, 0.4);
  transform: translateY(-1px);
}

.menu-btn-sm {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-primary);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 4px;
  padding: 4px 10px;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.15s;
}
.menu-btn-sm:hover { border-color: rgba(255, 255, 255, 0.3); }

.menu-btn-sm.danger {
  background: var(--accent);
  border-color: var(--accent);
  color: white;
}
.menu-btn-sm.danger:hover { filter: brightness(1.1); }

.menu-btn-sm.primary-green {
  background: #22c55e;
  border-color: #22c55e;
  color: white;
}
.menu-btn-sm.primary-green:hover { filter: brightness(1.1); }

.menu-btn.primary:hover:not(:disabled) {
  background: #d13850;
  box-shadow: 0 6px 20px rgba(233, 69, 96, 0.4);
  transform: translateY(-1px);
}

/* Destructive variant. Used by confirmation modals for delete /
 * discard flows AND by inline buttons that fire those flows. Same
 * visual weight as `.menu-btn` (outline, not filled) so destructive
 * actions don't compete with the primary CTA, but a red tint signals
 * the consequence. */
.menu-btn.danger {
  color: var(--accent);
  border-color: rgba(233, 69, 96, 0.4);
}
.menu-btn.danger:hover:not(:disabled) {
  background: rgba(233, 69, 96, 0.15);
  border-color: rgba(233, 69, 96, 0.7);
}

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

/* ── Account Bar ───────────────────────────────────────────── */

.account-bar {
  position: absolute;
  top: 12px;
  right: 16px;
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 13px;
  color: var(--text-secondary);
}

.account-status {
  opacity: 0.7;
}

.account-status strong {
  color: var(--text-primary);
}

.account-link, .account-sign-in-link {
  color: var(--spirit-blue);
  cursor: pointer;
  transition: color 0.15s;
}

.account-link:hover, .account-sign-in-link:hover {
  color: #8fc4ef;
  text-decoration: underline;
}

/* ── Auth Modal ────────────────────────────────────────────── */

.auth-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.auth-modal {
  background: var(--bg-secondary);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 14px;
  padding: 36px;
  width: 360px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
}

.auth-modal h2 {
  font-size: 20px;
  font-weight: 700;
  color: var(--text-primary);
  text-align: center;
}

.auth-modal input {
  background: var(--bg-primary);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 6px;
  padding: 10px 14px;
  font-size: 14px;
  color: var(--text-primary);
  width: 100%;
}

.auth-modal input:focus {
  outline: none;
  border-color: var(--spirit-blue);
}

.auth-modal .auth-error {
  color: var(--accent);
  font-size: 13px;
  text-align: center;
  min-height: 18px;
}

.auth-modal .auth-buttons {
  display: flex;
  gap: 8px;
}

.auth-modal .auth-buttons .menu-btn {
  flex: 1;
}

.account-gate-message {
  font-size: 14px;
  color: var(--text-secondary);
  text-align: center;
  line-height: 1.5;
}

/* Gate modal needs three buttons (Cancel / Sign In / Create Account) — wrap
 * to a second row on narrow viewports so labels don't truncate. */
.account-gate .auth-buttons {
  flex-wrap: wrap;
}

/* ── Starter Selection ──────────────────────────────────────── */

.starter-deck-grid {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
  justify-content: center;
  max-width: 960px;
}

.starter-deck-card {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 14px;
  padding: 24px;
  width: 280px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  transition: all 0.25s;
}

.starter-deck-card:hover {
  border-color: rgba(233, 69, 96, 0.5);
  background: rgba(233, 69, 96, 0.03);
  transform: translateY(-4px);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}

.starter-deck-name {
  font-size: 20px;
  font-weight: 700;
  color: var(--text-primary);
}

.starter-deck-info {
  font-size: 12px;
  color: var(--text-secondary);
}

.starter-deck-rarity {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.rarity-tag {
  font-size: 10px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 4px;
  text-transform: uppercase;
}

.rarity-tag.common { background: rgba(148, 148, 148, 0.2); color: #aaa; }
.rarity-tag.uncommon { background: rgba(74, 222, 128, 0.15); color: var(--uncommon-border); }
.rarity-tag.rare { background: rgba(78, 168, 222, 0.15); color: var(--rare-border); }
.rarity-tag.legendary { background: rgba(255, 215, 0, 0.15); color: var(--legendary-border); }

.starter-deck-key-cards {
  font-size: 11px;
  color: var(--text-secondary);
  font-style: italic;
}

/* ── Main Menu ──────────────────────────────────────────────── */

.player-currencies {
  display: flex;
  gap: 12px;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: 0.5px;
}

/* Daily-gems claim button + "Buy Gems" link sit inline with the
 * coins / gems numbers so the player has a single bar of currency
 * affordances. The buttons are sized down (font 12, padding 6×10) so
 * they don't fight visually with the numerical balances. */
.player-currencies .menu-btn-sm {
  font-size: 12px;
  padding: 6px 12px;
}

.player-currencies .gem-shop-link {
  color: #5dd0ff;
  border-color: rgba(78, 168, 222, 0.35);
}

/* Daily-gems claim button: blue (matches the gem balance color, less
 * threatening than the red accent) with a soft pulse to invite a
 * click. The leading sparkle twinkles asynchronously so the button
 * doesn't read as a static decorative element. */
.player-currencies .daily-gems-btn {
  background: var(--spirit-blue);
  border-color: var(--spirit-blue);
  color: white;
  box-shadow: 0 0 10px rgba(78, 168, 222, 0.45);
  animation: daily-gems-pulse 2.4s ease-in-out infinite;
}

.player-currencies .daily-gems-btn:hover:not(:disabled) {
  background: #3b8abf;
  border-color: #3b8abf;
  box-shadow: 0 0 16px rgba(78, 168, 222, 0.7);
}

.daily-gems-sparkle {
  display: inline-block;
  margin-right: 2px;
  animation: daily-gems-sparkle 1.6s ease-in-out infinite;
}

@keyframes daily-gems-pulse {
  0%, 100% { box-shadow: 0 0 8px rgba(78, 168, 222, 0.40); }
  50%      { box-shadow: 0 0 18px rgba(78, 168, 222, 0.80); }
}

@keyframes daily-gems-sparkle {
  0%, 100% { transform: scale(1) rotate(0deg);   opacity: 0.85; }
  50%      { transform: scale(1.25) rotate(12deg); opacity: 1; }
}

/* Daily-gems claim flyaway — floats up from the claim button so the
 * player sees the gain amount without having to spot the balance
 * ticking. Positioned by viewport coords (fixed) since spawnGemGainFlyaway
 * appends to <body>, not the menu/shop view. */
.gem-gain-popup {
  position: fixed;
  font-size: 22px;
  font-weight: 800;
  color: #5dd0ff;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.85), 0 0 12px rgba(93, 208, 255, 0.6);
  pointer-events: none;
  z-index: 2000;
  transform: translateX(-50%);
  animation: gem-gain-float 1.2s ease-out forwards;
}

@keyframes gem-gain-float {
  0% {
    opacity: 0;
    transform: translate(-50%, 4px) scale(0.85);
  }
  15% {
    opacity: 1;
    transform: translate(-50%, 0) scale(1.1);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -48px) scale(1.1);
  }
}

.player-coins {
  color: var(--gold);
  text-shadow: 0 0 12px rgba(255, 215, 0, 0.15);
}

.player-gems {
  color: #5dd0ff;
  text-shadow: 0 0 12px rgba(93, 208, 255, 0.2);
}

.menu-buttons {
  display: flex;
  gap: 12px;
  /* Breathing room above + below the primary CTA row so the title
   * stack reads as a clear "header zone" and the tournament widget
   * doesn't feel jammed under the buttons. Parent .menu-screen gap
   * already provides ~8px; these margins add a bit more. */
  margin: 8px 0 24px;
}

.menu-buttons .menu-btn.primary {
  padding: 14px 48px;
  font-size: 18px;
  letter-spacing: 1px;
  border-radius: 10px;
}

/* ── Menu Content Layout ────────────────────────────────────── */

.menu-content {
  display: flex;
  gap: 32px;
  align-items: flex-start;
  justify-content: center;
  width: 100%;
  max-width: 900px;
  /* Spacing above governed by the parent .menu-screen `gap`. */
}

@media (max-width: 700px) {
  .menu-content {
    flex-direction: column;
    align-items: center;
    /* Desktop uses a 32px horizontal gap between deck-manager and
     * pack-shop. When the layout stacks vertically on mobile, 32px
     * reads as way too much breathing room between YOUR DECKS and
     * CARD SHOP — shrink to a tight vertical gap. */
    gap: 8px;
  }
}

/* ── Deck Manager ───────────────────────────────────────────── */

.deck-manager {
  flex: 1;
  max-width: 420px;
  min-width: 300px;
}

.section-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-secondary);
  margin-bottom: 10px;
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 2px;
}

.deck-list-menu {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 12px;
}

.deck-list-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 10px;
  transition: all 0.2s;
}

.deck-list-item:hover {
  background: rgba(255, 255, 255, 0.05);
}

.deck-list-item.active {
  border-color: rgba(233, 69, 96, 0.4);
  background: rgba(233, 69, 96, 0.04);
}

.dli-info {
  display: flex;
  align-items: center;
  gap: 10px;
}

.dli-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-primary);
}

.dli-count {
  font-size: 12px;
  color: var(--text-secondary);
}

.dli-count.incomplete {
  color: var(--accent);
}

.dli-active-badge {
  font-size: 10px;
  font-weight: 700;
  color: var(--accent);
  background: rgba(233, 69, 96, 0.15);
  padding: 2px 8px;
  border-radius: 4px;
  text-transform: uppercase;
}

.dli-actions {
  display: flex;
  gap: 6px;
}

/* Buy-an-extra-deck-slot button: full width to match the rows above
   it, with the gem cost on the left (spirit-blue, matches .player-gems)
   and the label on the right — same left/right shape as deck-list-item
   so the deck-manager column reads as one consistent stack. */
.buy-slot-btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
}

.buy-slot-cost {
  color: #5dd0ff;
  text-shadow: 0 0 12px rgba(93, 208, 255, 0.2);
  font-weight: 600;
}

.buy-slot-label {
  font-weight: 600;
}

.dli-btn {
  padding: 4px 10px;
  font-size: 11px;
}

.dli-btn.danger {
  color: var(--accent);
  border-color: rgba(233, 69, 96, 0.3);
}

.dli-btn.danger:hover {
  background: rgba(233, 69, 96, 0.15);
}

.db-rename-btn {
  padding: 4px 10px;
  font-size: 11px;
}

/* Tap-to-rename affordance on the deck-name heading. Dashed underline +
 * pointer cursor signal "this is interactive" — clearer than a plain
 * heading that happens to be clickable. The Rename button used to
 * carry this affordance; folding it into the title freed up phone-
 * header real estate so Save stays visible. */
.db-deck-name {
  cursor: pointer;
  text-decoration: underline dashed rgba(255, 255, 255, 0.25);
  text-underline-offset: 4px;
}
.db-deck-name:hover {
  text-decoration-color: rgba(255, 255, 255, 0.6);
}

/* ── Pack Shop ──────────────────────────────────────────────── */

.pack-shop {
  text-align: center;
  flex: 1;
  max-width: 420px;
  min-width: 260px;
}

.pack-grid {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: stretch;
}

.pack-card {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  padding: 16px 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  transition: all 0.2s;
  position: relative;
}

.pack-card:hover {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.15);
}

/* FREE ribbon: shown when the player has at least one matching ticket
 * for this pack. Pinned to the top-right corner, mirrors the
 * .completion-badge / .lock-badge corner-pin pattern used elsewhere. */
.pack-card.has-ticket {
  border-color: rgba(34, 197, 94, 0.55);
  background: rgba(34, 197, 94, 0.06);
}

.pack-card.has-ticket::before {
  content: 'FREE';
  position: absolute;
  top: -10px;
  right: 10px;
  background: linear-gradient(135deg, #22c55e, #16a34a);
  color: white;
  padding: 3px 12px;
  border-radius: 4px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 1px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}

.pack-details {
  flex: 1;
  text-align: left;
}

.pack-name {
  font-size: 15px;
  font-weight: 700;
  color: var(--text-primary);
}

.pack-info {
  font-size: 12px;
  color: var(--text-secondary);
}

/* ── Deck Builder ───────────────────────────────────────────── */

.deck-builder {
  display: flex;
  flex-direction: column;
  /* 100dvh follows the visible viewport so the bottom of the panel
     isn't clipped behind the mobile address bar. With overflow:
     hidden, 100vh would silently truncate the inner grid. */
  height: 100dvh;
  overflow: hidden;
}

.deck-builder-header {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 16px;
  background: var(--bg-secondary);
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.deck-builder-header h2 {
  flex: 1;
  font-size: 18px;
  color: var(--text-primary);
}

.deck-counter {
  font-size: 16px;
  font-weight: 700;
  color: var(--accent);
  padding: 4px 12px;
  border-radius: 6px;
  background: rgba(233, 69, 96, 0.15);
}

.deck-counter.complete {
  color: var(--hp-green);
  background: rgba(74, 222, 128, 0.15);
}

.deck-builder-body {
  display: flex;
  flex: 1;
  overflow: hidden;
}

.collection-panel {
  flex: 2;
  display: flex;
  flex-direction: column;
  border-right: 1px solid rgba(255, 255, 255, 0.1);
  overflow: hidden;
}

/* The h3 used to live in its own column with `justify-content: space-between`,
   which left a wide unused gap on narrow screens. The h3 is now a flex child
   of `.collection-filters` itself, so it wraps along with the controls. */
.collection-filters h3 {
  font-size: 14px;
  color: var(--text-primary);
  margin: 0;
  margin-right: 4px;
}

.collection-filters {
  padding: 8px 12px;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}

.collection-filters select {
  background: var(--bg-secondary);
  color: var(--text-primary);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 11px;
}

/* Tag filter dropdown */
.tag-filter-dropdown {
  position: relative;
}

.tag-filter-btn {
  background: var(--bg-secondary);
  color: var(--text-primary);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 11px;
  cursor: pointer;
  white-space: nowrap;
}

.tag-filter-btn:hover {
  border-color: rgba(255, 255, 255, 0.3);
}

.tag-filter-menu {
  display: none;
  position: absolute;
  top: 100%;
  right: 0;
  background: var(--bg-secondary);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 6px;
  padding: 6px 0;
  z-index: 100;
  max-height: 300px;
  overflow-y: auto;
  min-width: 160px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}

.tag-filter-menu.open {
  display: block;
}

.tag-filter-item {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 12px;
  font-size: 11px;
  color: var(--text-primary);
  cursor: pointer;
  white-space: nowrap;
}

.tag-filter-item:hover {
  background: rgba(255, 255, 255, 0.05);
}

.tag-filter-item input {
  accent-color: var(--spirit-blue);
}

/* Header row in the Tags dropdown holds the All checkbox on the left and
   the Or/And mode toggle on the right. */
.tag-filter-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px 0 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  margin-bottom: 4px;
}

.tag-filter-header .tag-filter-item {
  flex: 1;
}

.tag-filter-mode {
  display: inline-flex;
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 4px;
  overflow: hidden;
}

.tag-filter-mode button {
  background: transparent;
  color: var(--text-secondary);
  border: none;
  padding: 2px 8px;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}

.tag-filter-mode button + button {
  border-left: 1px solid rgba(255, 255, 255, 0.15);
}

.tag-filter-mode button.active {
  background: var(--spirit-blue);
  color: white;
}

.collection-grid,
.deck-list {
  --cb-card-scale-set: 1;
  --cb-cell-size: calc(110px * var(--cb-card-scale-set, 1));
  flex: 1;
  overflow-y: auto;
  padding: 14px 12px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--cb-cell-size), 1fr));
  gap: 18px 10px;
  align-content: start;
  justify-items: center;
}

/* Row view: deck panel is always a single column (the user wants every deck
   row legible end-to-end). The collection panel is single-column on mobile
   and at higher size steps; only at the two smallest steps on desktop does
   it get a second column (toggled by the `.row-cols-2` class from JS).
   `minmax(0, 1fr)` is critical — the default `1fr` minimum is `auto`,
   which lets a row's intrinsic (nowrap) content force the column wider
   than the panel and clip the right edge. */
.collection-grid.view-row,
.deck-list.view-row {
  gap: 4px 8px;
  justify-items: stretch;
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 700px) {
  .collection-grid.view-row.row-cols-2 {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

.card-tile.row {
  /* Fixed-width grid columns so power / clock / spirit / count line up
     vertically across every row in the list. The two flexible slots are
     the name (column 1) and the ability text (column 5). At narrow widths
     the ability text is hidden and column 5 collapses to the icon's
     intrinsic width — see the media query below.
     `font-variant-numeric: tabular-nums` keeps digit advance widths equal
     across "400" / "1000" / "1100" so the right-aligned numbers stack
     cleanly. Stat ems are tuned to the longest content the column ever
     holds: 4-digit power ("1200"), 1-emoji+1-digit clock ("⏱5"), signed
     ±1-digit+1-emoji spirit ("+5✦"), 2-digit/1-digit count ("10/1"). */
  min-width: 0;
  width: 100%;
  box-sizing: border-box;
  height: var(--cb-row-height, 32px);
  display: grid;
  grid-template-columns: minmax(0, 1fr) 2.6em 1.9em 2.6em minmax(0, 1fr) 2.5em;
  align-items: center;
  gap: 6px;
  padding: 0 8px;
  font-size: var(--cb-row-fs, 12px);
  font-variant-numeric: tabular-nums;
  background: rgba(255, 255, 255, 0.04);
  border-radius: 4px;
  cursor: pointer;
  border: 1px solid rgba(255, 255, 255, 0.06);
}

/* Stat cells are grid items; right-align so the digit ones-place sits at
   the column's right edge and tens-place stacks above tens, etc. */
.card-tile.row .row-stat { text-align: right; }

.card-tile.row:hover {
  background: rgba(255, 255, 255, 0.08);
}

.card-tile.row[data-rarity="uncommon"]  { border-color: var(--uncommon-border); }
.card-tile.row[data-rarity="rare"]      { border-color: var(--rare-border); }
.card-tile.row[data-rarity="legendary"] { border-color: var(--legendary-border); }

.card-tile.row .row-name {
  /* The grid column (`minmax(0, 1fr)`) controls the cell's max width;
     min-width: 0 here lets text-overflow: ellipsis kick in. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

.card-tile.row .row-stat {
  white-space: nowrap;
  font-weight: 700;
}

.card-tile.row .row-power  { color: var(--accent); }
.card-tile.row .row-clock  { color: #d6b75a; }
.card-tile.row .row-spirit { color: var(--spirit-blue); }

.card-tile.row .row-ability {
  /* The ability grid cell is its own flex container with the icon and
     description side-by-side. The grid column controls outer width; text
     shrinks first when the row is narrow, disappears at phone widths
     (see media query below) leaving just the icon. text-align overridden
     because this column reads left-to-right unlike the right-aligned
     numeric stats. */
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 4px;
  overflow: hidden;
  text-align: left;
}

.card-tile.row .row-ability-icon {
  flex-shrink: 0;
}

.card-tile.row .row-ability-text {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  opacity: 0.7;
  font-style: italic;
}

@media (max-width: 700px) {
  /* On phones, drop the ability description from rows — there isn't
     room for "⚡ Deal 500 damage to opponent's strongest character."
     and the card name on a ~200-px-wide deck panel. The icon alone
     still tells you what kind of ability it is. */
  .card-tile.row .row-ability-text { display: none; }

  /* Tighten everything: collapse the ability column to just the icon's
     intrinsic width, halve the gap, halve the padding. With the deck
     panel narrowed to half the screen, every saved pixel goes back to
     the name column so the card name stops being clipped to nothing. */
  .card-tile.row {
    grid-template-columns: minmax(0, 1fr) 2.4em 1.8em 2.4em min-content 2.3em;
    gap: 3px;
    padding: 0 4px;
  }

  /* And while we're here, give both panels equal width on phone-width
     screens so the deck-side rows can show their card names too. */
  .deck-builder .collection-panel,
  .deck-builder .deck-panel {
    flex: 1 1 0;
  }
}

.card-tile.row .row-count {
  font-weight: 700;
  color: var(--text-primary);
  opacity: 0.9;
}

.card-tile.row.dimmed {
  opacity: 0.4;
}

/* Card-detail modal — pattern mirrors .auth-overlay. Tap a tile to open;
   tap the backdrop or press Esc to close. */
.card-detail-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.78);
  /* Above any other modal in the app. The picker modal lives at
   * 1100; tapping a preview from within it must put the card
   * detail on top, otherwise the new modal hides behind the
   * picker and looks like a no-op (the player tap-spams and
   * stacks invisible detail modals). Card detail is always the
   * deepest "drill-in" surface so it gets the top slot. */
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

.card-detail-modal {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  max-width: 92vw;
  /* Sizing & scaling vars are declared here (rather than on `.card` alone)
   * so siblings of the card — currently the share button — can reference
   * --modal-scale to position themselves proportional to the card's size. */
  --card-w: min(
    calc(100vw - 32px),
    calc((100dvh - 240px) / 1.5)
  );
  --card-h: calc(var(--card-w) * 1.5);
  --modal-scale: calc(var(--card-w) / 110);
}

.card-detail-art {
  position: relative;
  display: inline-flex;
}

/* Sized to the card it contains; lets the close button anchor against the
   card's actual top-right corner regardless of the modal's overall size. */
.card-detail-card-host {
  display: inline-flex;
}

.card-detail-close {
  position: absolute;
  /* Slightly outside the card edge so the X is visible even when the art
     under the corner is bright. */
  top: -10px;
  right: -10px;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.65);
  color: rgba(255, 255, 255, 0.9);
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  /* Above .card (which has z-index: auto) and any inner card overlays. */
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
  transition: background 0.15s, color 0.15s;
}

.card-detail-close:hover {
  background: rgba(0, 0, 0, 0.85);
  color: rgba(255, 255, 255, 1);
}

/* Deck-analyze modal: shares the .card-detail-overlay backdrop. The modal
   itself stacks three histograms vertically with the same width. */
.deck-analyze-modal {
  position: relative;
  background: var(--bg-secondary);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 10px;
  padding: 16px;
  width: min(540px, 92vw);
  /* Cap to viewport height; the three sections share remaining space via
     flex below, so the dialog never needs to scroll. Targets the same
     vertical fill as the card-detail modal. */
  max-height: 92dvh;
  height: 92dvh;
  display: flex;
  flex-direction: column;
  gap: 14px;
  /* visible so the close button can sit outside the corner without being
     clipped by the rounded border. */
  overflow: visible;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.6);
}

.deck-analyze-modal .card-detail-close {
  /* Sit on top of the modal's rounded corner — out of the content flow
     entirely so all the modal's interior height is available for the
     three histograms. */
  top: -12px;
  right: -12px;
}

.analyze-section {
  /* Three sections share the modal's remaining height equally; the bars
     track inside each section then fills its remaining vertical space. */
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.analyze-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 13px;
}

.analyze-label {
  font-weight: 700;
  color: var(--text-primary);
}

.analyze-power  .analyze-label { color: var(--accent); }
.analyze-clock  .analyze-label { color: #d6b75a; }
.analyze-spirit .analyze-label { color: var(--spirit-blue); }

.analyze-avg {
  font-size: 12px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

.analyze-bars {
  display: flex;
  align-items: stretch;
  gap: 2px;
  /* Fill whatever vertical space the section gives us — the modal as a
     whole is sized to the viewport, so each histogram naturally grows
     to use available height instead of being capped at a fixed pixel
     value. */
  flex: 1 1 0;
  min-height: 80px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

/* Each bin is a vertical column with three stacked regions: a count slot
   at the top (always the same height regardless of bar size, so counts
   sit on a flat horizontal line), the bar track in the middle, and the
   bin label anchored at the very bottom of the track. The label sits
   inside the bar at its base when the bar is non-empty (black-on-color)
   or at the empty baseline grid (grey-on-background) when the bin has
   no values — labels for every bin land at the same y-position. */
.analyze-bin {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.analyze-bin-count {
  height: 14px;
  line-height: 14px;
  font-size: 10px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  text-align: center;
  /* Slight gap below the count so the tallest bars don't kiss the digits. */
  margin-bottom: 4px;
}

.analyze-bin-track {
  flex: 1 1 0;
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.analyze-bin-bar {
  width: 100%;
  /* Floor on the bar height so a count-of-1 bar against a peak-of-30
     deck still has room to display its black label without the digits
     poking out the top. Zero bins are not rendered (see JS), so this
     min-height never accidentally creates phantom bars. */
  min-height: 18px;
  background: rgba(255, 255, 255, 0.4);
  border-radius: 3px 3px 0 0;
  transition: height 0.15s;
}

.analyze-bin-label {
  position: absolute;
  /* Sit slightly inside the bottom of the bar — gives room for the
     digits' descenders below the baseline and a touch of breathing
     room above so a 18px-tall bar still cleanly frames a 10px digit. */
  bottom: 3px;
  left: 0;
  right: 0;
  text-align: center;
  font-size: 10px;
  line-height: 1;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: rgba(0, 0, 0, 0.85);
  z-index: 2;
  pointer-events: none;
}

.analyze-bin-label.empty {
  /* Empty bin → no bar, label lives directly on the panel background. */
  color: var(--text-secondary);
  font-weight: 400;
}

/* Per-stat colors match the in-row + in-card palette: red power, yellow
   clock, blue spirit. */
.analyze-power .analyze-bin-bar  { background: var(--accent); }
.analyze-clock .analyze-bin-bar  { background: #d6b75a; }
.analyze-spirit .analyze-bin-bar { background: var(--spirit-blue); }

.analyze-avg-value {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.analyze-power  .analyze-avg-value { color: var(--accent); }
.analyze-clock  .analyze-avg-value { color: #d6b75a; }
.analyze-spirit .analyze-avg-value { color: var(--spirit-blue); }
.analyze-support .analyze-avg-value { color: #b48cf2; }
.analyze-support .analyze-label { color: #b48cf2; }

/* Support section is fixed-height (just a label row + a thin progress bar)
   so it doesn't compete with the histograms for vertical flex space. */
.analyze-support {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.analyze-support-detail {
  font-size: 11px;
  color: var(--text-secondary);
  font-weight: 400;
  margin-left: 4px;
}

.support-bar-track {
  height: 8px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.08);
  overflow: hidden;
}

.support-bar-fill {
  height: 100%;
  background: #b48cf2;
  border-radius: 4px;
  transition: width 0.2s;
}

.analyze-empty {
  font-size: 12px;
  color: var(--text-secondary);
  font-style: italic;
  padding: 16px 0;
  text-align: center;
  flex: 1;
}

.confirm-modal {
  background: var(--bg-secondary);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 10px;
  padding: 20px 22px;
  width: min(380px, 92vw);
  display: flex;
  flex-direction: column;
  gap: 14px;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.6);
}

.confirm-modal h3 {
  margin: 0;
  font-size: 18px;
  color: var(--text-primary);
}

.confirm-modal p {
  margin: 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--text-secondary);
}

.confirm-modal .confirm-buttons {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.card-detail-modal .card {
  /* Card uses the --card-w / --card-h / --modal-scale defined on the modal
     wrapper above. The card's `.card` base rule reads --card-w for its
     width; height follows from var(--card-h, ...). All inner text scales
     linearly via --modal-scale (110px is the baseline tile width — at
     scale=1 the per-element font sizes below match the original tile
     pixel sizes). */
  cursor: default;
}

/* The detail modal is for reading the card — disable the hover-lift
   transform since nothing interactive happens on the card itself. */
.card-detail-modal .card:hover {
  transform: none;
  box-shadow: none;
}

.card-detail-modal .card               { font-size: calc(9px  * var(--modal-scale)); }
.card-detail-modal .card .card-name        { font-size: calc(10px * var(--modal-scale)); }
.card-detail-modal .card .card-power       { font-size: calc(15px * var(--modal-scale)); }
.card-detail-modal .card .card-armor       { font-size: calc(10px * var(--modal-scale)); }
.card-detail-modal .card .card-spirit      { font-size: calc(12px * var(--modal-scale)); }
.card-detail-modal .card .card-clock       { font-size: calc(12px * var(--modal-scale)); }
.card-detail-modal .card .card-ability-icon{ font-size: calc(12px * var(--modal-scale)); }
.card-detail-modal .card .card-ability-text{
  font-size: calc(8px * var(--modal-scale));
  -webkit-line-clamp: 4;
}
.card-detail-modal .card .card-tags        { font-size: calc(7px  * var(--modal-scale)); }

.card-detail-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Match the card art width so prev/next pin to the card's outer edges
     while the +/-/count cluster floats in the middle via `.cd-mid`. */
  width: var(--card-w);
  font-size: 18px;
  font-weight: 700;
  color: var(--text-primary);
}

/* Middle cluster — keeps the +/-/count tightly grouped while the parent
   row uses space-between to push prev/next to the edges. */
.card-detail-controls .cd-mid {
  display: flex;
  align-items: center;
  gap: 18px;
}

.card-detail-controls .cd-count {
  min-width: 64px;
  text-align: center;
}

.card-detail-controls button {
  width: 48px;
  height: 48px;
  font-size: 22px;
  font-weight: 700;
  border-radius: 8px;
  background: var(--accent);
  color: white;
  border: none;
  cursor: pointer;
}

.card-detail-controls button:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

/* Prev/next navigation buttons sit flush with the +/- row but read as
   secondary controls — neutral background so the accent-colored +/-
   buttons stay the primary action. The chevron glyph is larger to
   visually balance the +/- digits. */
.card-detail-controls .cd-nav {
  background: var(--bg-card);
  color: var(--text-primary);
  font-size: 28px;
  line-height: 1;
  padding-bottom: 4px;
}

/* Info block: italic flavor quote + flex-wrapping pill row of tags. Sits
   between the card art and the +/- controls so the player reads the lore
   before deciding to add or remove copies. */
.card-detail-info {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  max-width: min(420px, 90vw);
  text-align: center;
}

.card-detail-flavor {
  color: var(--text-secondary);
  font-style: italic;
  font-size: 14px;
  line-height: 1.4;
}

.card-detail-tags {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px 6px;
}

.card-detail-tag {
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.25);
  font-size: 11px;
  color: var(--text-primary);
  white-space: nowrap;
}

/* Size slider + row toggle in the filter bar */
.cb-size-slider {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-secondary);
  font-size: 13px;
}

.cb-size-slider input[type="range"] {
  width: 100px;
}

.cb-row-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-secondary);
  font-size: 13px;
  cursor: pointer;
}

.deck-list {
  align-content: start;
}

.deck-list.drop-target-active {
  background: rgba(78, 168, 222, 0.06);
  outline: 2px dashed rgba(78, 168, 222, 0.5);
  outline-offset: -8px;
}

.card-tile {
  position: relative;
  /* The grid sets `--cb-card-scale` (default 1.0); these derived vars feed
     both the outer .card-tile box and the inner .card so the entire card
     scales — image, name, stats, ability, tags — when the size slider
     moves. */
  --cb-card-scale: var(--cb-card-scale-set, 1);
  --card-w: calc(110px * var(--cb-card-scale));
  --card-h: calc(165px * var(--cb-card-scale));
  --card-fs: calc(9px * var(--cb-card-scale));
  width: var(--card-w);
  height: var(--card-h);
}

/* Inner-card text doesn't read --card-fs (those sizes are hardcoded in
   css/card.css), so we explicitly cascade --cb-card-scale through every
   readable element inside a deck-builder tile. Modal overrides come later
   with their own larger sizes. */
.card-tile .card-name        { font-size: calc(10px * var(--cb-card-scale)); }
.card-tile .card-power       { font-size: calc(15px * var(--cb-card-scale)); }
.card-tile .card-armor       { font-size: calc(10px * var(--cb-card-scale)); }
.card-tile .card-spirit      { font-size: calc(12px * var(--cb-card-scale)); }
.card-tile .card-clock       { font-size: calc(12px * var(--cb-card-scale)); }
.card-tile .card-ability-icon{ font-size: calc(12px * var(--cb-card-scale)); }
.card-tile .card-ability-text{ font-size: calc(8px  * var(--cb-card-scale)); }
.card-tile .card-tags        { font-size: calc(7px  * var(--cb-card-scale)); }

.card-tile .card {
  cursor: pointer;
}

/* Suppress the in-game hand-card lift on deck-builder tiles. The lift
   shifts the card up by 4px which uncovers the share/count overlays
   sitting on the tile and reads as flicker more than affordance. The
   z-index reset keeps the lifted card from sliding above sibling overlays
   like the share button (which lives on the tile, not the card). */
.card-tile .card:hover {
  transform: none;
  box-shadow: 0 0 0 2px rgba(78, 168, 222, 0.5);
  z-index: auto;
}

.card-tile.dimmed .card { opacity: 0.35; cursor: default; }
.card-tile.dimmed .card:hover { transform: none; box-shadow: none; }

.card-tile.dragging .card { opacity: 0.4; }

.tile-count {
  position: absolute;
  top: 4px;
  right: 4px;
  z-index: 5;
  font-size: 10px;
  font-weight: 700;
  color: var(--text-primary);
  background: rgba(0, 0, 0, 0.75);
  padding: 2px 6px;
  border-radius: 8px;
  pointer-events: none;
  letter-spacing: 0.3px;
}

.tile-count .cc-in-deck { color: var(--spirit-blue); }

.tile-share {
  position: absolute !important;
  /* Sits directly below the power badge (top-left corner) — same slot
     the .exhausted 💤 indicator uses on in-game cards. 24px clears the
     power badge's ~20px footprint plus a small gap. */
  top: 24px;
  left: 2px;
  z-index: 6;
  background: rgba(15, 25, 40, 0.92) !important;
  border-color: rgba(78, 168, 222, 0.85) !important;
  color: #7fc4f0 !important;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}

.tile-share:hover {
  background: rgba(15, 25, 40, 0.95) !important;
  border-color: rgba(140, 200, 240, 1) !important;
  color: #fff !important;
  box-shadow: 0 0 6px rgba(78, 168, 222, 0.7), 0 1px 3px rgba(0, 0, 0, 0.5) !important;
}

/* Share button on the card-detail modal — sits inside the card area, just
 * below the power badge in the top-left, matching the .tile-share position
 * on the in-grid card tiles. The power badge has a fixed `font-size: 14px`
 * (no --modal-scale override), so it stays roughly 18px tall regardless of
 * how big the modal scales the card. A fixed `top: 24px / left: 2px`
 * therefore clears the badge on any card size — same offsets as the
 * in-grid `.tile-share`. z-index: 20 beats .card:hover's z-index: 10 so
 * the button stays clickable when the cursor crosses the card. */
.card-detail-share {
  position: absolute !important;
  top: 24px;
  left: 2px;
  z-index: 20;
  background: rgba(15, 25, 40, 0.92) !important;
  border-color: rgba(78, 168, 222, 0.85) !important;
  color: #7fc4f0 !important;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}

.card-detail-share:hover {
  background: rgba(15, 25, 40, 0.95) !important;
  border-color: rgba(140, 200, 240, 1) !important;
  color: #fff !important;
  box-shadow: 0 0 6px rgba(78, 168, 222, 0.7), 0 1px 3px rgba(0, 0, 0, 0.5) !important;
}

.tile-remove {
  position: absolute;
  top: 26px;
  right: 4px;
  z-index: 5;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid rgba(0, 0, 0, 0.5);
  background: rgba(233, 69, 96, 0.85);
  color: white;
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}

.tile-remove:hover {
  background: var(--accent);
}

.deck-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.deck-panel-header {
  padding: 8px 12px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.deck-panel-header h3 {
  font-size: 14px;
  color: var(--text-primary);
  margin: 0;
  margin-right: 4px;
}

.deck-panel-header select {
  background: var(--bg-secondary);
  color: var(--text-primary);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 4px;
  padding: 4px 8px;
  font-size: 11px;
}

/* ── Pack Opening ───────────────────────────────────────────── */

.pack-opening {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* `safe center` falls back to flex-start when contents overflow,
     keeping the top (title) reachable instead of pushing it off-screen
     above the viewport. Critical on portrait phones where a Premium
     Pack's reveals stack vertically taller than the viewport. */
  justify-content: safe center;
  /* 100dvh tracks the actual visible viewport on mobile so the bottom
     buttons aren't pushed behind the browser address bar. We bound the
     element to that height (rather than min-height) and let it scroll
     internally — body uses overflow:hidden, so without our own scroll
     the Continue button can land below the viewport with no way to
     reach it. */
  height: 100dvh;
  overflow-y: auto;
  gap: 24px;
  padding: 32px;
  box-sizing: border-box;
}

.pack-opening-title {
  font-size: 28px;
  font-weight: 900;
  color: var(--text-primary);
}

/* Button row below the revealed cards. Continue stays the rightmost
 * (default-affirmative) button; an optional Add to Deck button sits
 * to its left when the caller provides a callback. The row is
 * centered as a group rather than each button being centered
 * independently. */
.pack-finish-row {
  display: flex;
  gap: 12px;
  justify-content: center;
}

.pack-cards {
  display: flex;
  gap: 16px;
  justify-content: center;
  flex-wrap: wrap;
}

.pack-card-reveal {
  width: 140px;
  height: 200px;
  border-radius: 10px;
  position: relative;
  perspective: 600px;
}

.pack-card-back,
.pack-card-front {
  position: absolute;
  inset: 0;
  border-radius: 10px;
  backface-visibility: hidden;
  transition: transform 0.5s ease;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.pack-card-back {
  background: linear-gradient(135deg, #2d1b69, #1a1a4e);
  border: 2px solid #4a3080;
  font-size: 36px;
  color: rgba(255, 255, 255, 0.15);
}

.pack-card-front {
  background: var(--bg-secondary);
  border: 2px solid rgba(255, 255, 255, 0.15);
  transform: rotateY(180deg);
  padding: 8px;
  gap: 6px;
}

.pack-card-reveal.face-down .pack-card-back { transform: rotateY(0deg); }
.pack-card-reveal.face-down .pack-card-front { transform: rotateY(180deg); }

.pack-card-reveal.revealed .pack-card-back { transform: rotateY(-180deg); }
.pack-card-reveal.revealed .pack-card-front { transform: rotateY(0deg); }

/* Revealed tiles are tappable — clicking opens the full card-detail
 * modal. Face-down tiles get the default cursor since they're
 * not interactive yet. */
.pack-card-reveal.revealed { cursor: pointer; }

.pack-card-reveal.revealed[data-rarity="uncommon"] .pack-card-front { border-color: var(--uncommon-border); }
.pack-card-reveal.revealed[data-rarity="rare"] .pack-card-front { border-color: var(--rare-border); box-shadow: 0 0 12px rgba(78, 168, 222, 0.4); }
.pack-card-reveal.revealed[data-rarity="legendary"] .pack-card-front { border-color: var(--legendary-border); box-shadow: 0 0 16px rgba(255, 215, 0, 0.5); }

.pack-reveal-art {
  width: 100%;
  height: 120px;
  object-fit: cover;
  object-position: top;
  border-radius: 6px;
}

.pack-reveal-name {
  font-size: 12px;
  font-weight: 700;
  color: var(--text-primary);
  text-align: center;
}

.pack-reveal-rarity {
  font-size: 10px;
  color: var(--text-secondary);
  text-transform: capitalize;
}

/* "New!" badge on a freshly-acquired card. The badge sits inside the
 * front face so it rotates with it during the flip; its own opacity +
 * scale transition (with a 0.25s delay) lands the fade-in halfway
 * through the 0.5s flip — the moment the front face crosses the
 * perpendicular and starts becoming visible. The cubic-bezier on
 * transform gives a small bounce so the badge "pops" rather than just
 * fades in. */
.pack-reveal-new-badge {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 5;
  background: linear-gradient(135deg, #f59e0b, #ef4444);
  color: white;
  padding: 3px 9px;
  border-radius: 4px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1px;
  text-transform: uppercase;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
  pointer-events: none;
  opacity: 0;
  transform: scale(0.7);
  transition:
    opacity 0.3s ease 0.25s,
    transform 0.3s cubic-bezier(0.5, 1.6, 0.5, 1) 0.25s;
}

.pack-card-reveal.revealed .pack-reveal-new-badge {
  opacity: 1;
  transform: scale(1);
}

/* ── Custom Pack ─────────────────────────────────────────────── */

.pack-card.custom-pack-tile {
  position: relative;
  background: linear-gradient(135deg, rgba(120, 80, 200, 0.18), rgba(60, 100, 200, 0.12));
  border-color: rgba(140, 110, 230, 0.35);
}

.pack-card.custom-pack-tile:hover {
  border-color: rgba(170, 130, 255, 0.55);
}

.custom-pack-badge {
  position: absolute;
  top: 6px;
  right: 8px;
  font-size: 9px;
  font-weight: 800;
  letter-spacing: 1px;
  padding: 2px 6px;
  background: rgba(170, 130, 255, 0.25);
  color: #d4b8ff;
  border: 1px solid rgba(170, 130, 255, 0.4);
  border-radius: 4px;
}

.pack-card.custom-pack-tile .pack-buy-btn {
  color: #5dd0ff;
  border-color: rgba(93, 208, 255, 0.4);
}

.pack-card.custom-pack-tile .pack-buy-btn:not(:disabled):hover {
  background: rgba(93, 208, 255, 0.12);
}

.pack-buy-strike {
  color: rgba(255, 255, 255, 0.45);
  text-decoration: line-through;
  text-decoration-color: rgba(255, 100, 100, 0.8);
  margin-right: 4px;
  font-weight: 600;
}

/* "90% off!" promo banner — pinned to the bottom-right corner of the
   custom-pack tile, overlapping the buy button at a jaunty angle. Shown
   only when customPack.discount is present in the config. */
.custom-pack-discount-banner {
  position: absolute;
  bottom: 8px;
  right: -14px;
  padding: 6px 18px;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.5px;
  color: #fff;
  background: linear-gradient(135deg, #ff3b6b, #ffb93a);
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: 4px;
  transform: rotate(-12deg);
  box-shadow: 0 4px 12px rgba(255, 50, 80, 0.45), 0 0 0 2px rgba(0, 0, 0, 0.25);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  pointer-events: none;
  white-space: nowrap;
  z-index: 2;
}

.gems-strike {
  color: var(--text-secondary);
  text-decoration: line-through;
  text-decoration-color: rgba(255, 100, 100, 0.8);
  margin-right: 4px;
}

.custom-pack-discount-tag {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 8px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.5px;
  color: #fff;
  background: linear-gradient(135deg, #ff3b6b, #ffb93a);
  border-radius: 4px;
  transform: rotate(-4deg);
  box-shadow: 0 2px 6px rgba(255, 50, 80, 0.35);
}

/* Own scroll container so long reveals fit on short viewports. #game / body
   both have overflow:hidden — without this, anything below the fold is lost
   and the Confirm button becomes unreachable. */
.custom-pack-view {
  flex: 1;
  min-height: 0;
  width: 100%;
  padding: 24px;
  overflow-y: auto;
  /* Reserve the scrollbar's horizontal space even when content doesn't
     overflow yet. Otherwise, shortening the viewport adds a scrollbar,
     eats ~15px of width, and can push the flex row past its fit
     threshold — three reveal cards suddenly wrap to two, which looks
     like "the cards got narrower when I resized the height". */
  scrollbar-gutter: stable;
}

.custom-pack-panel {
  max-width: 1500px;
  width: 100%;
  margin: 0 auto;
  padding: 32px;
  background: rgba(15, 52, 96, 0.25);
  border: 1px solid rgba(170, 130, 255, 0.2);
  border-radius: 16px;
  text-align: center;
}

/* Keep the Confirm bar visible as the card list scrolls past. Sticks to the
   scroll container's bottom edge. */
.custom-pack-panel .custom-pack-footer {
  position: sticky;
  bottom: 0;
  background: rgba(22, 33, 62, 0.95);
  backdrop-filter: blur(8px);
  z-index: 5;
}

.custom-pack-panel.busy,
.custom-pack-panel.error {
  max-width: 440px;
}

.custom-pack-title {
  font-size: 28px;
  font-weight: 800;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--text-primary);
  margin-bottom: 8px;
}

.custom-pack-subtitle {
  font-size: 14px;
  color: var(--text-secondary);
  margin-bottom: 20px;
}

.custom-pack-cost-line {
  font-size: 18px;
  margin: 16px 0;
  color: var(--text-primary);
}

.custom-pack-cost-line .gems {
  color: #5dd0ff;
  font-weight: 700;
}

.custom-pack-description {
  font-size: 14px;
  color: var(--text-secondary);
  line-height: 1.6;
  max-width: 520px;
  margin: 0 auto 24px;
}

.custom-pack-hint-label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 520px;
  margin: 0 auto 20px;
  text-align: left;
  font-size: 12px;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 1px;
}

.custom-pack-hint-input {
  font-family: inherit;
  font-size: 14px;
  color: var(--text-primary);
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  padding: 10px 12px;
  resize: vertical;
  min-height: 44px;
  letter-spacing: 0;
  text-transform: none;
}

.custom-pack-hint-input:focus {
  outline: none;
  border-color: rgba(170, 130, 255, 0.55);
}

.custom-pack-hint-sub {
  font-size: 11px;
  color: var(--text-muted, rgba(255,255,255,0.4));
  text-transform: none;
  letter-spacing: 0;
}

.custom-pack-actions {
  display: flex;
  justify-content: center;
  gap: 12px;
}

.custom-pack-slots {
  display: flex;
  gap: 18px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 24px;
}

.custom-pack-slot {
  width: 220px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.custom-pack-slot .slot-frame {
  width: 100%;
  aspect-ratio: 5/7;
  border: 1px dashed rgba(170, 130, 255, 0.35);
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: rgba(15, 52, 96, 0.15);
  padding: 12px;
  transition: border 0.3s;
}

.custom-pack-slot.done .slot-frame {
  border: 1px solid rgba(170, 230, 140, 0.55);
  background: rgba(60, 120, 80, 0.1);
}

.slot-index {
  font-size: 32px;
  font-weight: 900;
  color: rgba(170, 130, 255, 0.4);
  letter-spacing: 2px;
}

.slot-name {
  font-size: 14px;
  font-weight: 700;
  color: var(--text-primary);
  text-align: center;
  margin-top: 8px;
}

.slot-rarity {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  margin-top: 6px;
  padding: 2px 8px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.06);
}

.slot-rarity.rarity-common { color: #cccccc; }
.slot-rarity.rarity-uncommon { color: #5dd0a7; }
.slot-rarity.rarity-rare { color: #5dd0ff; }
.slot-rarity.rarity-legendary { color: var(--gold); }

.slot-description {
  font-size: 11px;
  font-style: italic;
  color: var(--text-secondary);
  opacity: 0.8;
  line-height: 1.35;
  margin-top: 6px;
  text-align: center;
  padding: 0 6px;
}

.slot-status {
  font-size: 12px;
  color: var(--text-secondary);
  font-style: italic;
  min-height: 16px;
}

.slot-theme {
  font-size: 11px;
  color: var(--text-secondary);
  opacity: 0.75;
  margin-top: 4px;
  line-height: 1.3;
}

.custom-pack-spinner {
  width: 48px;
  height: 48px;
  border: 3px solid rgba(170, 130, 255, 0.25);
  border-top-color: #d4b8ff;
  border-radius: 50%;
  margin: 32px auto 16px;
  animation: custom-pack-spin 1s linear infinite;
}

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

.custom-pack-busy-label {
  color: var(--text-secondary);
  font-size: 14px;
  margin-bottom: 16px;
}

/* `--reveal-scale` is the single knob that controls reveal-card visual size.
   Everything that has to match the card width (zoomed card preview, details
   panel below it) reads from this variable, so scale changes stay
   consistent. Base card is 130×215; visual width is 130 × --reveal-scale. */
.custom-pack-panel.reveal {
  --reveal-scale: 2.3;
}

.custom-pack-reveal-row {
  display: flex;
  gap: 32px;
  justify-content: center;
  flex-wrap: wrap;
  margin: 24px 0;
}

.custom-pack-reveal-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 18px;
  background: rgba(15, 52, 96, 0.2);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 12px;
  transition: border 0.2s, background 0.2s;
}

.custom-pack-reveal-card.keeping {
  border-color: rgba(170, 130, 255, 0.55);
  background: rgba(120, 80, 200, 0.12);
}

/* Bigger + taller-than-default aspect ratio for the reveal screen so the AI art
   isn't cropped. Base card is 130x182 (≈1.4 ratio); we bump to 130x215 (≈1.65)
   and zoom by --reveal-scale, giving a visual ~300-340px card.

   Explicitly reset --card-w/h/fs to insulate from the in-game responsive
   overrides in main.css (which shrink cards on short desktops and phones
   to fit the game board). Without this, a phone viewport collapses the
   card to 58×81 and the reveal preview renders as a tiny squashed box. */
.custom-pack-reveal-card .card.reveal-card-preview {
  --card-w: 130px;
  --card-h: 215px;
  --card-fs: 10px;
  zoom: var(--reveal-scale);
  height: 215px;
  cursor: pointer;
  transition: filter 0.2s, transform 0.2s;
}

.custom-pack-reveal-card .card.reveal-card-preview:hover {
  filter: brightness(1.08);
}

.custom-pack-reveal-card.keeping .card.reveal-card-preview {
  outline: 2px solid rgba(170, 130, 255, 0.7);
  outline-offset: 4px;
  border-radius: 8px;
}

/* Reveal cards have plenty of vertical space — drop the in-game truncation
   on ability text and card name so long content renders in full instead of
   being cut off with an ellipsis. */
.custom-pack-reveal-card .card-ability-text {
  display: block;
  -webkit-line-clamp: unset;
  overflow: visible;
}

.custom-pack-reveal-card .card-name {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  line-height: 1.15;
}

/* Filmstrip only visible when the card is being kept (preserves layout height) */
.custom-pack-reveal-card:not(.keeping) .custom-pack-filmstrip {
  visibility: hidden;
}

/* Narrow viewports: three layers of padding (view + panel + card) steal
   ~150px per side at desktop sizing, crushing the reveal cards. Tighten
   all three plus the row gap so the card takes most of the width. */
@media (max-width: 700px) {
  .custom-pack-view {
    padding: 8px;
  }
  .custom-pack-panel {
    padding: 16px 10px;
  }
  .custom-pack-reveal-row {
    gap: 16px;
    margin: 12px 0;
  }
  .custom-pack-reveal-card {
    padding: 10px 6px;
  }
}

/* Phone-sized single-column layout: bump the card scale so it fills more
   of the reclaimed width. Upper cap in the 430px range covers modern
   iPhones (15 Pro Max at 430px); the next breakpoint below keeps narrow
   phones at the desktop default scale so the card doesn't overflow. */
@media (max-width: 500px) {
  .custom-pack-panel.reveal {
    --reveal-scale: 2.6;
  }
}

@media (max-width: 400px) {
  .custom-pack-panel.reveal {
    --reveal-scale: 2.3;
  }
}

/* ── Reveal card details panel ──────────────────────────────── */

.reveal-card-details {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  /* Match the zoomed card's visual width so both stay aligned at any
     scale. Fixed 300px used to collide with the row-wrap threshold on
     mid-width desktops, causing cards to re-wrap when a scrollbar
     appeared. */
  width: calc(130px * var(--reveal-scale));
  max-width: 100%;
  margin-top: 10px;
  text-align: center;
}

.details-rarity {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 2px;
  text-transform: uppercase;
  padding: 2px 10px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.05);
}

.details-rarity.rarity-common { color: #cccccc; }
.details-rarity.rarity-uncommon { color: #5dd0a7; }
.details-rarity.rarity-rare { color: #5dd0ff; }
.details-rarity.rarity-legendary { color: var(--gold); }

.details-ability {
  font-size: 12px;
  color: var(--text-primary);
  line-height: 1.35;
}

.details-ability-type {
  text-transform: uppercase;
  font-size: 10px;
  letter-spacing: 1px;
  color: var(--text-secondary);
}

.details-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  justify-content: center;
}

.details-tag {
  font-size: 10px;
  letter-spacing: 0.5px;
  text-transform: lowercase;
  color: var(--text-secondary);
  background: rgba(255, 255, 255, 0.04);
  padding: 2px 8px;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.details-flavor {
  font-size: 11px;
  font-style: italic;
  color: var(--text-secondary);
  opacity: 0.8;
  line-height: 1.35;
  margin-top: 2px;
}

.custom-pack-filmstrip {
  display: flex;
  gap: 8px;
  margin-top: 6px;
}

.filmstrip-thumb {
  width: 68px;
  height: 68px;
  padding: 0;
  border: 2px solid rgba(255, 255, 255, 0.1);
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.3);
  cursor: pointer;
  overflow: hidden;
  transition: border 0.15s;
}

.filmstrip-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.filmstrip-thumb:hover {
  border-color: rgba(170, 130, 255, 0.55);
}

.filmstrip-thumb.selected {
  border-color: #d4b8ff;
  box-shadow: 0 0 0 2px rgba(170, 130, 255, 0.4);
}

.custom-pack-keep-toggle {
  margin-top: 4px;
  padding: 6px 20px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: var(--text-secondary);
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.15s;
}

.custom-pack-keep-toggle:hover {
  background: rgba(255, 255, 255, 0.08);
}

.custom-pack-keep-toggle.keeping {
  background: rgba(170, 130, 255, 0.18);
  border-color: rgba(170, 130, 255, 0.55);
  color: #d4b8ff;
}

.custom-pack-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}

.custom-pack-summary {
  display: flex;
  gap: 24px;
  font-size: 14px;
  color: var(--text-secondary);
}

.custom-pack-summary strong {
  color: var(--text-primary);
}

.custom-pack-cost strong {
  color: #5dd0ff;
}

/* ── Reveal card action row ─────────────────────────────────── */

.custom-pack-reveal-actions {
  display: flex;
  gap: 8px;
  margin-top: 4px;
  align-items: center;
}

/* ── Share button ──────────────────────────────────────────── */

.share-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 12px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  background: rgba(78, 168, 222, 0.14);
  border: 1px solid rgba(78, 168, 222, 0.4);
  color: var(--spirit-blue);
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.15s;
}

.share-btn:hover {
  background: rgba(78, 168, 222, 0.25);
  border-color: rgba(78, 168, 222, 0.7);
}

.share-btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
}

.share-btn-icon .share-icon-svg {
  display: block;
}

.share-btn.compact {
  padding: 4px 6px;
  font-size: 11px;
  letter-spacing: 0;
}

/* The compact (icon-only) share-icon scales down a touch so it sits well
 * in the small overlay buttons on tile + card-detail. */
.share-btn.compact .share-icon-svg {
  width: 14px;
  height: 14px;
}

.collection-card .share-btn.compact {
  flex-shrink: 0;
  margin-right: 6px;
}

/* ── Toast ────────────────────────────────────────────────── */

.toast {
  position: fixed;
  bottom: 32px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(22, 33, 62, 0.96);
  color: var(--text-primary);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 8px;
  padding: 10px 20px;
  font-size: 13px;
  font-weight: 500;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  z-index: 2000;
  pointer-events: none;
  transition: opacity 0.4s;
}

.toast-success { border-color: rgba(34, 197, 94, 0.5); }
.toast-error { border-color: rgba(233, 69, 96, 0.5); }
.toast.fade-out { opacity: 0; }

/* ── Campaign list / stages ─────────────────────────────────── */

.campaign-list-screen,
.campaign-stages-screen {
  justify-content: flex-start;
  gap: 24px;
  /* Allow stage / campaign tiles to scroll on viewports shorter than the
   * total tile stack — otherwise mobile players can't reach the bottom
   * stages. */
  overflow-y: auto;
}

.campaign-header {
  width: 100%;
  max-width: 760px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.campaign-back {
  /* Lives on its own row above the title so a wide title can't overlap it
   * on narrow viewports. */
  align-self: flex-start;
  font-size: 13px;
  padding: 6px 14px;
}

.campaign-subtitle {
  color: var(--text-secondary);
  font-size: 14px;
  text-align: center;
  max-width: 600px;
  line-height: 1.5;
}

.campaign-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px;
  width: 100%;
  max-width: 760px;
}

.stage-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 14px;
  width: 100%;
  max-width: 760px;
}

/* Shared tile base — campaign and stage tiles use the same chrome. */
.campaign-tile,
.stage-tile {
  position: relative;
  background: var(--bg-secondary);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 10px;
  padding: 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  transition: all 0.18s;
}

.campaign-tile.clickable,
.stage-tile.clickable {
  cursor: pointer;
}

.campaign-tile.clickable:hover,
.stage-tile.clickable:hover {
  transform: translateY(-2px);
  border-color: rgba(255, 255, 255, 0.3);
}

/* Available — the obvious next tap. Uses the campaign blue palette so it
 * reads as inviting rather than alarming (the red accent is reserved for
 * destructive / urgent affordances like End Turn). */
.campaign-tile.available,
.stage-tile.available {
  background: linear-gradient(180deg, rgba(78, 168, 222, 0.20) 0%, var(--bg-secondary) 80%);
  border-color: var(--spirit-blue);
  box-shadow: 0 4px 18px rgba(78, 168, 222, 0.25);
}
.campaign-tile.available:hover,
.stage-tile.available:hover {
  box-shadow: 0 6px 22px rgba(78, 168, 222, 0.45);
}

/* Completed — checkmark, dimmer, still clickable for replay. */
.campaign-tile.completed,
.stage-tile.completed {
  opacity: 0.7;
}
.campaign-tile.completed:hover,
.stage-tile.completed:hover {
  opacity: 0.9;
}

/* Locked — greyed out, not clickable. */
.campaign-tile.locked,
.stage-tile.locked {
  opacity: 0.4;
  filter: grayscale(0.6);
  cursor: not-allowed;
}

/* Blur the description of a locked stage so it reads as "spoiler-
 * gated content" — the name + stage number stay readable so the
 * player can see their path forward, but the prose hint about what
 * happens next is hidden until they earn the unlock. Campaign tiles
 * intentionally don't blur: the player needs to know what each
 * campaign is about to pick which one to play. */
.stage-tile.locked .stage-tile-description {
  filter: blur(4px);
  user-select: none;
}

.campaign-tile-name,
.stage-tile-name {
  font-size: 18px;
  font-weight: 700;
  color: var(--text-primary);
}

.campaign-tile-description,
.stage-tile-description {
  font-size: 13px;
  color: var(--text-secondary);
  line-height: 1.45;
}

.campaign-tile-progress {
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: auto;
  font-weight: 600;
  letter-spacing: 0.4px;
}

.stage-tile-number {
  position: absolute;
  top: 12px;
  right: 14px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-secondary);
  opacity: 0.5;
}

.lock-badge {
  position: absolute;
  top: 12px;
  right: 14px;
  font-size: 18px;
  opacity: 0.7;
}

.completion-badge {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(34, 197, 94, 0.85);
  color: white;
  border-radius: 50%;
  font-size: 14px;
  font-weight: 700;
  box-shadow: 0 2px 6px rgba(34, 197, 94, 0.3);
}

/* When a stage tile has both a number and a completion badge, hide the
 * number so the badge has the corner to itself. */
.stage-tile.completed .stage-tile-number {
  display: none;
}

/* ── Tournament widget on main menu ──────────────────────────── */

.tournament-widget {
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* Match the pack-shop column width (max-width: 420px). Keeps the
   * widget vertically aligned with the card-shop tiles on desktop
   * and gives the menu a consistent right-edge on phone aspect
   * ratios where pack-shop stacks below the widget.
   *
   * Bottom margin separates the widget from the YOUR DECKS / CARD
   * SHOP block below; the parent .menu-screen gap alone reads as too
   * tight there. Top margin is 0 because .menu-buttons already adds
   * room directly above. */
  width: min(420px, 92vw);
  margin: 0 auto 8px;
  padding: 10px 14px;
  background: linear-gradient(180deg, rgba(70, 40, 130, 0.45) 0%, rgba(40, 20, 80, 0.5) 100%);
  border: 1px solid rgba(139, 92, 246, 0.45);
  border-radius: 8px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
}
/* Each widget row is its own left-↔-right pair. Three rows total
 * for the active-tournament case (title/countdown, rank/details,
 * deck/play); two rows for the claim-prize case (no deck picker). */
.tournament-widget-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.tournament-widget-name {
  font-weight: 700;
  color: #f4f0ff;
  font-size: 15px;
}
.tournament-widget-countdown {
  font-size: 12px;
  color: #d6c8f5;
  font-variant-numeric: tabular-nums;
}
.tournament-widget-rank {
  color: #efe6ff;
  font-size: 13px;
}

/* Tighten the primary CTAs inside the widget. The default `.menu-btn`
 * padding (10px 24px) makes the widget cumulatively tall on phones —
 * with three rows that's ~150px of menu real estate before the deck
 * list shows up. Trim height + horizontal padding so the widget feels
 * like a HUD chip rather than a full menu section. */
.tournament-widget .menu-btn {
  padding: 6px 12px;
  font-size: 13px;
}

/* Header row's right-hand group: Details button + countdown. The
 * Details secondary CTA lives up here (instead of in the same row as
 * Play Match) so a phone thumb aimed at one button can't accidentally
 * tap the other. Gap is wider than other widget rows so the countdown
 * doesn't read as a label on the Details button. */
.tournament-widget-header-actions {
  display: flex;
  align-items: center;
  gap: 24px;
}

/* Bottom block under the title row: rank + deck picker stacked on the
 * left, a tall Play Match button on the right that spans both lines.
 * The Play Match button gets more visual weight than the other small
 * CTAs in the widget while keeping the widget's overall height the
 * same — its size comes from the height of the left stack, not from
 * extra rows. */
.tournament-widget-bottom {
  display: flex;
  align-items: stretch;
  gap: 12px;
}
.tournament-widget-bottom-left {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
}
.tournament-widget-play-match.menu-btn {
  /* Wider than the default trimmed-CTA padding so the tall button
   * reads as the primary action of the widget without being so big
   * that it dominates. Height comes from align-items: stretch on
   * the bottom block. */
  padding: 6px 20px;
  font-size: 14px;
  flex-shrink: 0;
}

/* ── Tournament list view ───────────────────────────────────── */

.tournament-list .tournament-header,
.tournament-detail .tournament-header,
.tournament-ladder-view .tournament-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: min(720px, 96vw);
  margin: 16px auto 8px;
}
.tournament-cards {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: min(720px, 96vw);
  margin: 0 auto;
}
.tournament-card {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 10px;
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.tournament-card-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
.tournament-card-title {
  font-size: 18px;
  font-weight: 700;
  color: var(--text-primary);
}
.tournament-state-badge {
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 12px;
  font-weight: 700;
  text-transform: uppercase;
}
.tournament-state-badge.active { background: rgba(139, 92, 246, 0.25); color: #d8c5ff; }
.tournament-state-badge.ended { background: rgba(255, 255, 255, 0.1); color: #aaa; }
.tournament-card-desc {
  color: var(--text-secondary);
  font-size: 13px;
  line-height: 1.4;
}
.tournament-card-rank {
  font-size: 14px;
  color: #d6c8f5;
}
.tournament-card-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 4px;
}

/* Deck picker on tournament cards / detail / main-menu widget.
 * Selecting from this dropdown saves a per-tournament-def preference
 * (localStorage); the Play Match button reads it. Lets the player
 * keep a Pauper deck for the Pauper League and a different active
 * deck for skirmish / Weekly Ladder without re-toggling each session.
 *
 * Sits inline on the same row as the rank line (or the widget's
 * player-count line) to keep the card compact. */
.tournament-deck-picker {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text-secondary);
}
.tournament-deck-picker select {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-primary);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 6px;
  padding: 3px 6px;
  font-size: 13px;
  min-width: 0;
  max-width: 160px;
}
.tournament-deck-picker-label {
  flex-shrink: 0;
}

/* Shared rank-row container — the rank line is left-aligned, the
 * deck picker right-aligned. Wraps to two lines on narrow viewports
 * so neither half gets clipped. Used by tournament card + detail
 * view; the main-menu widget has its own row pattern. */
.tournament-card-rank-row,
.tournament-detail-rank-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.tournament-card-countdown {
  font-size: 13px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.tournament-card-actions {
  display: flex;
  gap: 6px;
}
.tournament-empty {
  color: var(--text-secondary);
  text-align: center;
  padding: 32px 16px;
}

/* ── Tournament detail view ──────────────────────────────────── */

.tournament-detail-desc {
  width: min(720px, 96vw);
  margin: 4px auto 12px;
  color: var(--text-secondary);
  font-size: 14px;
}
.tournament-detail-meta {
  display: flex;
  justify-content: space-between;
  width: min(720px, 96vw);
  margin: 0 auto 12px;
  align-items: center;
}
.tournament-detail-countdown {
  font-size: 15px;
  color: #d6c8f5;
  font-variant-numeric: tabular-nums;
}
.tournament-detail-rank {
  font-size: 14px;
  color: var(--text-primary);
}
.tournament-detail-cta {
  display: flex;
  align-items: center;
  gap: 10px;
  width: min(720px, 96vw);
  margin: 0 auto 16px;
}
.tournament-detail-blocked {
  color: #e94560;
  font-size: 12px;
}
.tournament-detail-section {
  width: min(720px, 96vw);
  margin: 18px auto 0;
}
.tournament-ladder-preview {
  background: rgba(0, 0, 0, 0.2);
  border-radius: 8px;
  padding: 10px;
  margin-bottom: 8px;
}
.tournament-ladder-rows {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.tournament-ladder-row {
  display: grid;
  /* rank · avatar · name · record. Avatar is a fixed 36px square
   * (the cell uses min-content so the actual avatar element's
   * `width` controls visible size). 36 fits 3+ rows on a phone
   * screen at the row height set by `padding: 8px` below. */
  grid-template-columns: 50px min-content 1fr auto;
  gap: 8px;
  align-items: center;
  padding: 8px 10px;
  border-radius: 4px;
  font-size: 13px;
}
.tournament-ladder-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.4);
  overflow: hidden;
  cursor: pointer;
  flex: 0 0 auto;
}
.tournament-ladder-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;  /* same crop as the settings preview */
  display: block;
}
.tournament-ladder-avatar:hover {
  box-shadow: 0 0 0 2px rgba(196, 181, 253, 0.7);
}
.tournament-ladder-avatar:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px #c4b5fd;
}
.tournament-ladder-avatar-empty {
  cursor: default;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-secondary);
  background: rgba(255, 255, 255, 0.06);
  border: 1px dashed rgba(255, 255, 255, 0.18);
}
.tournament-ladder-row.is-me .tournament-ladder-avatar {
  box-shadow: 0 0 0 2px #c4b5fd;
}
/* Stronger "this is you" styling. The previous .18 alpha purple was
 * too subtle to read as a distinct highlight against the bot rows
 * (which already carry a muted color). Border + brighter background
 * + bolder text + accent for the rank make the row pop. */
.tournament-ladder-row.is-me {
  background: rgba(139, 92, 246, 0.32);
  border-left: 3px solid #c4b5fd;
  color: #fff;
  font-weight: 700;
  padding-left: 7px; /* compensate for the 3px border so text doesn't shift */
}
.tournament-ladder-row.is-me .tournament-ladder-rank {
  color: #fff;
}
.tournament-ladder-row.is-me .tournament-ladder-record {
  color: #efe6ff;
}
.tournament-ladder-row.is-bot {
  color: var(--text-secondary);
}
.tournament-ladder-rank {
  color: #d6c8f5;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.tournament-ladder-bot-tag {
  display: inline-block;
  font-size: 10px;
  padding: 1px 5px;
  background: rgba(255, 255, 255, 0.1);
  border-radius: 3px;
  margin-left: 6px;
  color: var(--text-secondary);
  font-weight: 400;
}
.tournament-ladder-record {
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
}
.tournament-ladder-gap {
  text-align: center;
  padding: 6px 0;
  color: var(--text-secondary);
  letter-spacing: 4px;
}
.tournament-prizes-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.tournament-prizes-table th,
.tournament-prizes-table td {
  text-align: left;
  padding: 6px 12px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.tournament-prizes-table th {
  color: var(--text-secondary);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.tournament-prizes-table.per-match {
  margin-top: 12px;
}

/* ── Tournament ladder paginated view ────────────────────────── */

.tournament-ladder-view .tournament-ladder-rows {
  width: min(720px, 96vw);
  margin: 0 auto;
}
.tournament-ladder-loading {
  text-align: center;
  padding: 16px;
  color: var(--text-secondary);
  font-size: 13px;
}
.tournament-ladder-sentinel {
  height: 1px;
}

/* ── Game-over modal: tournament rank line ───────────────────── */

.game-over-panel .tournament-rank {
  font-size: 16px;
  color: #d6c8f5;
  margin: 8px 0 4px;
}
.game-over-panel .tournament-rank strong {
  color: #f4f0ff;
}
.game-over-panel .tournament-rank-note {
  font-size: 12px;
  color: var(--text-secondary);
  font-style: italic;
}

/* ── Tournament ladder: sectioned layout ──────────────────────── */

.tournament-ladder-view .menu-title {
  margin-bottom: 8px;
}
.ladder-jump-bar {
  width: min(720px, 96vw);
  margin: 0 auto 12px;
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.ladder-section {
  width: min(720px, 96vw);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ladder-section + .ladder-section { margin-top: 0; }
.ladder-section-divider {
  width: min(720px, 96vw);
  margin: 6px auto;
  text-align: center;
  color: var(--text-secondary);
  letter-spacing: 6px;
  font-size: 18px;
  opacity: 0.6;
}
.ladder-section-sentinel {
  width: min(720px, 96vw);
  margin: 0 auto;
  height: 1px;
}

/* ── Scrollable menu-screen variant ───────────────────────────
 *
 * The default .menu-screen uses justify-content: center on a 100dvh
 * flex column, which centers content vertically when it fits and
 * SPLITS overflow above/below when it doesn't (so the top of the
 * page is clipped off-screen on short viewports — that's why the
 * back button vanished on a 1080p browser). The .is-scrollable
 * variant pins content to the top of the screen and lets the
 * container scroll, matching the standard document flow players
 * expect on desktop and mobile.
 *
 * Applied to tournament-list, tournament-detail, tournament-ladder.
 * Inherits the menu-screen background and padding-x but drops the
 * default vertical centering and the gap (because sticky headers
 * own their own spacing).
 */
.menu-screen.is-scrollable {
  justify-content: flex-start;
  align-items: stretch;
  /* Body / html have `overflow: hidden` (so the in-game board can't
   * scroll), so the menu-screen must do its own scrolling. With only
   * `min-height: 100dvh` the container grows with content and
   * overflow-y: auto never activates. Pinning the height to 100dvh
   * caps the container at the viewport, which is what triggers the
   * internal scrollbar when content is taller. */
  height: 100dvh;
  overflow-y: auto;
  /* Outer padding is 0 so a sticky header can span edge-to-edge.
   * Inner sections (.tournament-detail-body, .tournament-cards,
   * .ladder-section) have their own max-width caps. */
  padding: 0;
  gap: 0;
}

/* ── Tournament detail sticky header ──────────────────────────
 *
 * Pinned at top so Back / Play / Claim stay reachable while the
 * player scrolls through prizes. Grid layout (3 columns) so the
 * title stays centered without depending on the side buttons'
 * widths. On narrow viewports the title font shrinks rather than
 * the layout reflowing — keeps the bar predictable. */
.tournament-sticky-header {
  position: sticky;
  top: 0;
  z-index: 20;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  width: 100%;
  background: var(--bg-primary);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
}
.tournament-sticky-title {
  font-size: clamp(15px, 4vw, 22px);
  font-weight: 800;
  text-align: center;
  letter-spacing: 1px;
  color: var(--text-primary);
  /* Ellipsize on tight widths so the bar doesn't wrap. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.tournament-sticky-back,
.tournament-sticky-cta .menu-btn,
.tournament-sticky-cta .menu-btn-sm {
  white-space: nowrap;
}
.tournament-sticky-cta {
  display: flex;
  gap: 6px;
  justify-self: end;
}
/* Shrink the Play button slightly so it fits comfortably in the bar
 * alongside the back button and title. */
.tournament-sticky-cta .menu-btn {
  padding: 6px 14px;
  font-size: 13px;
  box-shadow: none;
}

/* Body of the detail screen — sits below the sticky header. Reuses
 * the .tournament-detail-* rules already in this file for the
 * inner sections; this wrapper just sets the max-width and the
 * top padding. */
.tournament-detail-body {
  width: min(720px, 96vw);
  margin: 0 auto;
  padding: 16px 0 48px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.tournament-detail-body .tournament-detail-desc,
.tournament-detail-body .tournament-detail-meta,
.tournament-detail-body .tournament-detail-section {
  width: 100%;
  margin-left: 0;
  margin-right: 0;
}

/* Scrollable variants of the list + ladder pages also need their
 * back button and content to sit inside a max-width column with
 * sensible top padding — they share the same modifier but don't
 * have a sticky header. */
.tournament-list.is-scrollable .menu-title,
.tournament-list.is-scrollable .campaign-back,
.tournament-ladder-view.is-scrollable .menu-title,
.tournament-ladder-view.is-scrollable .campaign-back {
  width: min(720px, 96vw);
  align-self: center;
}
.tournament-list.is-scrollable .campaign-back,
.tournament-ladder-view.is-scrollable .campaign-back {
  margin-top: 16px;
}
.tournament-list.is-scrollable .menu-title,
.tournament-ladder-view.is-scrollable .menu-title {
  margin: 8px auto 12px;
  text-align: center;
}

/* ── Ladder body (below sticky header on the full-ladder page) ──
 *
 * Mirrors .tournament-detail-body — sets a max-width column, top
 * padding, and bottom breathing room so the last loaded row isn't
 * flush against the scroll-edge. Sections within keep their own
 * width:min(720px,96vw) rules; this wrapper just centers them and
 * adds spacing. */
.ladder-body {
  width: 100%;
  padding: 16px 0 48px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* ── Prize tier highlight ────────────────────────────────────
 *
 * Marks the row the player would receive RIGHT NOW given their
 * current rank — mirrors `resolveFinalPrize` server-side so the
 * highlight matches what would actually be awarded if the
 * tournament ended at this moment. Same purple band the ladder
 * uses for "is-me" rows so the visual language is consistent. */
.tournament-prizes-table tr.is-my-tier {
  background: rgba(139, 92, 246, 0.28);
  outline: 1px solid rgba(196, 181, 253, 0.55);
}
.tournament-prizes-table tr.is-my-tier td {
  color: #fff;
  font-weight: 600;
}
.prize-you-are-here {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 8px;
  border-radius: 10px;
  background: #c4b5fd;
  color: #1a1428;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  vertical-align: 1px;
}

/* ── Tournament prize claim modal ────────────────────────────
 *
 * Reuses the .game-over-overlay + .game-over-panel structure
 * (and the per-currency .coins-earned / .gems-earned /
 * .tickets-earned color classes so the gold/blue/green palette
 * matches the per-match victory screen). The tournament-specific
 * pieces below add a purple banner up top and a card-reward
 * line in the panel's neutral light color. */
.game-over-panel.tournament-prize-panel {
  border: 1px solid rgba(139, 92, 246, 0.55);
  background: linear-gradient(180deg, rgba(40, 24, 70, 0.95) 0%, var(--bg-secondary) 100%);
  position: relative;
  overflow: hidden;
}
.tournament-prize-banner {
  display: inline-block;
  margin: 0 auto 10px;
  padding: 4px 14px;
  border-radius: 16px;
  background: linear-gradient(135deg, #8b5cf6, #c4b5fd);
  color: #1a1428;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}
.game-over-panel .cards-earned {
  color: #c4b5fd;
  font-size: 18px;
  font-weight: 700;
  margin: 4px 0 8px;
}
.game-over-panel .prize-no-reward {
  color: var(--text-secondary);
  font-style: italic;
  margin: 16px 0;
}

/* ── Prize table currency color-coding ────────────────────────
 *
 * Mirrors the per-currency classes used by the game-over modal so
 * the same hue means the same thing everywhere: coins gold, gems
 * cyan, cards purple, tickets green. Each piece in the comma-
 * separated list inside a Reward cell gets its own span; the
 * inline structure stays a single visual line. Bold weight makes
 * the colored text legible against the dark panel without needing
 * larger sizes. */
.tournament-prizes-table .prize-coins   { color: var(--gold); font-weight: 600; }
.tournament-prizes-table .prize-gems    { color: #5dd0ff;    font-weight: 600; }
.tournament-prizes-table .prize-cards   { color: #c4b5fd;    font-weight: 600; }
.tournament-prizes-table .prize-tickets { color: #5cd687;    font-weight: 600; }
.tournament-prizes-table .prize-empty   { color: var(--text-secondary); }
/* On the highlighted "You are here" row the dark purple background
 * washes out the colored text. Bump saturation + opacity so each
 * currency stays distinguishable from the row backdrop. */
.tournament-prizes-table tr.is-my-tier .prize-coins   { color: #ffd96b; }
.tournament-prizes-table tr.is-my-tier .prize-gems    { color: #8be4ff; }
.tournament-prizes-table tr.is-my-tier .prize-cards   { color: #e0d4ff; }
.tournament-prizes-table tr.is-my-tier .prize-tickets { color: #82e6a0; }

/* ── Settings screen ─────────────────────────────────────────
 *
 * Uses .menu-screen.is-scrollable so the settings body scrolls
 * inside the screen (the global body has overflow:hidden, same
 * pattern as the tournament views). Sections sit in a single
 * max-width column so forms read naturally on phone and desktop.
 */
.settings-screen .menu-title {
  margin: 24px auto 12px;
  width: min(720px, 96vw);
  text-align: center;
}
.settings-screen .campaign-back {
  align-self: flex-start;
  margin: 16px 0 0 max(2vw, calc((100% - 720px) / 2));
}
.settings-body {
  width: min(720px, 96vw);
  margin: 0 auto;
  padding-bottom: 64px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}
.settings-section {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  padding: 16px 18px;
}
.settings-section h2.section-title {
  margin-bottom: 10px;
}
.settings-section-sub {
  color: var(--text-secondary);
  font-size: 13px;
  margin-bottom: 10px;
}
.settings-meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 14px;
}
.settings-meta-label {
  color: var(--text-secondary);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-right: 6px;
}
.settings-stack {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: stretch;
  max-width: 360px;
}
/* Wider, comfortable inputs across the settings forms. Native input
 * defaults render very narrow on desktop (~150px), which looked
 * cramped against the surrounding form labels and section padding.
 * width:100% inside .settings-stack (max-width 360px) fills the
 * column without sprawling on wide viewports. The username row
 * uses .inline-form, so its input gets an explicit min-width. */
.settings-section .settings-stack input {
  width: 100%;
  padding: 8px 12px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 6px;
  color: var(--text-primary);
  font-size: 14px;
}
.settings-section .inline-form input[type="text"] {
  width: min(280px, 100%);
  padding: 8px 12px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 6px;
  color: var(--text-primary);
  font-size: 14px;
}
.settings-actions-row {
  display: flex;
  gap: 10px;
  margin-top: 12px;
}
.settings-feedback {
  margin-top: 8px;
  font-size: 13px;
  min-height: 18px;  /* reserve space so the form doesn't reflow when feedback appears */
}
.settings-feedback.success { color: #5cd687; }
.settings-feedback.error   { color: var(--accent); }

/* ── Profile picker carousel ─────────────────────────────────
 *
 * Wraps to a grid that fits 2–6 columns depending on viewport.
 * Tiles are tappable; the currently-selected card gets a strong
 * purple ring + brightness boost so it's unambiguous which is
 * the active profile picture. */
.settings-filter {
  width: 100%;
  max-width: 320px;
  margin-bottom: 12px;
  padding: 8px 12px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 6px;
  color: var(--text-primary);
  font-size: 14px;
}
/* Carousel tiles render the card art as an avatar (image only,
 * head-cropped) rather than the full card with chrome. This matches
 * what the profile picture will look like elsewhere in the game and
 * gives the art way more room — on a 360px phone the old full-card
 * tiles shrank the art to a sliver. minmax(96px) lets the grid pack
 * 3+ avatars across on phone while still scaling to larger tiles
 * on desktop. */
.profile-carousel {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 12px;
  margin-top: 8px;
}
.profile-carousel-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  cursor: pointer;
  border-radius: 8px;
  padding: 6px 4px 4px;
  transition: transform 0.15s, background 0.15s;
}
.profile-carousel-tile:hover {
  transform: translateY(-2px);
  background: rgba(255, 255, 255, 0.04);
}
.profile-carousel-art {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 8px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.4);
  display: flex;
  align-items: center;
  justify-content: center;
}
.profile-carousel-art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;
  display: block;
}
.profile-carousel-art-fallback {
  font-size: 18px;
  font-weight: 700;
  color: rgba(220, 235, 255, 0.85);
  letter-spacing: 1px;
}
.profile-carousel-name {
  font-size: 11px;
  line-height: 1.2;
  color: var(--text-secondary);
  text-align: center;
  width: 100%;
  /* Ellipsize long names so the tile never grows taller than its
   * neighbors (matters because the carousel is a grid — tall rows
   * inherit the tallest tile's height). */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.profile-carousel-tile.is-current {
  background: rgba(139, 92, 246, 0.18);
}
.profile-carousel-tile.is-current .profile-carousel-art {
  box-shadow: 0 0 0 3px #c4b5fd, 0 6px 14px rgba(139, 92, 246, 0.45);
}
.profile-carousel-tile.is-current .profile-carousel-name {
  color: #efe6ff;
  font-weight: 600;
}
.profile-carousel-tile.is-current::after {
  content: '✓ Current';
  position: absolute;
  top: -6px;
  right: 4px;
  background: #c4b5fd;
  color: #1a1428;
  font-size: 10px;
  font-weight: 800;
  padding: 2px 6px;
  border-radius: 10px;
  z-index: 1;
}
.profile-carousel-empty {
  padding: 24px;
  text-align: center;
  color: var(--text-secondary);
  grid-column: 1 / -1;
}
/* No sentinel — the carousel renders all owned cards as placeholders
 * up front; per-tile IntersectionObserver hydrates art via the shared
 * ImageLoader. */

/* ── Profile preview row ─────────────────────────────────────
 *
 * Filter input on the left, current-pick art preview on the right.
 * On phone widths the row wraps so the preview lands below the
 * input — still legible, and the carousel takes the rest. The
 * preview shows just the art, not the full card, so the player
 * sees what their portrait will actually look like (the future
 * match-start banner / leaderboard avatar will be art-only). */
.profile-picker-controls {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.profile-picker-controls .settings-filter {
  margin-bottom: 0;
  flex: 1 1 220px;
}
.profile-current-preview {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.profile-current-art {
  width: 96px;
  height: 96px;
  border-radius: 8px;
  object-fit: cover;
  object-position: center top;  /* matches the in-game card-thumb crop so the same face shows */
  box-shadow: 0 0 0 3px #c4b5fd, 0 6px 14px rgba(139, 92, 246, 0.45);
  background: rgba(0, 0, 0, 0.4);
  display: block;
}
.profile-current-art-empty {
  width: 96px;
  height: 96px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  color: var(--text-secondary);
  background: rgba(255, 255, 255, 0.04);
  border: 1px dashed rgba(255, 255, 255, 0.18);
}
/* Make the preview tap-targetable: visible cursor + focus ring so
 * keyboard users see they can hit Enter to open the card detail. */
.profile-current-preview {
  cursor: pointer;
  outline: none;
}
.profile-current-preview:focus-visible .profile-current-art,
.profile-current-preview:focus-visible .profile-current-art-empty {
  box-shadow: 0 0 0 3px #c4b5fd, 0 0 0 5px rgba(139, 92, 246, 0.5);
}

/* Settings → Profile picture section: preview on the left, name +
 * Change button stacked on the right. The Change button opens the
 * full-screen picker modal; tap on the preview itself opens the
 * standard card-detail view. */
.profile-section-row {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.profile-section-side {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.profile-section-name {
  font-size: 15px;
  color: var(--text-primary);
}

/* ── Profile picker modal ────────────────────────────────────
 *
 * Full-screen overlay (no document scroll bleed) that owns its own
 * scroll. Layout:
 *
 *   ┌──────────────────────────────────┐
 *   │  [preview]   name                │  ← fixed header
 *   │              [filter input    ]  │
 *   ├──────────────────────────────────┤
 *   │  ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐    │  ← scrollable grid
 *   │  └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘    │
 *   │   …                              │
 *   ├──────────────────────────────────┤
 *   │              [Cancel]  [Save]    │  ← fixed footer
 *   └──────────────────────────────────┘
 *
 * Selection state lives only inside the modal until the player
 * presses Save, at which point the caller persists it to the
 * server. The grid uses the same hydrate / dehydrate
 * IntersectionObserver pattern as the deck-builder collection so
 * a 1000-card collection doesn't flood the network on first
 * paint or rapid scroll. */
.profile-picker-overlay {
  position: fixed;
  inset: 0;
  z-index: 1100;          /* above the in-game game-over overlay (1000) */
  background: rgba(0, 0, 0, 0.78);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: stretch;
  justify-content: center;
  padding: 16px;
}
.profile-picker-modal {
  display: flex;
  flex-direction: column;
  width: min(720px, 100%);
  max-height: 100%;
  background: var(--bg-secondary);
  border: 1px solid rgba(139, 92, 246, 0.45);
  border-radius: 12px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.6);
  overflow: hidden;   /* contain the rounded corners over the inner scroll */
}
.profile-picker-header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  background: linear-gradient(180deg, rgba(40, 24, 70, 0.85) 0%, rgba(22, 14, 40, 0.85) 100%);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.profile-picker-meta {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1 1 auto;
  min-width: 0;
}
.profile-picker-name {
  font-size: 16px;
  font-weight: 700;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.profile-picker-meta .settings-filter {
  margin-bottom: 0;
  max-width: none;
  width: 100%;
}
.profile-picker-grid-wrap {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 14px 16px;
}
.profile-picker-grid-wrap .profile-carousel {
  margin-top: 0;
}
.profile-picker-footer {
  flex: 0 0 auto;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding: 12px 16px;
  background: rgba(0, 0, 0, 0.25);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}
/* On phones the preview + meta lane gets tight. Keep them on one
 * row at the top regardless — the grid below has the real space. */
@media (max-width: 480px) {
  .profile-picker-header {
    padding: 12px;
    gap: 10px;
  }
  .profile-picker-name {
    font-size: 14px;
  }
}

/* ── Main menu: responsive layout fixes ──────────────────────
 *
 * Two problems were observable on phone-sized viewports:
 *   1. `.account-bar` was `position: absolute` at top-right and
 *      overlapped the centered CARDVERSE title once the
 *      account bar gained the Settings link (three items wide
 *      easily covers the title on phones).
 *   2. `.menu-screen` has `overflow: hidden` via body, so any
 *      content past `100dvh` (e.g. the Custom Pack tile at the
 *      bottom of the shop column) was simply unreachable on
 *      small screens — no scroll.
 *
 * Fix:
 *   - Override the account-bar to be a normal flex child on the
 *     main menu, sitting at the top with right-aligned items.
 *     Other screens (starter selection, auth modal) keep the
 *     absolute behavior — they have less content so the corner
 *     pin still looks correct.
 *   - Switch the main menu to flex-start + overflow-y:auto with
 *     height:100dvh so it scrolls internally past viewport.
 *   - Use clamp() on the title font so it shrinks on phones
 *     instead of pushing other rows offscreen. */
.menu-screen.main-menu {
  justify-content: flex-start;
  align-items: center;
  height: 100dvh;
  overflow-y: auto;
  padding-top: 16px;
}
.menu-screen.main-menu .account-bar {
  position: static;
  align-self: stretch;
  justify-content: flex-end;
  /* No own margin-bottom — the parent flex `gap` already provides
   * spacing to the title. Adding a margin here doubles the perceived
   * gap because the title also carries its own top margin below. */
  margin-bottom: 0;
  padding: 0 8px;
}
.menu-screen.main-menu .menu-title {
  font-size: clamp(26px, 9vw, 42px);
  letter-spacing: clamp(1px, 0.6vw, 4px);
  /* Top margin removed — see the note on .account-bar above. The
   * parent flex `gap` alone handles spacing above the title, halving
   * the visual gap between "Signed in as…" and the CARDVERSE title. */
  margin: 0 0 8px;
  text-align: center;
}
