/* =============================================================
   COMPONENTS.CSS — All UI components and screen layouts.
   Organized: App shell → Nav → Home → Wallet → Play → Profile
   ============================================================= */

/* -----------------------------------------------------------
   APP SHELL
   The main container wraps all screens.
   Only one screen has the .active class at a time.
   ----------------------------------------------------------- */
#app {
  position: relative;
  width: 100%;
  max-width: 480px;        /* Cap at phone width even on desktop */
  margin: 0 auto;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Individual screen — all start hidden */
.screen {
  display: none;
  flex-direction: column;
  flex: 1;
  /* Clear the fixed nav (now just var(--nav-height) — it no longer occupies the
     safe-area inset zone, so nav-height clearance is correct everywhere; v128). */
  padding-bottom: var(--nav-height);
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;  /* smooth momentum scroll on iOS */
  position: relative;
  z-index: 1;                         /* sit ABOVE the persistent #app-bg (z-index 0) */
}

/* Only the active screen is shown */
.screen.active {
  display: flex;
  animation: screenSlideUp 0.28s ease-out both;
}

/* ── Persistent global background ─────────────────────────────────────────────
   The bg_landscape art, promoted out of #screen-home so it stays visible behind
   every screen (no black flash on navigation). Same geometry the old .home-bg
   had: fixed, centred, capped to the app width, full height, cover. Sits BELOW
   the screens (z-index 0) and below the bottom nav (z-index 1000), above the body
   gradient. Filled by applyThemeSlots() via data-slot="bg_landscape". Stage 1. */
#app-bg {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100vw;
  max-width: 480px;
  height: 100%;
  z-index: 0;
  background-size: cover;
  background-position: top center;
}

/* -----------------------------------------------------------
   BOTTOM NAVIGATION BAR
   ----------------------------------------------------------- */
#bottom-nav {
  position: fixed;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 480px;
  /* True nav-height, flush at bottom:0. The banner art (background 100% 100% /
     center bottom) fills it at its true proportion (no stretch); the iPhone home
     indicator overlays the bottom ~34px of the banner (standard for a bottom bar),
     with the tappable icons in the upper portion clear of it. [v128 — reverts the
     v114 inset height/padding that made the bar taller and stretched the art.] */
  height: var(--nav-height);
  background: rgba(4, 15, 15, 0.96);
  border-top: 1px solid var(--border-teal);
  display: flex;
  align-items: stretch;
  z-index: 1000;
  /* Blur effect for glassmorphism */
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

/* Each nav button */
.nav-btn {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  background: none;
  border: none;
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  position: relative;
  transition: color 0.2s ease;
  padding: 0;
}

/* Nav icon (SVG wrapper) */
.nav-btn svg {
  width: 22px;
  height: 22px;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.8;
  transition: stroke 0.2s ease, filter 0.2s ease;
}

/* Active nav tab */
.nav-btn.active {
  color: var(--teal-light);
}
.nav-btn.active svg {
  filter: drop-shadow(0 0 6px var(--teal-glow));
}

/* Active indicator bar at bottom */
.nav-btn.active::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 25%;
  width: 50%;
  height: 2px;
  background: var(--grad-teal);
  border-radius: var(--radius-full);
  animation: navTabIn 0.2s ease-out both;
}

/* Press feedback */
.nav-btn:active {
  transform: scale(0.9);
}

/* -----------------------------------------------------------
   TOAST NOTIFICATION (shared across all screens)
   ----------------------------------------------------------- */
#toast-container {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2000;
  pointer-events: none;
  width: calc(100% - 32px);
  max-width: 420px;
}

.toast {
  background: var(--bg-card);
  border: 1px solid var(--border-gold);
  border-radius: var(--radius-md);
  padding: var(--space-sm) var(--space-md);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.95rem;
  text-align: center;
  box-shadow: var(--shadow-gold);
  animation: toastIn 0.3s ease-out both;
  margin-bottom: 8px;
}
.toast.removing {
  animation: toastOut 0.3s ease-in forwards;
}
.toast.toast-win    { border-color: var(--border-gold);    color: var(--gold); }
.toast.toast-error  { border-color: var(--border-crimson); color: var(--crimson-light); }
.toast.toast-info   { border-color: var(--border-teal);    color: var(--teal-light); }


/* ============================================================
   HOME SCREEN
   ============================================================ */

#screen-home {
  padding: 0 var(--space-md) var(--space-md);
  gap: var(--space-md);
  background: var(--grad-bg);
  background-attachment: fixed;
}

/* ---- Logo section ---- */
.home-logo-section {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: var(--space-xl);
  padding-bottom: var(--space-sm);
  gap: var(--space-sm);
}

.logo-img {
  width: min(220px, 62vw);
  height: auto;
  animation: logoFloat 4s ease-in-out infinite;
  filter:
    drop-shadow(0 0 18px rgba(0,196,167,0.7))
    drop-shadow(0 0 36px rgba(245,166,35,0.4))
    drop-shadow(0 4px 12px rgba(0,0,0,0.6));
}

/* Lottery Clash text logo (shown when no image) */
.logo-text {
  font-family: var(--font-display);
  font-size: 2.4rem;
  letter-spacing: 0.06em;
  text-align: center;
  line-height: 1;

  /* Layered text stroke effect */
  -webkit-text-stroke: 2px var(--teal-dark);
  paint-order: stroke fill;
  color: var(--gold-bright);
  text-shadow:
    0 0 8px  var(--gold-glow),
    0 0 20px rgba(245,166,35,0.5),
    0 2px 0  rgba(0,0,0,0.8);
}
.logo-text span {
  color: var(--crimson-light);
  -webkit-text-stroke: 2px var(--crimson-dark);
  text-shadow:
    0 0 8px  var(--crimson-glow),
    0 0 20px rgba(220,28,46,0.5);
}

/* ---- Jackpot counter ---- */
.jackpot-section {
  width: 100%;
  background: var(--bg-card);
  border: 1px solid var(--border-gold);
  border-radius: var(--radius-lg);
  padding: var(--space-md) var(--space-lg);
  text-align: center;
  animation: glowPulseGold 3s ease-in-out infinite;
  position: relative;
  overflow: hidden;
}

/* Sparkle decorations in corners */
.jackpot-section::before,
.jackpot-section::after {
  content: '✦';
  position: absolute;
  color: var(--gold-light);
  font-size: 0.8rem;
  opacity: 0.5;
}
.jackpot-section::before { top: 8px; left: 12px; }
.jackpot-section::after  { top: 8px; right: 12px; }

.jackpot-label {
  font-family: var(--font-body);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-secondary);
  margin-bottom: 4px;
}

#jackpot-value {
  font-family: var(--font-mono);
  font-size: 2.4rem;
  font-weight: 900;
  color: var(--gold-bright);
  text-shadow: 0 0 12px var(--gold-glow), 0 0 30px rgba(245,166,35,0.4);
  animation: jackpotPulse 6s ease-in-out infinite;
  letter-spacing: 0.04em;
  display: block;
}

.jackpot-sub {
  font-size: 0.7rem;
  color: var(--text-secondary);
  margin-top: 2px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* ---- Active players pill ---- */
.active-players-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  background: rgba(0, 196, 167, 0.08);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-full);
  padding: 6px 16px;
  width: fit-content;
  margin: 0 auto;
}

/* Pulsing green dot — "live" indicator */
.live-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #00ff80;
  box-shadow: 0 0 6px #00ff80, 0 0 12px rgba(0,255,128,0.4);
  flex-shrink: 0;
  animation: glowPulseTeal 2s ease-in-out infinite;
}

.active-players-text {
  font-family: var(--font-body);
  font-size: 0.85rem;
  color: var(--teal-light);
  font-weight: 700;
  letter-spacing: 0.04em;
}
#player-count {
  display: inline-block;
  animation: playerCountPop 0.4s ease-out;
}

/* ---- Section heading ---- */
.section-heading {
  font-family: var(--font-display);
  font-size: 0.8rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-secondary);
  padding: 0 var(--space-xs);
}

/* ---- Tier cards ---- */
.tier-cards-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  width: 100%;
}

.tier-card {
  background: var(--grad-card);
  border-radius: var(--radius-xl);
  padding: var(--space-lg) var(--space-lg);
  border: 1px solid var(--border-teal);
  position: relative;
  overflow: hidden;
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

/* Tier 1 — active, teal border */
.tier-card.tier-active {
  border-color: var(--teal);
  box-shadow: var(--shadow-teal);
  animation: glowPulseTeal 3.5s ease-in-out infinite;
}
/* Tier 2 — gold */
.tier-card.tier-mid {
  border-color: var(--border-gold);
  opacity: 0.7;
  cursor: default;
}
/* Tier 3 — crimson */
.tier-card.tier-high {
  border-color: var(--border-crimson);
  opacity: 0.7;
  cursor: default;
}

/* Shimmer effect on the active card */
.tier-card.tier-active::before {
  content: '';
  position: absolute;
  top: 0; left: -40%;
  width: 30%; height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.08), transparent);
  animation: shimmer 3.5s ease-in-out infinite;
}

/* Tap/press feedback only on active card */
.tier-card.tier-active:active {
  transform: scale(0.97);
  box-shadow: var(--shadow-teal), 0 0 0 3px rgba(0,196,167,0.3);
}

/* Tier 2 is now PLAYABLE — gold equivalent of the active (Tier 1) treatment.
   ID specificity (1,0,0) overrides the dimmed `.tier-card.tier-mid` rule. */
#tier2-card {
  opacity: 1;
  cursor: pointer;
  box-shadow: var(--shadow-gold);
  animation: glowPulseGold 3.5s ease-in-out infinite;
}
#tier2-card::before {
  content: '';
  position: absolute;
  top: 0; left: -40%;
  width: 30%; height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.08), transparent);
  animation: shimmer 3.5s ease-in-out infinite;
}
#tier2-card:active {
  transform: scale(0.97);
  box-shadow: var(--shadow-gold), 0 0 0 3px rgba(245,166,35,0.3);
}

/* Price badge on left */
.tier-price-badge {
  flex-shrink: 0;
  width: 58px;
  height: 58px;
  border-radius: var(--radius-md);
  background: var(--grad-teal);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 1.3rem;
  font-weight: 900;
  color: var(--bg-deep);
  box-shadow: 0 2px 12px rgba(0,196,167,0.4);
}
.tier-card.tier-mid  .tier-price-badge { background: var(--grad-gold);    box-shadow: 0 2px 12px rgba(245,166,35,0.4); }
.tier-card.tier-high .tier-price-badge { background: var(--grad-crimson);  box-shadow: 0 2px 12px rgba(220,28,46,0.4); color: var(--text-white); }

/* Text content */
.tier-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.tier-name {
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--text-white);
  letter-spacing: 0.04em;
}
.tier-tagline {
  font-size: 0.8rem;
  color: var(--text-secondary);
  font-weight: 600;
}

/* "Play" arrow on the right for active tier */
.tier-arrow {
  flex-shrink: 0;
  color: var(--teal-light);
  font-size: 1.4rem;
  opacity: 0.9;
}

/* "Coming Soon" overlay */
.tier-locked {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-shrink: 0;
  gap: 2px;
}
.tier-locked .lock-icon {
  font-size: 1.3rem;
  animation: lockPulse 2.5s ease-in-out infinite;
}
.tier-locked .lock-label {
  font-size: 0.6rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-secondary);
  font-weight: 700;
  white-space: nowrap;
}

/* ============================================================
   WALLET SCREEN
   ============================================================ */

#screen-wallet {
  padding: var(--space-xl) var(--space-md) var(--space-md);
  gap: var(--space-lg);
  background: var(--grad-bg);
  background-attachment: fixed;
}

/* ---- Screen title ---- */
.screen-title {
  font-family: var(--font-display);
  font-size: 1.6rem;
  letter-spacing: 0.06em;
  color: var(--text-white);
  padding: 0 var(--space-xs);
}

/* ---- Total balance hero card ---- */
.wallet-total-card {
  background: var(--grad-card);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-xl);
  padding: var(--space-xl) var(--space-lg);
  text-align: center;
  animation: glowPulseTeal 4s ease-in-out infinite;
  position: relative;
  overflow: hidden;
}
.wallet-total-card::before {
  content: '';
  position: absolute;
  top: 0; left: -40%;
  width: 30%; height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.05), transparent);
  animation: shimmer 4s ease-in-out infinite;
}

.wallet-total-label {
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-secondary);
  margin-bottom: var(--space-xs);
}
#wallet-total-display {
  font-family: var(--font-mono);
  font-size: 3rem;
  font-weight: 900;
  color: var(--teal-light);
  text-shadow: var(--shadow-teal);
  letter-spacing: 0.04em;
  display: block;
}

/* ---- Balance breakdown (2×2 grid — 4 API buckets) ---- */
.wallet-breakdown {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-sm);
}

.breakdown-card {
  background: var(--bg-card);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-lg);
  padding: var(--space-md) var(--space-sm);
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* Color coding per bucket */
.breakdown-card.breakdown-bonus   { border-color: var(--border-crimson); }
.breakdown-card.breakdown-pending { border-color: var(--border-gold); }
.breakdown-card.breakdown-locked  { border-color: rgba(100,80,200,0.35); }

.breakdown-label {
  font-size: 0.65rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-secondary);
  font-weight: 700;
}
.breakdown-value {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--teal-light);
}
.breakdown-card.breakdown-bonus   .breakdown-value { color: var(--crimson-light); }
.breakdown-card.breakdown-pending .breakdown-value { color: var(--gold); }
.breakdown-card.breakdown-locked  .breakdown-value { color: #a090ff; }

/* Wallet loading indicator in the total card */
.wallet-loading {
  font-family: var(--font-mono);
  font-size: 1rem;
  color: var(--text-secondary);
  letter-spacing: 0.1em;
  animation: glowPulseTeal 1.5s ease-in-out infinite;
}

/* ---- Deposit button ---- */
.deposit-section {
  display: flex;
  flex-wrap: wrap;   /* v192: the money-movement note sits on its own row */
  gap: var(--space-sm);
  align-items: center;
}
/* v192: "deposits and withdrawals open at launch" note above the button */
.wallet-money-note {
  flex: 1 0 100%;
  margin: 0;
  padding: 10px 14px;
  border: 1px solid var(--gold);
  border-radius: var(--radius-lg);
  background: rgba(0, 0, 0, 0.25);
  color: var(--gold);
  font-size: 0.9rem;
  line-height: 1.35;
  text-align: center;
}
.wallet-money-note[hidden] { display: none; }

/* Primary action button — used across the app */
.btn-primary {
  flex: 1;
  background: var(--grad-teal);
  border: 1px solid var(--teal);
  border-radius: var(--radius-lg);
  color: var(--bg-deep);
  font-family: var(--font-display);
  font-size: 1rem;
  letter-spacing: 0.06em;
  padding: 14px var(--space-lg);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: filter 0.15s ease, transform 0.1s ease;
  box-shadow: var(--shadow-teal);
  text-shadow: none;
}
.btn-primary:active {
  transform: scale(0.97);
  filter: brightness(0.9);
}
.btn-primary:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

/* Button ripple on tap */
.btn-primary .ripple {
  position: absolute;
  border-radius: 50%;
  background: rgba(255,255,255,0.35);
  pointer-events: none;
  animation: ripple 0.5s ease-out forwards;
  transform-origin: center;
}

/* Gold variant */
.btn-gold {
  background: var(--grad-gold);
  border-color: var(--gold);
  color: #1a0800;
  box-shadow: var(--shadow-gold);
}
.btn-gold:active { filter: brightness(0.9); }

/* Danger/crimson variant */
.btn-crimson {
  background: var(--grad-crimson);
  border-color: var(--crimson);
  color: var(--text-white);
  box-shadow: var(--shadow-crimson);
}

/* ---- Transaction history ---- */
.transaction-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.transaction-list-header {
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-secondary);
  padding: 0 var(--space-xs);
}

.transaction-item {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  background: var(--bg-card);
  border: 1px solid rgba(0,196,167,0.12);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  transition: border-color 0.2s ease;
}
.transaction-item:hover { border-color: var(--border-teal); }

/* Type icon badge */
.tx-icon {
  width: 36px;
  height: 36px;
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.1rem;
  flex-shrink: 0;
}
.tx-icon.type-deposit     { background: rgba(0,196,167,0.15); }
.tx-icon.type-ticket_buy  { background: rgba(220,28,46,0.15); }
.tx-icon.type-win         { background: rgba(245,166,35,0.15); }
.tx-icon.type-free_ticket { background: rgba(100,80,220,0.15); }

.tx-details {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.tx-description {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--text-primary);
}
.tx-date {
  font-size: 0.72rem;
  color: var(--text-secondary);
  font-family: var(--font-mono);
}

.tx-amount {
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 700;
  flex-shrink: 0;
}
.tx-amount.positive { color: var(--gold); }
.tx-amount.negative { color: var(--crimson-light); }


/* ============================================================
   PLAY SCREEN — Scratch Rush
   ============================================================ */

#screen-play {
  padding: var(--space-md) var(--space-sm) var(--space-md);
  gap: var(--space-md);
  /* Transparent so the persistent global #app-bg shows behind the game — the
     framed steampunk stage floats on the art. */
  background: transparent;
  align-items: center;
}

/* ============================================================================
   TIER 2 — SCRATCH RUSH FRAMED STAGE (steampunk redesign, stage 2)
   One scaled container at the SR art aspect; coord space 880 × 1540 (SR_BG is
   880×1277 at top; the play panel + button protrude below to 1540). Every layer
   is position:absolute, sized by % of the stage, so it scales to any phone while
   staying aligned. NO overflow clipping — the button/pipes protrude below SR_BG.
   ============================================================================ */
.sr-stage {
  position: relative;
  width: 100%;
  max-width: 440px;          /* art is 880 wide → 2× retina at 440 */
  margin: 0 auto;
  aspect-ratio: 880 / 1540;
  /* no overflow:hidden — play button protrudes below the frame */
}

/* Shared art-layer base — image set by applyScratchStageArt(); contain so the
   frame art never stretches. */
.sr-layer {
  position: absolute;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

/* L1 — frame background (880×1277 → 82.92% of the 1540 stage) */
.sr-bg { left: 0; top: 8.12%; width: 100%; height: 82.92%; z-index: 1; }   /* frame unit moved down +125px (8.12%) */

/* L2 — inner play-panel background */
.sr-play-bg { left: 11.36%; top: 84.42%; width: 76.59%; height: 23.77%; z-index: 2; }   /* play panel moved down +100px (6.50%) */

/* L3 — 3×3 grid window (square: 65.68%×880 ≈ 37.53%×1540 ≈ 578px). The foil
   canvas (added by scratch-rush.js) is position:absolute inset:0 within this, so
   it auto-aligns. grid-template-rows added so the 3 rows fill the square rect.

   iOS FIX: the selector is id-qualified (#scratch-grid.sr-grid, specificity
   1,1,0) so it BEATS the base `.scratch-grid { position: relative }` (0,1,0)
   which — being later in source — was otherwise winning and leaving the grid
   position:RELATIVE. On iOS Safari a relative element's `top%`/`height%` don't
   resolve against an aspect-ratio container's (indefinite) content height, so
   the grid collapsed to the top of the stage (cells floated to the top). As an
   ABSOLUTE element its `top%` resolves against the padding-box used size (like
   the working art layers), and the height is derived from the DEFINITE width via
   aspect-ratio (no dependency on stage-height % resolution at all). */
#scratch-grid.sr-grid {
  position: absolute;
  left: 17.27%;
  top: 40.65%;            /* moved down +125px (8.12%) WITH the frame unit */
  width: 65.68%;
  aspect-ratio: 1;        /* square window — height from the definite width (iOS-robust) */
  height: auto;           /* let aspect-ratio drive the height (was 37.53%) */
  z-index: 3;
  grid-template-rows: repeat(3, 1fr);
  /* Tight gap for a near-seamless scratch surface (id-qualified so it beats the
     base + responsive .scratch-grid gaps). EASY TO TUNE: try 0 / 2px / 4px. The
     foil re-measures the cells from live rects in _initFoil() at buy time, so its
     9 detection zones follow the new cell positions automatically. */
  gap: 2px;
  /* overflow kept harmless (chips no longer overflow; cell-symbol inset:0). */
  overflow: hidden;
}

/* Grid items must fill their tracks (not self-size); min-*:0 stops the default
   auto min-size from overflowing on iOS. */
#scratch-grid.sr-grid .scratch-cell {
  aspect-ratio: auto;
  min-width: 0;
  min-height: 0;
}

/* Wallet live balance over the frame's top brass window — APPROX position,
   nudge to the art's wallet readout. */
.sr-wallet {
  position: absolute;
  left: 0;
  right: 0;
  top: 10.75%;            /* brass readout window (was 14%, up 50px = 3.25%) — ESTIMATE, nudge on-device */
  z-index: 4;
  text-align: center;     /* centered over the (centered) brass window */
  font-family: var(--font-mono);   /* Oswald */
  font-weight: 600;       /* Oswald SemiBold */
  font-size: 1.15rem;
  letter-spacing: 0.02em;
  color: var(--gold-bright);
  text-shadow: 0 1px 2px rgba(0,0,0,0.6);
  pointer-events: none;
}

/* Scratch hint — over the lower panel, below the grid */
.sr-hint {
  position: absolute;
  left: 11.36%;
  width: 76.59%;
  top: 82.54%;            /* moved down +170px (11.04%) from 71.5% */
  z-index: 4;
  text-align: center;
  margin: 0;
}

/* L5 — PLAY / pay button (tap target; protrudes below the frame). */
.sr-play-area {
  position: absolute;
  left: 34.09%;
  top: 84.74%;            /* play button moved down +125px (8.12%) */
  width: 31.14%;
  height: 17.79%;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
}
.sr-play-button {
  width: 100%;
  height: 100%;
  padding: 0;
  border: none;
  background: transparent center / contain no-repeat;
  cursor: pointer;
  font-size: 0;            /* hide any injected loading text over the art */
  -webkit-tap-highlight-color: transparent;
}
.sr-play-button:disabled { cursor: default; opacity: 0.7; }

/* Saved-free-ticket marker on the PLAY dial's top-right (the red-pill spot).
   Positioned by % of the 880×1540 stage like the other sr-* layers. ESTIMATE —
   nudge on-device against the dial art. CMS art (gameIcons.scratch.free_marker)
   fills background-image via applyScratchStageArt(); when null, the gradient pill
   below is the fallback. Shown only when count > 0 (display toggled in JS). */
.sr-free-marker {
  position: absolute;
  /* nudge: position over PLAY dial top-right (dial: left 34.09%, right 65.23%,
     top 84.74%). Marker floats just above-and-right of the dial rim. */
  left: 74%;
  top: 83.5%;
  right: auto;
  bottom: auto;
  width: 10%;
  aspect-ratio: 1;
  z-index: 7;                /* above the dial (z 5) + play button */
  padding: 0;
  border: 2px solid var(--gold);
  border-radius: 50%;
  /* Fallback pill (background-image overridden by the CMS slot when present) */
  background-image: radial-gradient(circle at 50% 35%, #ff4d5e, #b3001b);
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  color: var(--gold-bright);
  cursor: pointer;
  /* display:flex shows + centers the count; JS sets inline display:'' (→ flex)
     when count>0 and inline display:'none' (overrides this) when count is 0. */
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 8px rgba(0,0,0,0.5);
  -webkit-tap-highlight-color: transparent;
  animation: srFreePulse 1.8s ease-in-out infinite;   /* gentle; only ever shown when count>0 */
}
.sr-free-marker:disabled { cursor: default; opacity: 0.55; animation: none; }
#free-ticket-marker-count {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 0.95rem;
  line-height: 1;
  text-shadow: 0 1px 2px rgba(0,0,0,0.7);
  pointer-events: none;      /* taps go to the button, not the span */
}
@keyframes srFreePulse {
  0%, 100% { transform: scale(1);    box-shadow: 0 2px 8px rgba(0,0,0,0.5); }
  50%      { transform: scale(1.06); box-shadow: 0 2px 14px var(--gold-glow); }
}

/* Win/Lose result — overlay centered over the grid window when .visible */
.sr-result {
  position: absolute;
  /* Centered OVER the grid window (#scratch-grid.sr-grid: top 40.65% + height
     37.53% → centre ≈ 59% of the 880×1540 stage), horizontally centered. */
  left: 50%;
  top: 59%;
  transform: translate(-50%, -50%);
  width: 84%;
  z-index: 6;
  padding: var(--space-md);
  border-radius: var(--radius-lg);
  background: rgba(4, 15, 15, 0.92);
  border: 1px solid var(--border-teal);
  box-shadow: var(--shadow-teal);
  text-align: center;
}

/* Metal bird perched on the nav — Tier 1 only (toggled in app.js). Above the
   nav, overlapping its top edge; never blocks taps. APPROX position — nudge. */
/* Bird is now a child of #app (NOT #bottom-nav) and position:FIXED, so the nav's
   overflow:hidden (which was clipping the wings/tail) can't crop it. Anchored to
   the right edge of the centered, max-480 nav bar on any width. Box keeps the
   257:233 art ratio + contain so the whole bird shows. Perch offset = ESTIMATE. */
.sr-bird {
  position: fixed;
  bottom: calc(var(--nav-height) - 30px);          /* perch overlapping the nav top edge (lowered 15px) */
  right: calc(50vw - min(50vw, 240px) + 6px);      /* 6px inside the centered bar's right edge */
  width: 110px;
  aspect-ratio: 257 / 233;                          /* full bird (≈110×99.8), uncropped */
  z-index: 1001;                                    /* above the nav (z 1000) */
  background-size: contain;
  background-repeat: no-repeat;
  background-position: bottom center;
  pointer-events: none;
}

/* ---- Game header ---- */
.game-header {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 var(--space-xs);
}

.game-title {
  font-family: var(--font-display);
  font-size: 1.5rem;
  letter-spacing: 0.06em;
  color: var(--text-white);
}

.game-balance-pill {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--bg-card);
  border: 1px solid var(--border-gold);
  border-radius: var(--radius-full);
  padding: 6px 14px;
  font-family: var(--font-mono);
  font-size: 0.95rem;
  color: var(--gold);
  font-weight: 700;
}

/* ---- Scratch card container ---- */
.scratch-card-wrapper {
  width: 100%;
  max-width: 360px;
}

.scratch-card {
  background: var(--grad-card);
  border: 2px solid var(--border-teal);
  border-radius: var(--radius-xl);
  padding: var(--space-lg);
  box-shadow: var(--shadow-teal);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  position: relative;
  overflow: hidden;
}

/* Top bar of the card */
.card-top-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.card-game-label {
  font-family: var(--font-display);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--teal-light);
}
.card-ticket-price {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--text-secondary);
  letter-spacing: 0.08em;
}

/* ---- 3x3 Scratch grid ---- */
.scratch-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-sm);
  /* Anchor the single foil canvas, which is absolutely positioned over the
     whole field (see .scratch-foil). */
  position: relative;
}

/* Each scratch cell */
.scratch-cell {
  aspect-ratio: 1;
  border-radius: var(--radius-md);
  background: transparent;      /* no opaque/colored backing — the teal frame shows behind the chip art */
  border: none;                 /* idle colored outline removed */
  position: relative;
  overflow: visible;            /* let the chip art expand 5px beyond the cell */
  transition: border-color 0.3s ease;
}

/* The symbol underneath the foil. Always VISIBLE (opacity:1): the symbols/chips
   render here from the moment the card is populated and the opaque .scratch-foil
   canvas sits ON TOP (higher z-index). As the player scratches (destination-out
   erases foil pixels), the symbol shows through the cleared area immediately —
   true scratch-card behaviour. The old opacity:0 "hidden until .revealed" gate is
   gone; the `revealed` class is now just a JS state marker. */
.cell-symbol {
  position: absolute;
  /* Chip art fills its cell EXACTLY (edge-to-edge within the cell) for a seamless
     surface. Tune 0–6px if the art needs to tuck inside the gold border. The
     winning glow is an OUTWARD box-shadow (matchGlow), so edge-filling chips
     don't break it. */
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2rem;
  opacity: 1;
}

/* Winning cell highlight */
.scratch-cell.winning {
  border-color: var(--gold);
  animation: matchGlow 1.2s ease-in-out infinite;
}

/* Per-tile FLASH — fired once when a tile first crosses ~60% scratched.
   Feedback only: it does NOT clear the foil, and is seen through the parts the
   player has already scratched away. */
.scratch-cell.tile-flash {
  animation: tileFlash 0.5s ease-out;
}

/* The SINGLE continuous foil canvas — overlays the entire 3×3 field (all cells
   + the gaps between them) so the player scratches one surface in a single
   sweep. Absolutely positioned so it stays out of the grid track flow. */
.scratch-foil {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  cursor: crosshair;
  touch-action: none;  /* Prevent scroll while scratching */
  display: block;
  z-index: 3;          /* above every cell + its symbol */
  border-radius: var(--radius-md);
}

/* ---- Card footer (result / CTA) ---- */
.card-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  min-height: 56px;
  justify-content: center;
}

/* Instructions text */
.scratch-hint {
  font-size: 0.8rem;
  color: var(--text-secondary);
  letter-spacing: 0.08em;
  text-align: center;
}

/* ---- Buy ticket CTA area ---- */
.buy-area {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
}

.btn-buy-ticket {
  width: 100%;
}

.btn-buy-ticket .btn-price {
  font-family: var(--font-mono);
  font-size: 0.9rem;
}

/* ---- Free-play tickets ---- */
/* (The old bottom "🎟 N free tickets" bar was replaced by #free-ticket-marker on
   the PLAY dial — see .sr-free-marker near the SR stage layers.) */

/* Play Now / Save Ticket actions on a granted free_ticket win */
.free-ticket-actions {
  display: flex;                 /* toggled by JS */
  gap: var(--space-sm);
  width: 100%;
}
.free-ticket-actions .btn-primary { flex: 1; }

/* ---- Game state messages ---- */
.game-result {
  display: none;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-md);
}
.game-result.visible { display: flex; }

.result-label {
  font-family: var(--font-display);
  font-size: 1.5rem;
  letter-spacing: 0.08em;
}
.result-label.win-label   { color: var(--gold-bright); text-shadow: 0 0 12px var(--gold-glow), 0 0 30px rgba(245,166,35,0.5); }
.result-label.lose-label  { color: var(--text-secondary); }
.result-label.free-label  { color: var(--teal-light);  text-shadow: 0 0 12px var(--teal-glow); }

.result-prize {
  font-family: var(--font-mono);
  font-size: 2rem;
  color: var(--gold);
  text-shadow: var(--shadow-gold);
}

.result-sub {
  font-size: 0.8rem;
  color: var(--text-secondary);
  text-align: center;
}

/* ---- Win flash overlay (the full-screen flash on big wins) ---- */
#win-flash {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 900;
  opacity: 0;
}
#win-flash.active {
  animation: flashGold 0.8s ease-out forwards;
}

/* ---- Coin burst container ---- */
#coin-container {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 901;
  overflow: hidden;
}

/* Individual flying coin */
.coin-particle {
  position: absolute;
  font-size: 1.6rem;
  line-height: 1;
  pointer-events: none;
  animation: coinFly var(--duration, 1s) ease-out var(--delay, 0s) forwards;
}

/* ---- Odds reference (shown on play screen) ---- */
.odds-panel {
  width: 100%;
  max-width: 360px;
  background: var(--bg-card);
  border: 1px solid rgba(0,196,167,0.15);
  border-radius: var(--radius-lg);
  padding: var(--space-md);
}

.odds-title {
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-secondary);
  text-align: center;
  margin-bottom: var(--space-sm);
}

.odds-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 5px 0;
  border-bottom: 1px solid rgba(255,255,255,0.04);
  font-size: 0.82rem;
}
.odds-row:last-child { border-bottom: none; }
.odds-row .odds-prize { color: var(--gold); font-family: var(--font-mono); font-weight: 700; }
.odds-row .odds-pct   { color: var(--text-secondary); font-size: 0.75rem; }


/* ============================================================
   PROFILE SCREEN (placeholder)
   ============================================================ */

#screen-profile {
  padding: var(--space-xl) var(--space-md) var(--space-md);
  gap: var(--space-lg);
  align-items: center;
  justify-content: center;
  background: var(--grad-bg);
  background-attachment: fixed;
  text-align: center;
}

/* ── Settings screen (v1: header + Sound row + placeholder toggle) ──────── */
#screen-settings {
  padding: var(--space-xl) var(--space-md) var(--space-md);
  gap: var(--space-lg);
  background: var(--grad-bg);
  background-attachment: fixed;
}

.settings-group {
  background: var(--grad-card);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-lg);
  padding: var(--space-md) var(--space-lg);
}
.settings-group-label {
  font-family: var(--font-display);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--teal-light);
  margin-bottom: var(--space-sm);
}
.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  min-height: 38px;
}
.settings-row-label {
  font-family: var(--font-body);
  font-size: 0.98rem;
  font-weight: 600;
  color: var(--text-primary);
}

/* Toggle switch — iOS-style. Hidden checkbox drives the visual track/thumb. */
.toggle {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
  cursor: pointer;
}
.toggle input {
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}
.toggle-track {
  width: 48px;
  height: 28px;
  padding: 3px;
  display: flex;
  align-items: center;
  border-radius: var(--radius-full);
  background: var(--bg-deep);
  border: 1px solid var(--border-teal);
  transition: background 0.2s ease, border-color 0.2s ease;
}
.toggle-thumb {
  width: 20px;
  height: 20px;
  border-radius: var(--radius-full);
  background: var(--text-secondary);
  box-shadow: 0 1px 3px rgba(0,0,0,0.5);
  transition: transform 0.2s ease, background 0.2s ease;
}
.toggle input:checked + .toggle-track {
  background: var(--grad-teal);
  border-color: var(--teal);
}
.toggle input:checked + .toggle-track .toggle-thumb {
  transform: translateX(20px);
  background: #fff;
}
.toggle input:focus-visible + .toggle-track {
  box-shadow: 0 0 0 3px rgba(0,196,167,0.35);
}

.profile-avatar {
  width: 80px;
  height: 80px;
  border-radius: 50%;
  background: var(--bg-card);
  border: 2px solid var(--border-teal);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.5rem;
  box-shadow: var(--shadow-teal);
}

.profile-name {
  font-family: var(--font-display);
  font-size: 1.5rem;
  color: var(--text-white);
}

.profile-coming-soon {
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-secondary);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-full);
  padding: 6px 16px;
  animation: glowPulseTeal 3s ease-in-out infinite;
}

/* ============================================================
   SYM-CHIP — Casino chip tiles revealed inside scratch cells
   Each chip fills the full cell-symbol area and is color-coded
   by symbol value via the data-sym attribute.
   ============================================================ */

.sym-chip {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  gap: 2px;
  font-size: initial;  /* reset the 2rem inherited from .cell-symbol */
  background: transparent;   /* no colored backing — frame shows behind the chip art */
  border: none;        /* idle + per-symbol colored chip outlines removed */
}

/* Cash value label — Orbitron for the number look */
.chip-val {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  font-weight: 900;
  line-height: 1;
  letter-spacing: -0.01em;
}

/* Special prize icon */
.chip-ico {
  font-size: 1.15rem;
  line-height: 1;
}

/* Special prize label underneath icon */
.chip-lbl {
  font-family: var(--font-body);
  font-size: 0.47rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: center;
  line-height: 1.1;
}

/* ── CMS image chips (L2) ────────────────────────────────────────────────
   When an uploaded chip image exists, .sym-chip--img replaces the emoji/CSS
   chip with the artwork. The per-symbol gradient backgrounds below still apply
   but sit hidden behind the cover image (harmless). Cash chips overlay the
   value text on top of the shared cash-chip background. */
.sym-chip--img {
  padding: 0;
  overflow: hidden;
  position: relative;
  /* keeps the existing border-radius / sizing from .sym-chip */
}
.chip-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border-radius: inherit;
}
/* Cash value drawn over the cash-chip art — kept readable with a dark halo */
.chip-val--overlay {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  font-size: 24px;       /* bumped from 1.05rem (16.8px) — value over the cash chip art */
  color: #fff;
  text-shadow: 0 1px 3px rgba(0,0,0,0.9), 0 0 6px rgba(0,0,0,0.7);
}

/* Per-symbol color schemes */
/* Per-symbol TEXT colour only (for the emoji/CSS fallback chips). The colored
   gradient backgrounds + colored box-shadow glows were removed so nothing
   colored sits behind the chip art — the teal frame shows through instead. */
/* v198 — one colour per LIVE cash prize (€1 €2 €5 €25 €100 €250 €5,000).
   The old ladder coloured €10/€50/€500, which are no longer prizes, and had
   nothing for €2/€250/€5,000. An amount with no rule here simply inherits the
   default chip colour, so a prize change can never leave an unstyled hole. */
.sym-chip[data-sym="€1"]     { color:#81e881; }
.sym-chip[data-sym="€2"]     { color:#8ed6c8; }
.sym-chip[data-sym="€5"]     { color:#9fa8da; }
.sym-chip[data-sym="€25"]    { color:#ce93d8; }
.sym-chip[data-sym="€100"]   { color:var(--gold-bright); }
.sym-chip[data-sym="€250"]   { color:#ffb74d; }
.sym-chip[data-sym="€5,000"] { color:#ff8a80; }
.sym-chip[data-sym="FREE"] { color:var(--teal-light); }
.sym-chip[data-sym="T2"]   { color:#81d4fa; }
.sym-chip[data-sym="JF"]   { color:#b388ff; }
.sym-chip[data-sym="MG"]   { color:var(--gold-bright); }

/* Winning chip gets an extra outer glow */
.scratch-cell.winning .sym-chip { box-shadow: 0 0 14px 2px var(--gold-glow); }

/* ============================================================
   FRAGMENT PROGRESS DOTS — Tier 3 card on Home screen
   7 dots, one per Jackpot Fragment collected.
   ============================================================ */

.frag-progress {
  display: flex;
  gap: 5px;
  align-items: center;
  margin-top: 5px;
}

.frag-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.18);
  transition: background 0.35s ease, box-shadow 0.35s ease, border-color 0.35s ease;
}

.frag-dot.filled {
  background: var(--crimson-light);
  border-color: var(--crimson);
  box-shadow: 0 0 6px rgba(220,28,46,0.65);
}

/* ============================================================
   JACKPOT TICKET MODAL — celebration overlay on fragment completion
   ============================================================ */

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.88);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  z-index: 3000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-lg);
  animation: toastIn 0.35s ease-out both;
}

.modal-overlay.hidden,
.pp-popup.hidden {
  display: none;
}

.modal-card {
  background: var(--grad-card);
  border: 2px solid var(--border-gold);
  border-radius: var(--radius-xl);
  padding: var(--space-xl) var(--space-lg);
  text-align: center;
  max-width: 360px;
  width: 100%;
  box-shadow: var(--shadow-gold), 0 24px 80px rgba(0,0,0,0.8);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
  animation: glowPulseGold 3s ease-in-out infinite;
}

.modal-title {
  font-family: var(--font-display);
  font-size: 1.4rem;
  letter-spacing: 0.06em;
  color: var(--gold-bright);
  text-shadow: 0 0 12px var(--gold-glow), 0 0 30px rgba(245,166,35,0.4);
}

.modal-subtitle {
  font-size: 0.72rem;
  color: var(--text-secondary);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* Row of lottery number balls */
.ticket-numbers {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: center;
  padding: var(--space-sm) 0;
}
/* v198: no numbers to show (the server sent none) → take no space at all.
   display:flex above would otherwise beat the hidden attribute. */
.ticket-numbers[hidden] { display: none; }

.ticket-ball {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--grad-gold);
  color: #1a0800;
  font-family: var(--font-mono);
  font-size: 1rem;
  font-weight: 900;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 12px rgba(245,166,35,0.5), inset 0 -3px 6px rgba(0,0,0,0.25);
  animation: tileReveal 0.45s ease-out both;
}

.modal-note {
  font-size: 0.73rem;
  color: var(--text-secondary);
  max-width: 260px;
  line-height: 1.5;
}

/* Ticket held count badge */
.modal-ticket-count {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  color: var(--teal-light);
  background: rgba(0,196,167,0.1);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-full);
  padding: 4px 14px;
}

/* ============================================================
   SETTINGS MODAL — western Lucky Valley menu + sub-panels.
   Reuses .modal-overlay (backdrop/positioning); the card is a
   maroon/gold menu (left-aligned, scrollable, no glow-pulse).
   ============================================================ */
.settings-card {
  /* v199: the CARD chrome moved INTO the content — each section is its own
     popup frame (.settings-section), so this is now just the sized, scrolling
     container. Keeping a maroon card here too would box a card inside a card. */
  background: none;
  border: none;
  border-radius: 0;
  width: 100%;
  max-width: 400px;
  max-height: 82vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  text-align: left;
  box-shadow: none;
}

/* Header: back (sub-panel only) · centred title · close */
.settings-header {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-md) var(--space-lg);
  /* v199: no bar of its own — the title sits over the dim, like a popup's. */
  border-bottom: none;
  background: none;
}
.settings-title {
  font-family: var(--font-display);
  font-size: 1.15rem;
  letter-spacing: 0.05em;
  color: var(--gold-bright);
  text-shadow: 0 1px 3px rgba(0,0,0,0.8);
}
.settings-hbtn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  background: none;
  border: none;
  color: var(--gold-light);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  transition: color 0.12s ease;
}
.settings-hbtn:hover { color: var(--gold-bright); }
/* `display:flex` above overrides the HTML `hidden` attribute's display:none —
   restore it so the back button actually hides on the menu view. */
.settings-hbtn[hidden] { display: none; }
.settings-back  { left: 8px; font-size: 1.9rem; }
.settings-close { right: 8px; }

/* ── v199: SECTION CARDS ──────────────────────────────────────────────────
   Each Settings section is ONE popup frame. The frame, its art, the no-art
   fallback and the lift shadow are the POPUP's (see .pp-popup-panel and the
   shared rule groups below) — this only sets the spacing and the row treatment
   on top of it, so there is no second copy of that CSS anywhere. */
.settings-section {
  width: 100%;
  box-sizing: border-box;
  padding: var(--space-md) var(--space-lg);
  margin-bottom: var(--space-md);
}
.settings-section:last-child { margin-bottom: 0; }
/* The shared lift is a PALE halo, tuned to separate one popup from bright
   cabinet art. Stacked over a dim, four of them read as light bands in the
   gaps, so these cards take the documented dark-shadow option of the SAME rule
   (its own tunables — no second shadow implementation). */
#settings-modal .settings-section {
  --lr-sum-lift:      0.5;
  --lr-sum-lift-rgb:  0 0 0;     /* the rule's own "conventional dark shadow" option */
  --lr-sum-lift-blur: 14px;
}
/* The nine-slice art already draws a 26px inset of its own (frames keep their
   layout border — that is what insets the content), so the padding all but
   disappears when it lands. Measured on screen, not guessed: with 10px/16px the
   cards read as half-empty boxes. */
.settings-section.uipop-frame { padding: 0 2px 2px; }
.settings-section-title {
  font-family: var(--font-display);
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin: 0;
  padding-bottom: 2px;
}
/* Rows sit ON the frame: no card of their own, a hairline between them. */
.settings-section .settings-menu-row {
  padding: 11px var(--space-sm);
  border-bottom: 1px solid rgba(245, 166, 35, 0.16);
  border-radius: var(--radius-md);
}
.settings-section .settings-menu-row:last-child { border-bottom: none; }

/* Views (menu + sub-panels) — the scrollable body of the card */
.settings-view {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: var(--space-sm) 0;
  /* v199: the frames ARE the cards now, so a scrollbar track cutting down the
     side of them is chrome the popups never show. Scrolling is unchanged. */
  scrollbar-width: none;
}
.settings-view::-webkit-scrollbar { width: 0; height: 0; }
/* v199: the framed section inside provides the inset now, so the panel only
   keeps a small breathing gap (it used to add a full --space-lg on top). */
.settings-panel { padding: var(--space-sm) 0; }

/* Menu rows — icon · label · chevron */
.settings-menu-row {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: 14px var(--space-lg);
  background: none;
  border: none;
  border-bottom: 1px solid rgba(245,166,35,0.14);
  color: var(--gold-light);
  font-family: var(--font-body);
  font-size: 0.95rem;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s ease;
}
.settings-menu-row:last-child { border-bottom: none; }
.settings-menu-row:hover,
.settings-menu-row:active { background: rgba(245,166,35,0.08); }
.settings-row-icon  { font-size: 1.1rem; width: 1.5rem; text-align: center; }
.settings-menu-row .settings-row-label { flex: 1; color: var(--gold-light); font-weight: 600; }
.settings-row-chev  { color: var(--gold); font-size: 1.2rem; opacity: 0.7; }

/* Sound sub-panel toggle row */
.settings-toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  color: var(--gold-light);
  font-size: 0.95rem;
}
.settings-toggle-row .settings-row-label { color: var(--gold-light); }

/* Game-rules blocks (each its own data-rules unit for the CMS) */
.settings-rules-block { margin-bottom: var(--space-lg); }
.settings-rules-block:last-child { margin-bottom: 0; }
.settings-rules-title {
  font-family: var(--font-display);
  font-size: 1rem;
  letter-spacing: 0.03em;
  color: var(--gold-bright);
  margin-bottom: var(--space-sm);
  padding-bottom: 4px;
  border-bottom: 1px solid rgba(245,166,35,0.2);
}
.settings-rules-body { color: #f4e3c0; font-size: 0.88rem; line-height: 1.55; }
.settings-rules-body strong { color: var(--gold-bright); }
.settings-rules-list { list-style: none; display: flex; flex-direction: column; gap: var(--space-sm); }
.settings-rules-list li { position: relative; padding-left: 16px; }
.settings-rules-list li::before {
  content: '★'; position: absolute; left: 0; top: 2px; font-size: 0.7rem; color: var(--gold);
}
/* v196: Jackpot Strike prize table (server prize plan), below the rules text */
.js-prize-plan { margin-top: var(--space-md); }
.js-prize-plan[hidden] { display: none; }
.js-pt-title { color: var(--gold-bright); font-size: 0.9rem; margin: 0 0 var(--space-sm); letter-spacing: 0.04em; }
.js-pt-table { width: 100%; border-collapse: collapse; font-size: 0.82rem; color: #f4e3c0; }
.js-pt-table th, .js-pt-table td { text-align: left; padding: 6px 4px; border-bottom: 1px solid rgba(245, 166, 35, 0.18); vertical-align: top; }
.js-pt-table thead th { color: var(--gold); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; }
.js-pt-table tbody th { font-weight: 700; color: var(--gold-bright); }
.js-pt-odds { white-space: nowrap; text-align: right; }
.js-pt-table thead th:last-child { text-align: right; }
.js-pt-extra { display: block; font-size: 0.72rem; opacity: 0.8; }
.js-pt-notes { font-size: 0.75rem; opacity: 0.8; margin-top: var(--space-sm); }

/* Stub / coming-soon */
.settings-coming-soon {
  color: var(--text-secondary);
  font-size: 0.9rem;
  text-align: center;
  padding: var(--space-lg) 0;
}

/* About */
.settings-about { text-align: center; padding: var(--space-lg) 0; }
.settings-about-name {
  font-family: var(--font-display);
  font-size: 1.3rem;
  color: var(--gold-bright);
  text-shadow: 0 0 12px var(--gold-glow);
}
.settings-about-ver { font-family: var(--font-mono); font-size: 0.85rem; color: var(--text-secondary); margin-top: 6px; }

/* ============================================================
   MINI GAME MODAL
   Slides up from the bottom as a 90% height bottom sheet.
   All three mini games live inside .mini-game-content.
   ============================================================ */

@keyframes miniGameSlideUp {
  from { transform: translateY(100%); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}
@keyframes miniGameSlideDown {
  from { transform: translateY(0);    opacity: 1; }
  to   { transform: translateY(100%); opacity: 0; }
}

/* Dimmed backdrop */
.mini-game-modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  z-index: 4000;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

/* The sliding card */
.mini-game-modal-inner {
  width: 92%;
  max-width: 480px;
  height: 90vh;
  height: 90dvh;
  background: var(--grad-card);
  border: 1px solid var(--border-teal);
  border-bottom: none;
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  box-shadow: var(--shadow-teal), 0 -8px 40px rgba(0, 0, 0, 0.6);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: miniGameSlideUp 0.35s cubic-bezier(0.32, 0.72, 0, 1) both;
}
.mini-game-modal-inner--closing {
  animation: miniGameSlideDown 0.3s ease-in forwards !important;
}

/* Header bar */
.mini-game-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--border-teal);
  flex-shrink: 0;
}
.mini-game-title {
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--text-white);
  letter-spacing: 0.04em;
}
.mini-game-close-btn {
  background: none;
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-full);
  color: var(--text-secondary);
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  font-size: 0.85rem;
  transition: border-color 0.2s, color 0.2s;
  flex-shrink: 0;
}
.mini-game-close-btn:hover { border-color: var(--teal); color: var(--teal-light); }

/* Scrollable content area */
.mini-game-content {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: var(--space-lg);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
}

/* Loading state */
.mini-game-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 1;
  gap: var(--space-md);
  color: var(--text-secondary);
  font-size: 0.88rem;
}
.mini-game-loading-icon {
  font-size: 3rem;
  animation: glowPulseTeal 1.5s ease-in-out infinite;
}
/* v193: "Bonus game waiting" offer. .btn-primary carries flex:1, which in this
   column would stretch the button to the full modal height. */
.mg-pending { text-align: center; padding: var(--space-lg); }
.mg-pending .mga-action-btn { flex: 0 0 auto; min-width: 200px; }
/* v194: "2 spins left" / "1 hand left", from the server's `remaining` */
.mg-pending-left { margin: 0; color: var(--gold); font-weight: 700; letter-spacing: 0.04em; }
/* --- v200: Jackpot Ticket celebration. Frame, buttons, headline/body type,
   art box and close control are all the SHARED popup rules; these are the only
   lines this popup adds of its own. --- */
/* v201: ABOVE the mini-game modal (4000) too. Poker King now grants fragments,
   so the celebration can fire while its cabinet is open — at 1400 it opened
   BEHIND the cabinet and the player never saw it. Above everything: game
   popups 1200, settings 3000, mini-game modal 4000. */
#jackpot-modal { z-index: 4100; }
.jt-panel { position: relative; }
.jt-next-draw {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  color: var(--gold);
  letter-spacing: 0.04em;
}
.jt-next-draw[hidden] { display: none; }
.jt-held {
  font-size: 0.8rem;
  color: var(--teal-light);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-full);
  padding: 4px 14px;
}
.jt-hint { margin: 0; font-size: 0.76rem; color: rgba(255,255,255,0.72); line-height: 1.35; }
.mg-pending-left[hidden] { display: none; }

/* Error state */
.mini-game-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
  text-align: center;
  padding: var(--space-xl);
  flex: 1;
  justify-content: center;
}
.mini-game-error-icon { font-size: 2.5rem; }
.mini-game-error-msg  { color: var(--crimson-light); font-size: 0.88rem; line-height: 1.5; }

/* Shared instruction line */
.mga-instruction {
  font-size: 0.88rem;
  color: var(--text-secondary);
  text-align: center;
  letter-spacing: 0.04em;
}

/* Result panel — appears after the game animation */
.mini-game-result {
  display: flex;       /* toggled from none → flex by JS */
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-lg);
  text-align: center;
  background: rgba(0, 196, 167, 0.06);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-xl);
  width: 100%;
  animation: screenSlideUp 0.3s ease-out both;
}
.mini-game-result-icon  { font-size: 2.5rem; }
.mini-game-result-label {
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--text-white);
  letter-spacing: 0.04em;
}
.mini-game-result-prize {
  font-family: var(--font-mono);
  font-size: 1.7rem;
  color: var(--gold-bright);
  text-shadow: 0 0 12px var(--gold-glow);
}

/* Gold collect / close button */
.mini-game-collect-btn {
  background: var(--grad-gold);
  border: 1px solid var(--gold);
  border-radius: var(--radius-lg);
  color: #1a0800;
  font-family: var(--font-display);
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  padding: 13px var(--space-xl);
  cursor: pointer;
  box-shadow: var(--shadow-gold);
  transition: filter 0.15s ease, transform 0.1s ease;
  min-width: 150px;
}
.mini-game-collect-btn:active { transform: scale(0.97); filter: brightness(0.9); }

/* Generic styled action button (spin, etc.) */
.mga-action-btn {
  padding: 12px var(--space-xl);
  font-family: var(--font-display);
  font-size: 0.9rem;
  letter-spacing: 0.06em;
  border-radius: var(--radius-lg);
  border: none;
  cursor: pointer;
  transition: filter 0.15s ease, transform 0.1s ease;
}
.mga-action-btn.btn-primary { background: var(--grad-teal); color: var(--bg-deep); box-shadow: var(--shadow-teal); }
.mga-action-btn.btn-crimson { background: var(--grad-crimson); color: var(--text-white); }
.mga-action-btn:active:not(:disabled) { transform: scale(0.96); }
.mga-action-btn:disabled { opacity: 0.42; cursor: default; transform: none !important; }

/* ── Game 1: Pick a Door ──────────────────────────────────── */
.mga-pick-a-door {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
  width: 100%;
}
.mga-doors {
  display: flex;
  gap: var(--space-md);
  justify-content: center;
}
.mga-door {
  background: var(--bg-card);
  border: 2px solid var(--border-teal);
  border-radius: var(--radius-lg);
  width: 88px;
  height: 120px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  cursor: pointer;
  transition: transform 0.15s ease, border-color 0.3s ease, background 0.3s ease, box-shadow 0.3s ease;
  user-select: none;
  -webkit-user-select: none;
}
.mga-door:active:not(:disabled) { transform: scale(0.93); }
.mga-door-icon { font-size: 2rem; transition: transform 0.35s ease; }
.mga-door-num  { font-family: var(--font-mono); font-size: 0.78rem; color: var(--text-secondary); }
.mga-door--opened .mga-door-icon { transform: scale(1.2); }
.mga-door--win {
  border-color: var(--gold);
  background: linear-gradient(135deg, #2e2000, #6a4a00);
  box-shadow: 0 0 18px var(--gold-glow);
}
.mga-door--empty { border-color: rgba(255,255,255,0.07); background: var(--bg-mid); opacity: 0.55; }

/* ── Game 2: Lucky Reel — 5 reels × 3 rows (PLACEHOLDER ART) ──────────────────
   Deliberately provisional: dashed frame + emoji symbols so nobody mistakes
   this for finished art. Real art / CMS slots come later. */
.mga-lucky-reel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  width: 100%;
}
.mga-lr-note {
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-secondary);
  opacity: 0.75;
  border: 1px dashed var(--border-teal);
  border-radius: var(--radius-sm);
  padding: 2px 8px;
}
/* Spins-left readout — the number is ALWAYS server-supplied. */
.mga-lr-spins {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
}
.mga-lr-spins-label {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
}
.mga-lr-spins-val {
  font-family: var(--font-mono);
  font-size: 1.05rem;
  color: var(--gold-bright);
}

/* The grid: 5 reel WINDOWS side by side, each 3 cells tall.
   --lr-cell is the single source of truth for the cell step: the JS measures the
   rendered cell, so the responsive size below needs no JS counterpart. */
.mga-lr-grid {
  /* Cell geometry. Authoring size is 266 × 281 (slightly TALLER than wide) —
     these are ratios, not fixed pixels: change --lr-cell-w and everything else
     follows. The symbol box inside is square-ish and INSET, so the drum shows
     above and below each symbol, which is what sells the rotation. */
  --lr-cell-w: 46px;
  --lr-cell-h: calc(var(--lr-cell-w) * 281 / 266);
  /* Symbol box: 256 square inside the 266 × 281 cell → (266−256)/2 and
     (281−256)/2. Expressed against each axis's own var because a PERCENTAGE
     padding would resolve against the WIDTH on both axes. */
  --lr-sym-inset-x: calc(var(--lr-cell-w) * 5 / 266);
  --lr-sym-inset-y: calc(var(--lr-cell-h) * 12.5 / 281);
  /* ── Cylindrical drum lighting — TUNABLE, and zeroable ──────────────────────
     Set any of these to 0 to dial the effect down; set all three to 0 (or drop
     the .mga-lr-drum element) to remove it entirely once the cabinet art carries
     its own reel-housing shading. Nothing structural depends on them. */
  /* RETUNED in v146 for the real drum_tile, which is a LIGHT cream surface —
     these were tuned against a dark CSS box and read almost flat on the art.
     A light surface needs MORE edge falloff to curve, and the white centre
     highlight became pointless (it washed an already-light surface toward
     white and flattened it), so the shine is mostly retired and the darkening
     carries the cylinder. */
  --lr-drum-dark:     0.62;   /* shade at the very top/bottom edge (was 0.58) */
  --lr-drum-dark-mid: 0.30;   /* shade partway in (was 0.24) */
  --lr-drum-shine:    0.04;   /* centre highlight (was 0.09) */
  /* ── FUTURE: patterned drum surface ────────────────────────────────────────
     Set --lr-drum-texture to a url() and the strip picks it up as a repeating
     background that scrolls WITH the symbols (see .mga-lr-strip below).
     ⚠️ It MUST tile at exactly one cell height — background-size is already
     `100% var(--lr-cell-h)`. Any other tile height drifts against the symbols as
     the strip scrolls, because the strip's travel is an exact multiple of the
     cell step. No texture today; the structure is ready for one. */
  --lr-drum-texture:  none;
  display: flex;
  gap: 4px;
  padding: 6px;
  border: 2px dashed var(--border-teal);      /* dashed = placeholder */
  border-radius: var(--radius-md);
  background: var(--bg-mid);
  box-shadow: inset 0 0 14px rgba(0,0,0,0.5);
  max-width: 100%;
}
/* The WINDOW — exactly 3 cells tall, clipping the strip that scrolls inside. */
.mga-lr-reel {
  position: relative;
  width:  var(--lr-cell-w);
  height: calc(3 * var(--lr-cell-h));
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: rgba(0,0,0,0.28);
}
/* THE PAYLINE — a band on the WINDOW's middle third. The cells scroll past; the
   paying row does not move, so the band marks it unambiguously at all times. */
.mga-lr-reel::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: var(--lr-cell-h);
  height: var(--lr-cell-h);
  /* Reads on BOTH grounds: a solid gold rule carries the dark placeholder, and
     the dark inset hairline carries the LIGHT cream drum art, where gold-on-cream
     had almost no contrast and the paying row became ambiguous. Fill stays low —
     the payline symbols must be the most legible things on screen. */
  border-top: 2px solid rgba(255, 199, 64, 0.95);
  border-bottom: 2px solid rgba(255, 199, 64, 0.95);
  background: rgba(255, 199, 64, 0.06);
  /* Delineation comes from the EDGES, not more fill: the drum's centre highlight
     sits directly under this band, and raising the fill would wash out the very
     symbols that have to stay the most legible on screen. A soft edge glow keeps
     the paying row unmistakable at rest AND mid-spin without touching contrast. */
  box-shadow: 0 0 7px rgba(255, 199, 64, 0.45),
              inset 0 0 0 1px rgba(60, 30, 5, 0.55);
  pointer-events: none;
  z-index: 2;
}
/* The STRIP — a tall column of symbols translated upward by the spin engine.
   z-index:0 puts it explicitly at the BOTTOM of the window's stack. The blur
   classes apply a `filter`, which creates a stacking context on the strip; being
   positioned with an explicit z-index keeps that context from ever painting over
   the drum lighting or the payline band above it.
   The background-* trio is the drum-TEXTURE seam: unset today (--lr-drum-texture
   is `none`), and sized to exactly one cell so a future texture scrolls WITH the
   symbols instead of drifting against them. */
.mga-lr-strip {
  position: relative;
  z-index: 0;
  display: flex;
  flex-direction: column;
  will-change: transform;
  transition: filter 140ms linear;   /* blur → crisp as the reel stops */
  background-image:  var(--lr-drum-texture, none);
  background-repeat: repeat-y;
  background-size:   100% var(--lr-cell-h);   /* period = EXACTLY one cell */
}

/* ── CYLINDRICAL LIGHTING ─────────────────────────────────────────────────────
   A STATIC overlay: darkened at the top and bottom edges with a soft highlight
   across the vertical centre — the light a real cylinder catches from the front.
   It does NOT scroll; the strip moves beneath it, which is what sells the curve.
   Pure CSS gradients, no images.

   LAYER ORDER inside .mga-lr-reel:
     z 0 — .mga-lr-strip   (scrolls, blurs)
     z 1 — .mga-lr-drum    (this: static lighting)
     z 2 — .mga-lr-reel::after (the PAYLINE band — stays on top, so the paying
                                row is never in doubt)
   Kept as its own element with its own class so the art pass can weaken it via
   the custom properties above, or delete the element, without touching any of
   the reel structure or the spin engine. */
.mga-lr-drum {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    /* centre highlight — kept subtle so payline symbols stay legible, not washed */
    linear-gradient(to bottom,
      rgba(255,255,255,0)                        30%,
      rgba(255,255,255,var(--lr-drum-shine))     50%,
      rgba(255,255,255,0)                        70%),
    /* edge shading — the top and bottom rows are DIMMED, never obscured */
    linear-gradient(to bottom,
      rgba(0,0,0,var(--lr-drum-dark))            0%,
      rgba(0,0,0,var(--lr-drum-dark-mid))       18%,
      rgba(0,0,0,0)                             38%,
      rgba(0,0,0,0)                             62%,
      rgba(0,0,0,var(--lr-drum-dark-mid))       82%,
      rgba(0,0,0,var(--lr-drum-dark))          100%);
}
.mga-lr-cell {
  box-sizing: border-box;
  width:  var(--lr-cell-w);
  height: var(--lr-cell-h);
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
  line-height: 1;
  border-bottom: 1px solid rgba(255,255,255,0.06);
  user-select: none;
}
/* A cell showing CMS ART. background-size:contain keeps each symbol's own
   aspect ratio — the uploaded art is trimmed to content (169×181 … 248×224),
   not the square 256×256 the spec assumed, so a fixed square would distort every
   symbol by a different amount. content-box + the insets above hold it inside
   the cell rather than filling it. Cells WITHOUT art keep their emoji text. */
.mga-lr-cell--art {
  padding: var(--lr-sym-inset-y) var(--lr-sym-inset-x);
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  background-origin: content-box;
}

/* ART MODE — once the CMS supplies reel art, the placeholder chrome steps back:
   the drum tile provides the cell seams, so the per-cell rule and the dashed
   "this is placeholder" frame both go. Absence of art keeps the old look. */
.mga-lr-grid--art { border-style: solid; border-color: transparent; background: transparent; box-shadow: none; }
.mga-lr-grid--art .mga-lr-cell { border-bottom: 0; }

/* ── BARE MODAL (Lucky Reel) ──────────────────────────────────────────────────
   The cabinet IS the interface, so the modal gives it the whole frame: no title,
   no panel padding, no card background showing around the art. The CLOSE BUTTON
   is deliberately untouched — same place, same styling as every other mini game. */
.mini-game-modal--bare .mini-game-title { display: none; }
.mini-game-modal--bare .mini-game-header {
  position: absolute; top: 0; left: 0; right: 0;
  z-index: 6;                       /* above the cabinet AND the summary popup */
  background: none; border: none; padding: 6px 8px;
  pointer-events: none;             /* only the button itself stays tappable */
}
.mini-game-modal--bare .mini-game-close-btn { pointer-events: auto; }
/* FIT THE MACHINE TO THE VIEWPORT — the cabinet scales as ONE unit to the space
   available and NOTHING scrolls: not the page, not the modal, not the content.
   The cabinet is very tall (aspect ≈ 0.48), so height is what binds on a phone;
   the machine takes 100% of this box's height and derives its width from that. */
.mini-game-modal--bare {
  /* ── TUNABLES that the MODAL itself needs (the rest of the set is on
     .mga-lr-machine--art below; these four have to sit on an ancestor because
     the scrim and the entrance timing are painted by the modal, not the
     machine — and custom properties inherit downward only). ── */
  --lr-machine-fit:      0.90;   /* ← cabinet size as a fraction of the fitted
                                       box: 0.90 leaves ~5% of the height as
                                       margin top and bottom, through which the
                                       game underneath shows. Lower = more game. */
  --lr-scrim:            0.62;   /* ← how far the game behind is dimmed (0 = none) */
  --lr-enter-ms:         380ms;  /* ← entrance duration */
  --lr-enter-from:       0.86;   /* ← scale it arrives from */
  --lr-enter-overshoot:  1.028;  /* ← overshoot before it settles */

  overflow: hidden;
  padding: 0;
  align-items: center;
  justify-content: center;
  /* NO panel, border or background behind the cabinet — the art has its own
     frame and a second one would read as doubled. The dim is a separate layer
     so it can fade on OPACITY alone. */
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* The dim. A full-viewport layer of its own, so the fade animates opacity and
   touches no layout. Kept light enough that the player can still see they are
   in Scratch Rush or Power Play behind it. */
.mini-game-modal--bare::before {
  content: '';
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, var(--lr-scrim));
  animation: mgaLrScrimIn var(--lr-enter-ms) ease forwards;   /* see the note above */
  pointer-events: none;          /* the modal itself already swallows taps */
}
.mini-game-modal--bare .mini-game-modal-inner { position: relative; z-index: 1; }
.mini-game-modal--bare .mini-game-modal-inner {
  background: none; border: none; box-shadow: none; padding: 0;
  width: 100%;
  max-width: 100vw;
  height: 100dvh;                   /* dvh: excludes mobile browser chrome */
  max-height: 100dvh;
  overflow: hidden;
  display: flex; flex-direction: column;
  animation: none;                  /* the slide-in would reintroduce overflow */
}
.mini-game-modal--bare .mini-game-content {
  padding: 0;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.mini-game-modal--bare .mga-lucky-reel {
  height: 100%; width: 100%;
  min-height: 0;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  /* Becomes the sizing reference for the machine below, so the cabinet can take
     the SMALLER of "all the height" and "what the width allows" — the exact
     fit-inside rule, resolved in CSS with no JS and no scrollbars. */
  container-type: size;
}
.mini-game-modal--bare .mga-lucky-reel { gap: 0; }
.mini-game-modal--bare .mga-lr-note { display: none; }

/* ── BUTTONS ──────────────────────────────────────────────────────────────────
   The three spin states are custom properties, so :active / :disabled swap art
   with no JS. The label stays LIVE TEXT over the art — never baked in. */
.mga-spin-btn.mga-spin-btn--art {
  background: var(--lr-btn) center / 100% 100% no-repeat;
  border: none;
  box-shadow: none;
  aspect-ratio: 628 / 410;          /* authoring ratio of spin_button */
  width: 148px;
  max-width: 60%;
  height: auto;
  color: var(--text-primary);
  text-shadow: 0 1px 3px rgba(0,0,0,0.85);
}
/* ⚠️ THE PRESS-JUMP BUG: this rule used to say `transform: none`, added back when
   the button was statically laid out, purely to cancel the old press-scale. Once
   the button became absolutely positioned with `left:50%; transform:translateX(-50%)`,
   killing the transform also killed the CENTERING — so tapping shot it right by
   half its own width. The press must change APPEARANCE ONLY: same transform, same
   box, same position. */
.mga-spin-btn.mga-spin-btn--art:active:not(:disabled) {
  background-image: var(--lr-btn-pressed);
}
.mga-spin-btn.mga-spin-btn--art:disabled {
  background-image: var(--lr-btn-disabled);
  /* TEMPORARY: the three states are currently the SAME upload, so this dim is
     what makes "inert" readable while the reels settle. Drop the filter once
     distinct disabled art lands, if it then reads as double-dimmed. */
  opacity: 1;
  filter: grayscale(0.55) brightness(0.72);
}
.mini-game-close-btn--art {
  background: center / contain no-repeat;
  border: none;
  font-size: 0;                     /* the glyph is in the art */
}

/* ══ CABINET LAYOUT ═══════════════════════════════════════════════════════════
   EVERY alignment value Mattias will want to nudge lives in this one block.
   The aperture figures are MEASURED from cabinet_marquee's alpha channel, not
   guessed — re-measured for the v148 art (1536×3194): the transparent hole is
   x 91..1443, y 993..1836, which is exactly 3 cells tall (3.004 × 281) and
   5.083 cells wide. The art is authored to the 266 × 281 cell grid. */
.mga-lr-machine--art {
  /* ── the cabinet itself ── */
  --lr-cab-aspect:      1536 / 3194;  /* opaque-body aspect of cabinet_marquee   */
  /* ── reel aperture, % of the cabinet box (MEASURED) ── */
  --lr-ap-top:              31.090;   /* aperture top edge,    % of height       */
  --lr-ap-height:           26.425;   /* aperture height,      % of height       */
  --lr-ap-centre:              50;    /* aperture centre line, % of width        */
  --lr-reel-nudge-x:          0px;    /* ← nudge the whole grid horizontally     */
  --lr-reel-nudge-y:          0px;    /* ← nudge the whole grid vertically       */
  --lr-reel-scale:            1;      /* ← fine-scale the grid inside the hole   */
  --lr-payband-weight:   0.55cqw;     /* ← payline rule thickness                */
  --lr-payband-tint:        0.20;     /* ← how strongly the paying row is lit    */
  --lr-payband-chevron:     2.6;      /* ← edge marker size, % of cabinet width  */
  /* ── close button, pinned to the cabinet's own top-left corner ── */
  --lr-close-top:           0.55;     /* % of cabinet height                     */
  --lr-close-left:          1.20;     /* % of cabinet width                      */
  --lr-close-size:          3.10;     /* % of cabinet width                      */
  --lr-close-min:           30px;     /* ← floor, so it stays tappable           */
  /* ── how much DRUM shows around each symbol ────────────────────────────────
     1 = symbol fills the cell edge to edge (no drum visible); lower = more drum
     above and below, which is what communicates rotation. */
  --lr-sym-scale:          0.62;      /* ← symbol size as a fraction of the cell */
  /* ── marquee (the lit JACKPOT sign) ── */
  --lr-plaque-top:          21.5;     /* % of height                              */
  --lr-plaque-height:        7.0;
  --lr-plaque-size:          6.0;     /* figure size, % of cabinet WIDTH          */
  /* ── console plaques the ART provides (labels + chest icon are BAKED IN, so
        only VALUES are drawn here — never a second label) ── */
  --lr-plaque-row-top:      59.2;     /* % of height, both plaques               */
  --lr-plaque-row-height:    6.0;
  /* The dark bar between the plaques now holds SPIN, so the per-spin value goes
     back onto the chest plaque, pinned LOW so it clears the baked "PRIZE"
     caption above it rather than covering the icon's face. */
  --lr-prize-plaque-left:    6.0;     /* chest plaque, % of width                */
  --lr-prize-plaque-width:  23.0;
  --lr-prize-val-drop:      3.4;      /* value baseline, % of height below plaque top */
  --lr-spins-plaque-left:   70.4;     /* SPINS LEFT plaque, % of width           */
  --lr-spins-plaque-width:  23.0;
  --lr-plaque-val-size:      4.2;     /* SPINS LEFT value, % of cabinet width    */
  --lr-prize-val-size:       3.6;     /* PRIZE value, % of cabinet width         */
  /* ── SPIN button — ABOVE the prize list ── */
  /* SPIN sits in the empty dark bar BETWEEN the two plaques, vertically centred
     on them. Width is capped so it can never reach either plaque at any scale:
     the gap between them runs from --lr-prize-plaque-left+width to
     --lr-spins-plaque-left, i.e. 29%..70.4%, so 36% centred leaves ~2.7% clear
     on each side. */
  --lr-btn-top:             58.4;     /* % of height — aligned with the plaques  */
  --lr-btn-width:             36;     /* % of width                              */
  /* ── PRIZE LIST — takes the empty purple area below the button ── */
  --lr-list-top:            66.5;     /* % of height — SPIN vacated this area    */
  --lr-list-bottom:         10.0;     /* % of height, up from the cabinet base   */
  --lr-list-inset:           6.0;     /* % of width                              */
  --lr-list-scale-h:        0.90;     /* ← frame height, fraction of its slot    */
  --lr-list-scale-w:        0.80;     /* ← frame width,  fraction of its slot    */
  --lr-legend-size:          2.9;     /* prize-list type, % of cabinet width     */
  --lr-legend-sym:           3.2;     /* prize-list symbol size, % of width      */
  --lr-legend-row-gap:      0.28;     /* space between rows, % of height         */
  --lr-legend-cap:          1.55;     /* type ceiling, % of cabinet HEIGHT       */
  --lr-legend-sym-cap:      1.95;     /* symbol ceiling, % of cabinet HEIGHT     */
  /* ── prize-list frame slices (cabinet_prizelist is 1176 × 739) ── */
  --lr-plist-top:          150;       /* SOURCE slice px — the "PRIZE LIST" cap  */
  --lr-plist-side:          85;
  --lr-plist-bottom:        95;
  /* ONE knob for the rendered cap depth (the SIDE cap, % of cabinet width);
     top and bottom derive from the SLICE RATIO above, so all four sides
     compress the art by the SAME factor. v150 set the three widths
     independently — 150→54.5 (×0.363) on top but 85→39 (×0.459) on the sides —
     which squashed the corner art more vertically than horizontally. */
  --lr-plist-cap:           3.88;

  position: relative;
  display: block;
  aspect-ratio: var(--lr-cab-aspect);
  container-type: size;               /* lets children size off the cabinet box */
  /* FIT THE WHOLE MACHINE IN THE VIEWPORT — scales as ONE unit, never scrolls.
     cqh/cqw here resolve against .mga-lucky-reel (the nearest ancestor
     container), so this is literally "the smaller of the available height and
     the height the available width permits". Aspect is preserved either way. */
  /* Fit as before, then INSET by --lr-machine-fit so the cabinet no longer
     touches the screen edges and the game it was won in shows around it. Still
     ONE unit, aspect preserved, centred; if it does not fit it scales down. */
  height: calc(min(100cqh, calc(100cqw * 3194 / 1536)) * var(--lr-machine-fit));
  width: auto;
  margin: 0 auto;
  /* ⚠️ TRANSFORM + OPACITY ONLY. v148 had to kill the modal's slide-in because it
     animated layout and broke the no-scroll fit; a scale/opacity animation
     changes no box, so the fit holds throughout. Container query units resolve
     against the LAYOUT size, so the cabinet's internals do not reflow while it
     scales — it arrives as one piece. */
  /* fill-mode FORWARDS, never `both`. `both` also applies the 0% frame BEFORE the
     animation runs — and frame 0 is scale(0.86) + opacity 0. Any situation where
     the animation does not advance (a hidden tab does exactly this: playState
     stays "running" while currentTime never leaves 0, and an animations.css that
     failed to load would do it permanently) would then leave the whole cabinet
     INVISIBLE and 14% undersized. With `forwards` the resting state IS the
     default — correct size, fully opaque — so a non-running animation degrades to
     "no entrance" instead of "no mini game". */
  animation: mgaLrMachineIn var(--lr-enter-ms) cubic-bezier(0.22, 1.16, 0.36, 1) forwards;
  will-change: transform, opacity;
}

/* The cabinet art sits above the reels so the ARTWORK defines the window shape. */
.mga-lr-machine--art .mga-lr-cabinet {
  position: absolute; inset: 0; z-index: 2;
  background: center / 100% 100% no-repeat;
  pointer-events: none;
  /* drop-shadow (not box-shadow) so it follows the cabinet's own silhouette —
     rounded corners and all — instead of drawing a rectangle behind the art. */
  filter: drop-shadow(0 1.4cqw 2.6cqw rgba(0, 0, 0, 0.62))
          drop-shadow(0 0.3cqw 0.8cqw rgba(0, 0, 0, 0.45));
}

/* ── REELS IN THE APERTURE ────────────────────────────────────────────────────
   Cell height = aperture ÷ 3, so THREE FULL ROWS fill the hole exactly; width
   follows the authored 266:281. cqh = 1% of the cabinet height, so this holds at
   every size with no JS. */
.mga-lr-machine--art .mga-lr-grid {
  --lr-cell-h: calc(var(--lr-ap-height) * 1cqh / 3 * var(--lr-reel-scale));
  --lr-cell-w: calc(var(--lr-cell-h) * 266 / 281);
  position: absolute;
  z-index: 1;
  top: calc(var(--lr-ap-top) * 1cqh + var(--lr-reel-nudge-y));
  left: calc(var(--lr-ap-centre) * 1cqw + var(--lr-reel-nudge-x));
  transform: translateX(-50%);
  padding: 0; border: 0; background: none; box-shadow: none; gap: 0;
}
/* Symbol inset — how much drum shows. Derived from --lr-sym-scale so one knob
   controls it on both axes. */
.mga-lr-machine--art .mga-lr-cell--art {
  padding: calc(var(--lr-cell-h) * (1 - var(--lr-sym-scale)) / 2)
           calc(var(--lr-cell-w) * (1 - var(--lr-sym-scale)) / 2);
}

/* ── PAYLINE ──────────────────────────────────────────────────────────────────
   Its OWN element, a sibling of the cabinet at a HIGHER z-index — deliberately
   NOT a pseudo-element inside the grid, which is how it got trapped under the
   cabinet twice: any stacking context on an ancestor could swallow it. As a
   direct child of the machine it cannot be. Sits exactly on the middle third of
   the aperture. */
.mga-lr-machine--art .mga-lr-payband {
  position: absolute;
  z-index: 4;                          /* above the cabinet art (z2) */
  pointer-events: none;
  top: calc((var(--lr-ap-top) + var(--lr-ap-height) / 3) * 1cqh + var(--lr-reel-nudge-y));
  height: calc(var(--lr-ap-height) * 1cqh / 3 * var(--lr-reel-scale));
  left: calc(var(--lr-ap-centre) * 1cqw + var(--lr-reel-nudge-x));
  width: calc(var(--lr-ap-height) * 1cqh / 3 * var(--lr-reel-scale) * 266 / 281 * 5);
  transform: translateX(-50%);
  /* ⚠️ WHY THIS IS A TINT AND NOT JUST LINES.
     The drum_tile art carries its OWN horizontal seam at every cell boundary, so
     a dark rule on the middle row — which is what v148 used — is visually
     identical to an ordinary cell divider. It rendered perfectly (right place,
     right size, topmost, opaque) and was still unreadable as a payline, because
     nothing distinguished it from the three other lines on screen.
     The row itself is therefore TINTED: a lit row is something a seam can never
     imitate, so the paying row differs from its neighbours in a way no divider
     does. Bright gold rules replace the brown ones, and chevrons (below) point
     at the row from both edges. */
  border-top: var(--lr-payband-weight) solid rgba(255, 208, 92, 0.98);
  border-bottom: var(--lr-payband-weight) solid rgba(255, 208, 92, 0.98);
  background: linear-gradient(rgba(255, 196, 74, var(--lr-payband-tint)),
                              rgba(255, 196, 74, var(--lr-payband-tint)));
  box-shadow: 0 0 2.2cqw rgba(255, 180, 60, 0.75),
              inset 0 0 1.6cqw rgba(255, 190, 70, 0.45);
}
/* Chevrons marking the paying row from both edges. A cell seam spans the reels;
   these sit OUTSIDE them, so the two can never be confused. */
.mga-lr-machine--art .mga-lr-payband::before,
.mga-lr-machine--art .mga-lr-payband::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 0; height: 0;
  border-top:    calc(var(--lr-payband-chevron) * 0.62cqw) solid transparent;
  border-bottom: calc(var(--lr-payband-chevron) * 0.62cqw) solid transparent;
  filter: drop-shadow(0 0 0.5cqw rgba(0,0,0,.6));
}
.mga-lr-machine--art .mga-lr-payband::before {
  right: 100%;
  transform: translateY(-50%);
  border-left: calc(var(--lr-payband-chevron) * 1cqw) solid rgba(255, 208, 92, 0.98);
}
.mga-lr-machine--art .mga-lr-payband::after {
  left: 100%;
  transform: translateY(-50%);
  border-right: calc(var(--lr-payband-chevron) * 1cqw) solid rgba(255, 208, 92, 0.98);
}

/* The old in-grid band is retired — kept off so it can never double up. */
.mga-lr-machine--art .mga-lr-reel::after { content: none; }

/* ── LIVE TEXT OVER THE ART — all server-fed, never baked ────────────────────
   The art already carries every LABEL (JACKPOT, PRIZE + chest, SPINS LEFT), so
   only VALUES are drawn. Drawing a second label was the v147 duplication bug. */
.mga-lr-machine--art .mga-lr-jackpot {
  position: absolute; z-index: 5;
  top: calc(var(--lr-plaque-top) * 1cqh);
  height: calc(var(--lr-plaque-height) * 1cqh);
  left: 0; right: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-size: calc(var(--lr-plaque-size) * 1cqw);
  color: #ffd76b;
  text-shadow: 0 0 1.2cqw rgba(255,180,60,.75), 0 0.2cqw 0.3cqw rgba(0,0,0,.8);
  pointer-events: none;
}
.mga-lr-machine--art .mga-lr-plaque {
  position: absolute; z-index: 5;
  top: calc(var(--lr-plaque-row-top) * 1cqh);
  height: calc(var(--lr-plaque-row-height) * 1cqh);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-size: calc(var(--lr-plaque-val-size) * 1cqw);
  color: #ffe9a8;
  text-shadow: 0 0.2cqw 0.3cqw rgba(0,0,0,.9);
  text-align: center; line-height: 1.05;
  pointer-events: none;
  overflow: hidden;
}
.mga-lr-machine--art .mga-lr-plaque--prize {
  left:  calc(var(--lr-prize-plaque-left) * 1cqw);
  width: calc(var(--lr-prize-plaque-width) * 1cqw);
  top:   calc((var(--lr-plaque-row-top) + var(--lr-prize-val-drop)) * 1cqh);
  height: calc((var(--lr-plaque-row-height) - var(--lr-prize-val-drop)) * 1cqh);
  align-items: flex-end;
  font-size: calc(var(--lr-prize-val-size) * 1cqw);
}
.mga-lr-machine--art .mga-lr-plaque--spins {
  left:  calc(var(--lr-spins-plaque-left) * 1cqw);
  width: calc(var(--lr-spins-plaque-width) * 1cqw);
}
/* The drawn "Spins left" caption is GONE — the plaque says it. */
.mga-lr-machine--art .mga-lr-spins-label { display: none; }

/* ── SPIN BUTTON — above the prize list ── */
.mga-lr-machine--art .mga-spin-btn.mga-spin-btn--art {
  position: absolute; z-index: 6;      /* over the plaque bar it now sits in */
  top: calc(var(--lr-btn-top) * 1cqh);
  /* ⚠️ CENTRED WITHOUT transform, ON PURPOSE — this is the root of the jump that
     was reported three times. `.mga-action-btn:disabled` (components.css) carries
     `transform: none !important`, and _lrSetSpinEnabled sets btn.disabled = true
     the instant a spin starts. The !important beat this rule whatever its
     specificity, translateX(-50%) was dropped, and the button sat half its own
     width (+176px) to the RIGHT for the whole spin, snapping back when it was
     re-enabled. That is why the v149 :active audit passed (it is not an :active
     rule) and why the v150 rest-vs-pointerdown measurement saw nothing (disabled
     is not set until AFTER the press). The left EDGE is now computed directly,
     so no transform is involved and no transform rule can move it. */
  left: calc(((var(--lr-prize-plaque-left) + var(--lr-prize-plaque-width) +
               var(--lr-spins-plaque-left)) / 2 - var(--lr-btn-width) / 2) * 1cqw);
  width: calc(var(--lr-btn-width) * 1cqw);
  max-width: none; height: auto; margin: 0;
  /* spin_button carries its own SPIN lettering; our live label would double it.
     Kept in the DOM for screen readers, not painted. */
  font-size: 0;
}

/* ── CLOSE BUTTON ─────────────────────────────────────────────────────────────
   Re-parented into the machine by _lrApplyArt, so it inherits the machine's
   scale and its entrance: it stays welded to the cabinet's top-left corner at
   every size and never drifts into the margin. Same element, same handler,
   same look — only its anchor changed. */
.mga-lr-machine--art .mini-game-close-btn.mga-lr-close {
  position: absolute;
  z-index: 10;                     /* above the cabinet AND the summary popup */
  top:  calc(var(--lr-close-top) * 1cqh);
  left: calc(var(--lr-close-left) * 1cqw);
  /* Scales with the cabinet, but never below --lr-close-min: at 3.1% of a
     360px-wide cabinet it computed to an 11px dot, far too small to hit. The
     floor keeps it the size Mattias already signed off while still growing on
     bigger screens. */
  width:  max(var(--lr-close-min), calc(var(--lr-close-size) * 1cqw));
  height: max(var(--lr-close-min), calc(var(--lr-close-size) * 1cqw));
  font-size: max(0.8rem, calc(var(--lr-close-size) * 0.42cqw));
  pointer-events: auto;
}

/* ── SESSION COMPLETE ─────────────────────────────────────────────────────────
   The machine is out of play behind the popup, so its interactive chrome is
   REMOVED from the rendered result — display:none, not merely disabled or
   dimmed, so nothing can paint over the summary whatever its z-index. */
/* ONLY the SPIN button goes. The machine itself must stay visible behind the
   popup, dimmed — the plaques and prize list are part of that backdrop and
   deliberately stay. The compound selector outranks the art rules that set
   `display` on the button, so this cannot be quietly overridden. */
.mga-lr-machine--ended .mga-spin-btn.mga-spin-btn--art,
.mga-lr-machine--ended .mga-spin-btn { display: none !important; }

/* ── PRIZE LIST — the empty purple area under the button ──────────────────────
   Sized to show a full seven-entry set WITHOUT scrolling; if a set ever runs
   longer the type shrinks to fit rather than a scrollbar appearing. */
/* Scaled to --lr-list-scale-h / -w of the slot it occupies and CENTRED in it —
   the leftover is split evenly on each side rather than left as a gap at one
   edge. Done with insets, not transform: scale() would smear the border-image
   caps, which must stay crisp. */
.mga-lr-machine--art .mga-reel-legend.mga-reel-legend--art {
  position: absolute; z-index: 5;
  top:    calc((var(--lr-list-top) +
           (100 - var(--lr-list-top) - var(--lr-list-bottom)) * (1 - var(--lr-list-scale-h)) / 2) * 1cqh);
  bottom: calc((var(--lr-list-bottom) +
           (100 - var(--lr-list-top) - var(--lr-list-bottom)) * (1 - var(--lr-list-scale-h)) / 2) * 1cqh);
  left:   calc((var(--lr-list-inset) +
           (100 - var(--lr-list-inset) * 2) * (1 - var(--lr-list-scale-w)) / 2) * 1cqw);
  right:  calc((var(--lr-list-inset) +
           (100 - var(--lr-list-inset) * 2) * (1 - var(--lr-list-scale-w)) / 2) * 1cqw);
  width: auto; max-width: none; min-height: 0;
  overflow: hidden;                    /* never a scrollbar */
  display: flex; flex-direction: column;
  border-style: solid;
  /* The SOURCE slice values are unchanged — the caps still map to the same
     pixels of cabinet_prizelist. But the RENDERED cap depth is measured against
     the cabinet, so on a frame that is now 20% narrower the caps would be ~25%
     oversized relative to it. Scaling the border-width by the frame's own scale
     keeps the frame's proportions identical to before, just smaller. */
  /* Widths derived from the slices, so every side compresses by one factor and
     the corner caps stay square. */
  border-width: calc(var(--lr-plist-cap) * var(--lr-list-scale-w) * var(--lr-plist-top)    / var(--lr-plist-side) * 1cqw)
                calc(var(--lr-plist-cap) * var(--lr-list-scale-w) * 1cqw)
                calc(var(--lr-plist-cap) * var(--lr-list-scale-w) * var(--lr-plist-bottom) / var(--lr-plist-side) * 1cqw);
  border-image-slice: var(--lr-plist-top) var(--lr-plist-side) var(--lr-plist-bottom) fill;
  /* ⚠️ NOT `round`. `round` applies to the VERTICAL edges and to the middle, and
     tiles a WHOLE number of times: at the rendered size the scaled side tile is
     494 × (39/85) ≈ 227px against a ~346px side run, so round(1.52) = 2 TILES and
     the tile's own top edge redrew straight across the middle of the panel — the
     doubled frame, plus the second inner edge on each side. It rounded to 1 tile
     at smaller sizes, which is why it stayed hidden until the panel grew.
     `stretch` never tiles, so exactly ONE frame draws at any size. */
  border-image-repeat: stretch;
  background: none;
  padding: 0.6cqw 2.2cqw 0.4cqw;      /* keeps rows and amounts off the frame */
}
.mga-reel-legend.mga-reel-legend--art .mga-legend-title  { display: none; }
.mga-reel-legend.mga-reel-legend--art .mga-legend-status {
  color: #d9c9ff; font-size: calc(var(--lr-legend-size) * 1cqw);
}
/* ── THE LIST ─────────────────────────────────────────────────────────────────
   GRID, deliberately not flex. As a flex column the rows were shrinkable
   (flex-shrink defaults to 1), so seven entries in a short frame compressed each
   row below its content height: the text ran together, the fixed-height symbols
   spilled out of their rows and stacked down the left edge, and the last entries
   lost their symbol entirely. `grid-auto-rows: 1fr` gives every row an equal,
   NON-shrinking share of the frame instead, so rows can never overlap however
   many prizes a set has. */
.mga-reel-legend.mga-reel-legend--art .mga-legend-list {
  flex: 1 1 auto;
  min-height: 0;
  height: 100%;
  display: grid;
  /* minmax(0,1fr), NOT 1fr: a bare 1fr carries an implicit `auto` MINIMUM, so
     rows refuse to go below their text height and a long set pushes the grid
     past the frame. With a 0 minimum the rows always divide the frame exactly
     and the type below scales to match — shrink to fit, never scroll. */
  grid-auto-rows: minmax(0, 1fr);
  align-content: stretch;
  row-gap: calc(var(--lr-legend-row-gap) * 1cqh);
  padding: 0;
  list-style: none;
}
/* One row: symbol | name | amount. The amount column exists only for cash
   prizes, so a non-cash row simply has nothing in it. */
.mga-reel-legend.mga-reel-legend--art .mga-legend-row {
  display: grid;
  grid-template-columns: min(calc(var(--lr-legend-sym) * 1cqw), calc(var(--lr-legend-sym-cap) * 1cqh)) 1fr auto;
  align-items: center;
  column-gap: 0.9cqw;
  min-height: 0;
  /* Preferred size, but never taller than a row can hold. */
  font-size: min(calc(var(--lr-legend-size) * 1cqw), calc(var(--lr-legend-cap) * 1cqh));
  line-height: 1;
  color: #f3e9ff;
}
.mga-reel-legend.mga-reel-legend--art .mga-legend-label {
  color: #e8dcff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
}
.mga-reel-legend.mga-reel-legend--art .mga-legend-amt {
  color: #ffd76b;
  justify-self: end;                   /* right-aligned, cash only */
  font-family: var(--font-mono);
}
/* Symbols size off the ROW, so they can never overflow it. */
.mga-reel-legend.mga-reel-legend--art .mga-legend-sym {
  width:  min(calc(var(--lr-legend-sym) * 1cqw), calc(var(--lr-legend-sym-cap) * 1cqh));
  height: min(calc(var(--lr-legend-sym) * 1cqw), calc(var(--lr-legend-sym-cap) * 1cqh));
  max-height: 100%;
  display: flex; align-items: center; justify-content: center;
  font-size: calc(var(--lr-legend-sym) * 0.8cqw);
  line-height: 1;
}
.mga-legend-sym--art {
  background: center / contain no-repeat;
  flex-shrink: 0;
}

/* ── SESSION SUMMARY — a POPUP over the cabinet ───────────────────────────────
   Not rendered into the console: it lists one line per spin, so its height
   varies. The machine stays visible behind it, dimmed. */
/* z-index 9: ABOVE every piece of cabinet chrome (grid 1, cabinet 2, payband 4,
   live text 5, SPIN 6). v149 raised SPIN to 6 so it would sit over the plaque
   bar, which also put it above this popup at 5 — the button did not move or
   resize (measured identical rects), it was simply painting in front, and the
   popup's backdrop-filter blurring everything behind made the sharp button read
   as larger. The chrome is hidden outright below; this is the backstop. */
.mga-lr-machine .mga-lr-summary {
  position: absolute; inset: 0; z-index: 9;
  display: none;
  flex-direction: column; align-items: center; justify-content: center;
  gap: .5em;
  padding: 6%;
  /* v155 — NO DIM. The cabinet stays at full brightness behind the summary and
     the popup floats over it on its own shadow (see .mga-lr-summary-panel).
     This layer is now purely a TAP SHIELD: it still covers the whole machine
     and still hit-tests (a transparent background captures pointer events; only
     pointer-events:none would stop it), so nothing behind the summary can be
     tapped. The 1px teal border and tinted background it inherits from
     .mini-game-result are cleared here too — with the dim gone they would draw
     a stray rounded rectangle around the entire cabinet. */
  background: none;
  border: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-radius: var(--radius-md);
  overflow: auto;
}

/* A cell falling back to the PLACEHOLDER EMOJI must still read at art-mode size —
   the cell is ~180px tall there, where the old fixed 1.5rem glyph vanished. */
.mga-lr-grid--art .mga-lr-cell { font-size: calc(var(--lr-cell-h) * 0.5); }

/* Motion feel — streaking while the reel is at speed, resolving to crisp. */
.mga-lr-strip--blur-hi { filter: blur(2.2px) brightness(1.06); }
.mga-lr-strip--blur-lo { filter: blur(0.9px); }

/* Reduced motion: the engine settles directly on the result and never scrolls,
   so make sure nothing here animates either. */
@media (prefers-reduced-motion: reduce) {
  .mga-lr-strip { transition: none; }
  .mga-lr-strip--blur-hi,
  .mga-lr-strip--blur-lo { filter: none; }
}
}
.mga-lr-payline-note,
.mga-lr-outcome {
  font-size: 0.7rem;
  text-align: center;
  color: var(--text-secondary);
  min-height: 1em;
}
.mga-lr-outcome--good { color: var(--gold-bright); font-size: 0.85rem; }
.mga-lr-outcome--bad  { color: var(--crimson); }

/* End-of-session summary — reports prizes already credited server-side.
   v156: the per-spin ledger (.mga-lr-summary-list / -row / -spin / -sym / -amt)
   is gone with the markup that used it. The outcome sentence below is now the
   whole popup, so it carries the weight the list used to take up. */
.mga-lr-outcome-line {
  /* Sized against the cabinet, like the rest of the Lucky Reel layout —
     .mga-lucky-reel is the size container, so this grows with the machine
     instead of against the viewport. Clamped so it stays readable on a small
     phone and does not run away on a tablet. */
  font-size: clamp(1.05rem, 7cqw, 2.3rem);
  line-height: 1.15;
  text-wrap: balance;              /* even lines rather than one orphan word */
  margin: 0.15em 0 0.35em;
}
.mga-lr-summary-note {
  font-size: 0.68rem;
  color: var(--text-secondary);
  text-align: center;
  margin-top: 4px;
}

/* Legend — what each symbol means (tiers + the neutral blanks). */
.mga-reel-legend {
  width: 100%;
  max-width: 230px;
  padding: var(--space-sm) var(--space-md);
  background: rgba(0, 196, 167, 0.06);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-md);
}
.mga-legend-title {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
  text-align: center;
  margin-bottom: 6px;
}
.mga-legend-list { list-style: none; display: flex; flex-direction: column; gap: 4px; }
.mga-legend-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  font-size: 0.82rem;
}
.mga-legend-sym { font-size: 1.15rem; width: 1.7rem; text-align: center; flex-shrink: 0; }
.mga-legend-sym--multi { font-size: 0.8rem; width: auto; letter-spacing: 1px; }
/* Shown while the reward table loads, or if it can't be fetched (we say so
   rather than inventing a prize list). */
.mga-legend-status {
  font-size: 0.68rem;
  color: var(--text-secondary);
  text-align: center;
  opacity: 0.8;
}
.mga-legend-label { color: var(--text-secondary); letter-spacing: 0.02em; flex: 1; }
.mga-legend-amt {
  font-family: var(--font-mono);
  color: var(--gold-bright);
  flex-shrink: 0;
}
.mga-spin-btn {
  background: var(--grad-teal);
  color: var(--bg-deep);
  border: 1px solid var(--teal);
  box-shadow: var(--shadow-teal);
}
.mga-spin-btn:disabled { opacity: 0.5; cursor: default; box-shadow: none; }

/* Narrow phones: shrink cells so 5 reels still fit inside the modal. */
@media (max-width: 380px) {
  .mga-lr-grid { --lr-cell-w: 38px; }
  .mga-lr-cell { font-size: 1.25rem; }
}

/* ── Game 3: Coin Flip Streak ─────────────────────────────── */
.mga-coin-flip {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
  width: 100%;
}
.mga-flip-counter,
.mga-score {
  font-size: 0.82rem;
  color: var(--text-secondary);
  font-family: var(--font-mono);
  letter-spacing: 0.06em;
}
.mga-coin-wrap { padding: var(--space-sm) 0; }
.mga-coin {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background: var(--grad-gold);
  border: 3px solid var(--gold);
  box-shadow: 0 0 20px var(--gold-glow), inset 0 -4px 8px rgba(0,0,0,0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 1.6rem;
  font-weight: 900;
  color: #1a0800;
  user-select: none;
  -webkit-user-select: none;
}

@keyframes mgaCoinFlip {
  0%   { transform: scaleX(1); }
  40%  { transform: scaleX(0); }
  60%  { transform: scaleX(0); }
  100% { transform: scaleX(1); }
}
.mga-coin--flip { animation: mgaCoinFlip 0.6s ease-in-out; }

.mga-choices { display: flex; gap: var(--space-md); }

/* ============================================================
   ONBOARDING / AUTH SCREENS
   Three panels: phone → OTP → age+terms.
   No bottom nav visible here — all padding/alignment self-contained.
   ============================================================ */

#screen-onboarding {
  padding-bottom: 0;       /* Nav is hidden — no need to reserve space for it */
  align-items: center;
  justify-content: flex-start;
  /* v179: TRANSPARENT — the global desert (#app-bg, slots.bg_landscape, the
     same layer Scratch Rush sits on) shows through. No second background.

     WHY IT WAS OPAQUE, and why that no longer holds. Nav redesign stage 1
     (v74) set var(--grad-bg) here "so the now-global landscape doesn't leak
     into the auth flow — keeps onboarding looking exactly as before". That is a
     LOOK, not a guard against a bug. Checked in v179 before removing it:
       · Boot: this screen is .active in the markup, and boot() awaits the CMS
         theme before applyThemeSlots() paints #app-bg. Until then #app-bg is
         empty and the BODY's own var(--grad-bg) shows through — exactly what
         the opaque rule used to paint. If the image is slow or fails, that
         gradient simply stays. Never a white or broken gap.
       · Transitions: onboarding always uses the instant swap and never moves
         #app-bg. The opaque rule was itself the artefact: .screen.active fades
         in from opacity 0 (screenSlideUp), background included, so on logout
         the desert showed through and faded to dark. Transparent, the card
         fades in over a desert that stays put.
       · Taps: a transparent background still hit-tests; the other screens are
         display:none and #app-bg takes no taps. */
  background: transparent;
}

/* Each sub-panel is a full-height flex column */
.onboard-panel {
  width: 100%;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-lg);
  padding: var(--space-xl) var(--space-md);
  overflow-y: auto;
}

/* Logo area at the top of each panel */
.onboard-logo .logo-img {
  width: min(160px, 45vw);
  filter:
    drop-shadow(0 0 14px rgba(0,196,167,0.6))
    drop-shadow(0 0 28px rgba(245,166,35,0.3));
}

/* Main card */
.onboard-card {
  width: 100%;
  max-width: 380px;
  background: var(--grad-card);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-xl);
  padding: var(--space-xl) var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  box-shadow: var(--shadow-teal);
}

.onboard-title {
  font-family: var(--font-display);
  font-size: 1.35rem;
  color: var(--text-white);
  letter-spacing: 0.04em;
  text-align: center;
}

.onboard-subtitle {
  font-size: 0.85rem;
  color: var(--text-secondary);
  text-align: center;
  line-height: 1.5;
}

/* ── v179: the card on the shared nine-slice frame (js/ui-popup.js) ─────────
   Until .uipop-frame lands — art missing, slice invalid, theme fetch failed,
   or the image still loading — the card keeps its own teal chrome above, so
   the step is always legible and usable.

   The frame's border is slice 44 x 0.60 = 26.4px a side, but only its outer
   ~6px is the gold rule; the rest is the art's own maroon. So the text already
   has room, and the card's padding shrinks to keep the content wide enough for
   the OTP row. The border is a FIXED width, so the corners are identical
   whatever height the step is — the three steps only stretch the edge and
   centre slices.

   The lift is filter: drop-shadow(), not box-shadow, for the v155 reason: the
   frame's corners are shaped, and a box-shadow would draw a rectangle. */
.onboard-card.uipop-frame {
  padding: var(--space-xs) 2px;
  box-shadow: none;
  filter:
    drop-shadow(0 4px 6px rgba(0, 0, 0, 0.55))
    drop-shadow(0 0 18px rgba(0, 0, 0, 0.35));
}

/* Inline error banner */
.onboard-error {
  background: rgba(220,28,46,0.12);
  border: 1px solid var(--border-crimson);
  border-radius: var(--radius-sm);
  color: var(--crimson-light);
  font-size: 0.82rem;
  font-weight: 600;
  padding: 10px var(--space-md);
  text-align: center;
  line-height: 1.45;
}

/* On the maroon frame the error banner's own faint red wash made the red text
   3.9:1. A dark backing plus a lighter red brings it to AA for its size. */
.onboard-card.uipop-frame .onboard-error {
  background: rgba(0, 0, 0, 0.55);
  border-color: rgba(255, 107, 120, 0.55);
  color: #ff9aa4;
}

/* ---- Panel 1: Phone ---- */
.phone-row {
  display: flex;
  gap: var(--space-sm);
  align-items: stretch;
}

.country-select {
  flex-shrink: 0;
  min-width: 84px;
  background: var(--bg-mid);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: 0.88rem;
  font-weight: 700;
  padding: 10px 6px;
  cursor: pointer;
  outline: none;
  appearance: none;
  -webkit-appearance: none;
  text-align: center;
  transition: border-color 0.2s, box-shadow 0.2s;
}
.country-select:focus {
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(0,196,167,0.15);
}

.phone-input {
  flex: 1;
  min-width: 0;            /* v179: an input's automatic minimum is its intrinsic
                              width (~150px+), which pushed the row out of a
                              narrow card */
  background: var(--bg-mid);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font-family: var(--font-mono);
  font-size: 1.05rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  padding: 10px var(--space-md);
  outline: none;
  -webkit-appearance: none;
  transition: border-color 0.2s, box-shadow 0.2s;
}
.phone-input:focus {
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(0,196,167,0.15);
}
.phone-input::placeholder {
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: 0.9rem;
  letter-spacing: 0;
  font-weight: 600;
}

/* ---- Panel 2: OTP ---- */
.otp-grid {
  display: flex;
  gap: 8px;
  justify-content: center;
}

.otp-digit {
  /* v179: up to 44px wide, shrinking evenly when the card is narrower. Six at
     44px + gaps is 304px; the card's content box is 293px at 375 wide and 238px
     at 320 (the row already overflowed there, 8px past the card's edges), and
     the frame's border takes 52.8px more. Height stays 58px — that is the tap
     target — and a digit needs ~17px of width. */
  width: 44px;
  flex: 0 1 44px;
  min-width: 0;
  height: 58px;
  background: var(--bg-mid);
  border: 2px solid var(--border-teal);
  border-radius: var(--radius-md);
  color: var(--teal-light);
  font-family: var(--font-mono);
  font-size: 1.7rem;
  font-weight: 900;
  text-align: center;
  outline: none;
  caret-color: transparent;
  -webkit-appearance: none;
  transition: border-color 0.2s, box-shadow 0.2s, color 0.2s;
}
.otp-digit:focus {
  border-color: var(--teal);
  box-shadow: 0 0 0 3px rgba(0,196,167,0.2), 0 0 14px var(--teal-glow);
}
.otp-digit.filled {
  border-color: var(--teal);
  color: var(--gold-bright);
}

.resend-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  flex-wrap: wrap;
  min-height: 28px;
}

.resend-countdown {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--text-secondary);
  letter-spacing: 0.06em;
}

/* Link-style buttons (Resend, Change number) */
.btn-link {
  background: none;
  border: none;
  color: var(--teal-light);
  font-family: var(--font-body);
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
  padding: 4px 6px;
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color 0.2s;
}
.btn-link:disabled {
  color: var(--text-secondary);
  text-decoration: none;
  cursor: default;
}
.btn-link-subtle {
  color: var(--text-secondary);
  font-size: 0.8rem;
  text-decoration: none;
}
.btn-link-subtle:not(:disabled):hover { color: var(--text-primary); }

/* v179 — the three submit buttons on popup_button_primary. Pressed and
   disabled come from the shared .uipop-btn rules (darken + label offset;
   opacity + desaturation) and never change the box. The login flow also swaps
   the label to '…' while a request is out, and the start button's label has an
   emoji whose line box is taller than '…', so the HEIGHT is fixed here and the
   label kept on one line: the rect is the same in every state. The fallback
   (.btn-primary, no art) gets the same fixed height. */
#screen-onboarding .onboard-card > .btn-primary {
  flex: 0 0 auto;
  height: 50px;
  line-height: 1;
  white-space: nowrap;
}

/* ---- Panel 3: Terms ---- */
.terms-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.terms-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-sm);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}
.terms-item:hover {
  background: rgba(0,196,167,0.05);
  border-color: rgba(0,196,167,0.15);
}
.terms-item-optional { opacity: 0.7; }
/* v179: on the maroon frame, 0.7 left "(optional)" at 3.56:1. 0.85 keeps the row
   visibly quieter than the required ones (9.45:1 body vs 12.74:1) and brings
   "(optional)" to 4.74:1. */
.onboard-card.uipop-frame .terms-item-optional { opacity: 0.85; }

.terms-check {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  margin-top: 2px;
  accent-color: var(--teal);
  cursor: pointer;
}

.terms-text {
  font-size: 0.88rem;
  color: var(--text-primary);
  line-height: 1.45;
}
.terms-text strong { color: var(--gold); }
.terms-text em     { color: var(--text-secondary); font-style: normal; }

.terms-link {
  color: var(--teal-light);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.terms-link:hover { color: var(--teal); }

/* ── v179: login on SHORT screens — no scrolling ────────────────────────────
   The logo (~102px), the panel's 32px padding top and bottom and its 24px gap
   are fixed costs on top of the card. With an error showing, the terms step
   needed 690px, so it scrolled at 390 x 640 and 375 x 667; at 320 x 568 the
   OTP-with-error and both terms states scrolled — and the old card, before the
   frame, scrolled in exactly the same places. Two tiers, by HEIGHT, tighten
   only the spacing and the logo; every control keeps its size. */
@media (max-height: 740px) {
  #screen-onboarding .onboard-panel {
    padding-top: var(--space-sm);
    padding-bottom: var(--space-sm);
    gap: var(--space-sm);
  }
  #screen-onboarding .onboard-logo .logo-img { width: min(100px, 28vw); }
}
@media (max-height: 600px) {
  #screen-onboarding .onboard-panel {
    padding-top: var(--space-xs);
    padding-bottom: var(--space-xs);
  }
  #screen-onboarding .onboard-logo .logo-img { width: min(64px, 20vw); }
  #screen-onboarding .onboard-card { gap: var(--space-sm); }
  #screen-onboarding .terms-list { gap: 2px; }
}

/* ============================================================
   RESPONSIVE — tiny phones (< 360px)
   ============================================================ */
@media (max-width: 359px) {
  #jackpot-value       { font-size: 2rem; }
  #wallet-total-display { font-size: 2.4rem; }
  .tier-card            { padding: var(--space-md); }
  .scratch-grid         { gap: var(--space-xs); }
}

/* Desktop centering decoration — vertical lines either side */
@media (min-width: 480px) {
  body::before, body::after {
    content: '';
    position: fixed;
    top: 0; bottom: 0;
    width: 1px;
    background: linear-gradient(to bottom, transparent, var(--border-teal), transparent);
    z-index: 0;
  }
  body::before { left: calc(50% - 240px); }
  body::after  { left: calc(50% + 240px); }
}

/* ===========================================================
   POWER PLAY (Tier 2) — FIGURE-8 board (mock visual scaffold)
   Layered scaled-stage: desert bg · figure-8 track coordinate
   layer (tiles + avatar at %-coords) · chrome · bottom ROLL panel.
   =========================================================== */
#screen-power-play {
  padding: var(--space-md) var(--space-md) var(--space-md);
  gap: var(--space-sm);
  /* Transparent so the persistent global #app-bg shows behind the game. */
  background: transparent;
  align-items: center;
  justify-content: flex-start;
}

/* ---- The stage: fixed figure-8 aspect (880×1415), centered + responsive ----
   Every child layer positions in % against this box, so the figure-8 scales
   as one unit. overflow:visible lets the avatar/edge tiles bleed past cleanly. */
.pp-stage {
  position: relative;
  z-index: 2;                /* above the full-bleed .pp-bg (z1) */
  width: 100%;
  max-width: 440px;
  margin-inline: auto;
  /* Push the WHOLE stage (board + all %-positioned chrome) down together.
     Mattias to fine-tune the exact amount. */
  margin-top: 6%;
  aspect-ratio: 880 / 1415;
  border-radius: var(--radius-lg);
  overflow: visible;
}

/* 1. Desert background — full-bleed, PP-screen-LOCAL (Option A).
   Lives as a direct child of #screen-power-play (NOT #pp-stage), so it covers the
   whole PP screen and the global #app-bg landscape (z0) no longer leaks at the
   edges. Sits above #app-bg (z1) but below the board/chrome (z2+). */
.pp-bg {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100vw;
  max-width: 480px;
  height: 100%;
  z-index: 1;
  background: #1a0f06 center / cover no-repeat;   /* PP_BG.png set inline */
}

/* 2. Figure-8 TRACK layer = PP_track.png — the COORDINATE SPACE tiles + token
   position against (they are its children, so they scale/move WITH it).
   DECOUPLED from the desert: smaller than the stage and pushed down, so the
   full 8 sits WITHIN the desert scene (margin on the sides, clearance above for
   the wallet/cowboy/signs and below for the ROLL panel — like the design).
   Keeps its native 880/1415 ratio (never stretched). */
/* ┌── HAND-TUNED by Mattias — DO NOT REGENERATE these positions. ──┐
   If a future task must restructure this area, PRESERVE these exact
   top/left/right/width/height values. Only change them on Mattias's explicit request. */
.pp-track {
  position: absolute;
  top: 14%;                  /* start below the top signs/wallet/cowboy zone */
  left: 50%;
  transform: translateX(-50%);
  width: 90%;                /* ~10% desert margin on each side */
  aspect-ratio: 880 / 1415;
  z-index: 2;
  background: center / 100% 100% no-repeat;     /* PP_track.png set inline */
}
/* └── end HAND-TUNED ──┘ */

/* ---- Tiles: absolutely placed at their %-coordinate, centered on it ---- */
.pp-tile {
  position: absolute;
  width: 12%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%) scale(0);   /* hidden until cascade-in */
  background: center / contain no-repeat;       /* icon set inline */
  display: flex;
  align-items: flex-end;
  justify-content: center;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,0.55));
  will-change: transform;
}
.pp-tile--in { animation: ppTileIn 0.34s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; }
@keyframes ppTileIn {
  0%   { transform: translate(-50%, -50%) scale(0); }
  70%  { transform: translate(-50%, -50%) scale(1.18); }
  100% { transform: translate(-50%, -50%) scale(1); }
}

/* Value label — ENGINE TEXT drawn over the art (never baked into the icon) */
.pp-tile-label {
  position: absolute;
  bottom: 35%;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  color: #fff;
  padding: 2px 5px;
  border-radius: var(--radius-full);
  background: rgba(8, 20, 18, 0.85);
  border: 1px solid rgba(255,255,255,0.14);
  text-shadow: 0 1px 2px rgba(0,0,0,0.8);
}
.pp-tile--cash       .pp-tile-label { color: var(--gold-light);   border-color: var(--border-gold); }
.pp-tile--throw      .pp-tile-label { color: #8ef0c0; }
.pp-tile--multiplier .pp-tile-label { color: var(--teal-light);   border-color: var(--teal-light); }
.pp-tile--tier3      .pp-tile-label { color: var(--crimson-light); border-color: var(--border-crimson); }
/* v196: Jackpot Strike fragment tile (BE #82). Uses CMS fragment art when
   present; otherwise the 🧩 glyph, centred on the tile face. */
.pp-tile-glyph {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 1.45rem;
  line-height: 1;
  filter: drop-shadow(0 0 6px rgba(245, 166, 35, 0.55));
}

/* START — the bundled start sign, slightly larger, no value */
.pp-tile--start { width: 18%; align-items: center; }

/* Empty path positions — faint dot so the figure-8 path reads between icons */
.pp-tile--empty {
  width: 5%;
  background: radial-gradient(circle, rgba(255,224,150,0.55) 0%, rgba(255,224,150,0.12) 60%, transparent 70%);
  filter: none;
}
.pp-tile--empty.pp-tile--in { animation-duration: 0.24s; }

/* Landed-tile highlight bump */
.pp-tile--landed { animation: ppTileLand 0.5s ease-out; z-index: 3; }
@keyframes ppTileLand {
  0%   { transform: translate(-50%, -50%) scale(1); filter: drop-shadow(0 0 0 transparent); }
  45%  { transform: translate(-50%, -50%) scale(1.3); filter: drop-shadow(0 0 12px var(--gold-bright)); }
  100% { transform: translate(-50%, -50%) scale(1); }
}

/* ---- 4. Avatar token ---- */
.pp-token {
  position: absolute;
  width: 13%;
  aspect-ratio: 128 / 193;
  z-index: 8;                                    /* above all chrome (wallet/signs) */
  background: center / contain no-repeat;       /* PP_avatar.png set inline */
  transform: translate(-50%, -78%);              /* feet on the coordinate point */
  transition: left var(--pp-hop, 0.17s) ease-in-out, top var(--pp-hop, 0.17s) ease-in-out;
  filter: drop-shadow(0 4px 6px rgba(0,0,0,0.6));
  pointer-events: none;
}
.pp-token--hop { animation: ppTokenHop 0.18s ease; }
@keyframes ppTokenHop {
  0%   { transform: translate(-50%, -78%) scale(1); }
  45%  { transform: translate(-50%, -78%) scale(1.08) translateY(-14%); }
  100% { transform: translate(-50%, -78%) scale(1); }
}

/* ---- Chrome: wallet frame (top-left) ---- */
.pp-wallet {
  position: absolute;
  top: 2%;
  left: 1%;
  z-index: 6;
  width: 28%;
  aspect-ratio: 263 / 176;
  background: center / contain no-repeat;       /* PP_wallet_frame.png set inline */
  display: flex;
  align-items: center;
  justify-content: center;
}
.pp-wallet-val {
  font-family: var(--font-display);
  font-size: 1.52rem;
  font-weight: 700;
  color: var(--gold-bright);
  text-shadow: 0 1px 3px rgba(0,0,0,0.9);
  transform: translateY(8%);
}

/* ---- Chrome: decorative signs ---- */
.pp-sign {
  position: absolute;
  z-index: 4;
  background: center / contain no-repeat;
  pointer-events: none;
  filter: drop-shadow(0 3px 6px rgba(0,0,0,0.5));
}
/* ┌── HAND-TUNED by Mattias — DO NOT REGENERATE these positions. ──┐
   If a future task must restructure this area, PRESERVE these exact
   top/left/right/width/height values. Only change them on Mattias's explicit request. */
.pp-sign--title  { top: 37.5%; left: 4%;  width: 25%; aspect-ratio: 319 / 587; opacity: 0.96; }
/* Moved DOWN to mid-right so it clears the bank (which now owns the top-right corner). */
.pp-sign--spaces { top: 48%; right: 0%; width: 22%; aspect-ratio: 181 / 209; }
/* Wooden START signpost — decorative chrome near the START perch (upper-left,
   below the wallet so it isn't occluded). Approximate placement — Mattias will nudge. */
.pp-sign--start  { top: 13%; left: 13%; width: 13%; aspect-ratio: 139 / 195; }
/* └── end HAND-TUNED ──┘ */

/* ---- Chrome: Lucky Valley bank (PP_bank gold vault), top-right ----
   Approximate placement — Mattias will nudge. Above the track, below wallet. */
/* ┌── HAND-TUNED by Mattias — DO NOT REGENERATE these positions. ──┐
   If a future task must restructure this area, PRESERVE these exact
   top/left/right/width/height values. Only change them on Mattias's explicit request. */
.pp-bank {
  position: absolute;
  top: 1%;
  right: -4%;
  z-index: 5;
  width: 27%;
  aspect-ratio: 348 / 572;          /* PP_bank.png native ratio */
  background: center / contain no-repeat;   /* PP_bank.png set inline */
  pointer-events: none;
  filter: drop-shadow(0 3px 6px rgba(0,0,0,0.5));
}
/* └── end HAND-TUNED ──┘ */

/* ---- 5. Bottom ROLL panel (PP_board bar chrome) ----
   ABSOLUTE-POSITIONING LAYER: the panel is just the PP_board bar + a positioning
   parent. Its four children (rolls / total / shields / dice) are each absolutely
   placed at % of the panel, so Mattias can nudge each independently via its own
   top/left below — same model as the board tiles and chrome signs.
   aspect-ratio matches PP_board.png so the % child coords map onto the bar art. */
/* ┌── HAND-TUNED by Mattias — DO NOT REGENERATE these positions. ──┐
   If a future task must restructure this area, PRESERVE these exact
   top/left/right/width/height values. Only change them on Mattias's explicit request. */
.pp-panel {
  position: relative;
  z-index: 2;                /* above the full-bleed .pp-bg (z1) */
  width: 100%;
  max-width: 440px;
  margin: 2px auto 0;
  aspect-ratio: 913 / 194;   /* PP_board.png native ratio (no stretch) */
  background: center / 100% 100% no-repeat;     /* PP_board.png set inline */
  border-radius: var(--radius-md);
}
/* └── end HAND-TUNED ──┘ */
/* Each stat = an absolutely-placed unit (label above value), anchored top/left. */
.pp-panel-stat {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
/* ┌── HAND-TUNED by Mattias — DO NOT REGENERATE these positions. ──┐
   If a future task must restructure this area, PRESERVE these exact
   top/left/right/width/height values. Only change them on Mattias's explicit request. */
/* nudge: ROLLS LEFT position */
.pp-stat--rolls   { top: 26%; left:20%; }
/* nudge: TOTAL WIN position */
.pp-stat--total   { top: 26%; left: 38%; }
/* nudge: SHIELDS position */
.pp-stat--shields { top: 26%; left: 54%; }
/* └── end HAND-TUNED ──┘ */
.pp-panel-label {
  font-family: var(--font-body);
  font-size: 0.62rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: #f4e3c0;
  text-shadow: 0 1px 2px rgba(0,0,0,0.7);
}
.pp-panel-val {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--gold-bright);
  text-shadow: 0 1px 3px rgba(0,0,0,0.8);
}

/* ROLL button — the bundled dice, tappable. Absolutely placed (top/left corner,
   so transform stays free for the press/shake anims below). */
/* ┌── HAND-TUNED by Mattias — DO NOT REGENERATE these positions. ──┐
   If a future task must restructure this area, PRESERVE these exact
   top/left/right/width/height values (and keep the size/shape/anim intact).
   Only change them on Mattias's explicit request. */
.pp-roll-btn {
  position: absolute;
  /* nudge: DICE ROLL button position */
  top: -10%;
  left: 73%;
  width: 80px;
  height: 80px;
  border: none;
  border-radius: 50%;
  background: center / contain no-repeat;       /* PP_dice.png set inline */
  cursor: pointer;
  filter: drop-shadow(0 0 8px var(--gold-glow));
  transition: transform 0.12s ease, filter 0.12s ease;
}
.pp-roll-btn:hover  { filter: drop-shadow(0 0 14px var(--gold-bright)); }
.pp-roll-btn:active { transform: scale(0.9) rotate(-12deg); }
.pp-roll-btn:disabled { opacity: 0.5; cursor: default; animation: ppDiceShake 0.36s linear infinite; }
@keyframes ppDiceShake {
  0%   { transform: rotate(-10deg); }
  50%  { transform: rotate(10deg); }
  100% { transform: rotate(-10deg); }
}
/* └── end HAND-TUNED ──┘ */

/* ── SPRITE-SHEET DIE (v138) — the roll button's face.
   Replaces the v136/v137 CSS 3D cube (.pp-die*) AND the flat PP_dice.png.
   Renders INSIDE #pp-roll-btn at 100%, so the HAND-TUNED block above still
   governs the button's nudged position/size (80×80, top:-10% left:73%) —
   nothing here touches those. The sprite tumble supersedes ppDiceShake. */
.pp-roll-btn { background-image: none; }
.pp-roll-btn:disabled { animation: none; opacity: 1; }   /* die tumbles instead of shaking */

/* A 300×300 frame viewport scaled to fill the button. background-size
   1600% 700% = 16 cols × 7 rows, so exactly ONE frame shows; power-play.js
   steps background-position (see PP_DICE_* / _ppDiceFramePos there). The sheet
   URL is set inline from ppAsset('dice_sheet') so a CMS theme can swap it. */
/* ┌── HAND-TUNED by Mattias — DO NOT REGENERATE these positions. ──┐
   If a future task must restructure this area, PRESERVE these exact
   top/left/width/height values (and keep the size/shape/anim intact).
   Only change them on Mattias's explicit request. */
.pp-dice-sprite {
  position: absolute;
  /* nudge: DICE sprite size + position (% of the 80×80 button box) */
  top: -1%;
  left: -10%;
  width: 120%;
  height: 120%;
  background-size: 1600% 700%;
  background-repeat: no-repeat;
  background-position: 0% 0%;          /* JS sets the real frame on paint */
  pointer-events: none;
}
/* └── end HAND-TUNED ──┘ */

/* ---- Idle PLAY CTA — its OWN square button (PP_play_button.png is 187×187),
   centered in the bottom region. It is NOT the PP_board bar and must NOT stretch
   across it: JS swaps it with the whole .pp-panel (only one shows at a time). ---- */
.pp-play-btn {
  position: relative;
  z-index: 2;                                   /* above the full-bleed .pp-bg (z1) */
  display: block;
  width: 128px;
  height: 128px;                                /* square — matches the round asset */
  margin: 2px auto 0;                           /* centered in the bottom area */
  padding: 0;
  border: none;
  background: center / contain no-repeat;       /* PP_play_button.png — not stretched */
  cursor: pointer;
  transition: transform 0.12s ease, filter 0.12s ease;
  filter: drop-shadow(0 3px 10px rgba(0,0,0,0.5));
}
.pp-play-btn:hover  { filter: drop-shadow(0 0 16px var(--gold-glow)); }
.pp-play-btn:active { transform: scale(0.94); }
.pp-play-btn:disabled { opacity: 0.6; cursor: default; }


/* =============================================================
   JACKPOT STRIKE (Tier 3 — number-pick lottery)
   ============================================================= */
#screen-jackpot-strike {
  padding: var(--space-xl) var(--space-md) var(--space-md);
  gap: var(--space-md);
  /* Transparent so the persistent global #app-bg shows behind the game. */
  background: transparent;
  align-items: center;
}
/* Cap the CONTENT (not the full-bleed .jps-bg) to the 440px column. */
#screen-jackpot-strike > *:not(.jps-bg) { width: 100%; max-width: 440px; }

/* ── Full-screen desert backdrop — screen-LOCAL (mirrors Power Play's .pp-bg) ──
   Fixed + full-bleed, above the global #app-bg (z0) but below the content (z2).
   Hidden on other screens via the .screen display:none rule. Image set by JS. */
.jps-bg {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100vw;
  max-width: 480px;
  height: 100%;
  z-index: 1;
  background: #1a0f06 center / cover no-repeat;   /* jps_bg.png set inline */
}
/* Content sits ABOVE the backdrop; keeps the old centered-column + 440px cap
   (the screen's flex/gap moved here since .jps-content is now the only in-flow
   child alongside the fixed .jps-bg). */
.jps-content {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-md);
}
.jps-content > * { width: 100%; }

/* ── Framed header art (jps_header 878×280) — the logo + live values sit ON it,
   each ABSOLUTELY positioned at % of the header box so Mattias can nudge each.
   Replaces the old teal .game-header (title/emoji-pill dropped). Approximate
   positions — fine-tune the per-element nudge values below. */
/* ┌── HAND-TUNED by Mattias (Jackpot Strike) — DO NOT REGENERATE these values. ──┐
   If a future task must restructure this area, PRESERVE these exact position/size
   values. Only change them on Mattias's explicit request. */
.jps-header {
  position: relative;
  aspect-ratio: 878 / 280;
  background: center / 100% 100% no-repeat;       /* jps_header.png set inline */
    /* nudge: header vertical position — increase to push DOWN, negative to pull UP */
  margin-top: 20px;
    /* nudge: space between header and the frame/tabs below */
  margin-bottom: 20px;
}
.jps-logo {                    /* nudge: LOGO position (centered-top) */
  position: absolute;
  top: 9%;
  left: 50%;
  transform: translateX(-50%);
  width: 46%;
  aspect-ratio: 280 / 67;
  background: center / contain no-repeat;          /* jps_logo.png set inline */
}
.jps-hdr-wallet {              /* nudge: WALLET balance (top-left) */
  position: absolute;
  top: 16%;
  left: 8%;
}
.jps-hdr-tickets {             /* nudge: TICKET count (top-right) */
  position: absolute;
  top: 16%;
  right: 12.5%;
}
.jps-hdr-wallet, .jps-hdr-tickets {
  font-family: var(--font-mono);
  font-size: 1.22rem;
  font-weight: 700;
  color: var(--gold-bright);
  text-shadow: 0 1px 3px rgba(0,0,0,0.9);
  white-space: nowrap;
}
/* Countdown re-anchored onto the header's lower bar; strip the card chrome so it
   reads as text over the art. JS toggles display:flex/none + updates by id.
   (.js-countdown.jps-hdr-countdown — 2 classes — beats the .js-countdown card
   rule which is defined later in the file.) */
.js-countdown.jps-hdr-countdown {   /* nudge: COUNTDOWN position (lower bar, centered) */
  position: absolute;
  bottom: 22%;
  left: 50%;
  transform: translateX(-50%);
  background: none;
  border: none;
  box-shadow: none;
  padding: 0;
  gap: 0;
}
.jps-hdr-countdown .js-countdown-label { font-size: 0.00rem; color: #f4e3c0; letter-spacing: 0.06em; text-transform: uppercase; }
.jps-hdr-countdown .js-countdown-time  { font-size: 1.3rem; color: var(--gold-bright); text-shadow: 0 1px 3px rgba(0,0,0,0.9); }
/* └── end HAND-TUNED ──┘ */

/* Small button variant (used by Quick Pick) */
.btn-sm {
  width: auto;
  padding: var(--space-sm) var(--space-md);
  font-size: 0.82rem;
}

/* Draw banner / empty state */
.js-draw-banner {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  color: var(--gold-light);
  background: var(--bg-card);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-md);
  padding: var(--space-sm) var(--space-md);
}

/* Pick / My Rows tabs */
.js-tabs { display: flex; gap: var(--space-sm); }
/* Western "folder tabs" — maroon fill + gold border, rounded top corners so they
   read as folders connecting up to the framed header. Inactive is dim; the active
   tab brightens (bright-gold top edge + brighter maroon + glow). Same box geometry
   both states (border-box, 3px top edge in both) so switching doesn't wobble. */
.js-tab {
  flex: 1;
  padding: var(--space-sm);
  background: linear-gradient(180deg, #5a1019 0%, #3d0a11 100%);  /* dim maroon */
  border: 1px solid var(--border-gold);
  border-top: 3px solid var(--border-gold);                      /* gold folder-tab edge (dim) */
  border-radius: var(--radius-md) var(--radius-md) 0 0;          /* rounded TOP only */
  color: var(--gold-light);
  opacity: 0.68;
  font-family: var(--font-display);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
  cursor: pointer;
  transition: opacity 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}
.js-tab--active {
  background: linear-gradient(180deg, #9a1223 0%, #6b0e1a 100%);  /* brighter maroon */
  border-color: var(--gold);
  border-top-color: var(--gold-bright);                          /* bright gold edge = selected */
  color: var(--gold-bright);
  opacity: 1;
  box-shadow: 0 -2px 12px var(--gold-glow), inset 0 0 12px rgba(0, 0, 0, 0.3);
  font-weight: 700;
}

/* ── LAYERED frame tabs (v123 — replaces the v122 CSS notch bar that doubled the
   outline) ───────────────────────────────────────────────────────────────────
   Both frames occupy the SAME position in .js-frame-stack. The ACTIVE frame is
   in-flow (drives the stack height), z2, opacity 1; the INACTIVE is absolute at
   the same origin, z1, opacity 0.6 (covered except its notch). The notch shapes
   come from the frame ART (baked in opposite top corners). */
/* overflow:hidden clips the (taller) INACTIVE frame to the ACTIVE frame's height,
   so the inactive body never peeks below — only its notch shows at the top. */
.js-frame-stack { position: relative; overflow: hidden; }
.js-frame {
  position: absolute;
  top: 0; left: 0; right: 0;
  z-index: 1;
  opacity: 0.6;
  transition: opacity 0.18s ease;
}
.js-frame--active {
  position: relative;      /* in flow → sizes the stack to the active frame */
  z-index: 2;
  opacity: 1;
}

/* Notch LABELS — overlay the frames' baked notches at the two top corners
   (pick=left, rows=right). z ABOVE both frames so BOTH are visible + clickable no
   matter which frame is active. NO own background/border (the notch shape is the
   frame art — that's what fixes the v122 doubling). Brightness tracks active. */
/* ┌── HAND-TUNED by Mattias (Jackpot Strike) — DO NOT REGENERATE these values. ──┐
   If a future task must restructure this area, PRESERVE these exact position/size
   values. Only change them on Mattias's explicit request. */
.js-frame-notch {
  position: absolute;
  top: 0;
  z-index: 5;
  padding: 3% 12%;
  background: none;
  border: none;
  color: var(--gold-light);
  opacity: 0.6;
  font-family: var(--font-display);
  font-size: 0.9rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
  cursor: pointer;
  transition: opacity 0.18s ease, color 0.18s ease;
}
.js-frame-notch--pick { left: 0; }
.js-frame-notch--rows { right: 0; }
.js-frame-notch--active { opacity: 1; color: var(--gold-bright); font-weight: 700; }
/* └── end HAND-TUNED ──┘ */

/* Pick grid frame — jps_pick_numbers (880×880, notch top-left) framing the
   picking area (grid + Selected/QuickPick + Add Row). Extra top padding clears
   the baked notch. Staged rows + Buy/Use live in the tail BELOW the stack. */
/* ┌── HAND-TUNED by Mattias (Jackpot Strike) — DO NOT REGENERATE these values. ──┐
   If a future task must restructure this area, PRESERVE these exact position/size
   values. Only change them on Mattias's explicit request. */
.js-pick-frame {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  background: var(--jps-pick) center / 100% 100% no-repeat;
  padding: 13% 6.5% 6.5%;
}
/* └── end HAND-TUNED ──┘ */
/* Pick tail — staged + pay, below the layered stack. */
.js-pick-tail { display: flex; flex-direction: column; gap: var(--space-md); margin-top: var(--space-md); }

/* Pick view */
.js-pick-view { display: flex; flex-direction: column; gap: var(--space-md); }
.js-pick-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm);
}
/* ┌── HAND-TUNED by Mattias (Jackpot Strike) — DO NOT REGENERATE these values. ──┐
   If a future task must restructure this area, PRESERVE these exact position/size
   values. Only change them on Mattias's explicit request. */
.js-counter {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  color: var(--text-secondary);
  /* Own line, centered (Quick Pick + Add Row sit in .js-pick-actions below). */
  display: block;
  width: 100%;
  text-align: center;
  margin-top: var(--space-sm);
}
.js-counter--ready { color: var(--gold-bright); }

/* Quick Pick + Add Row — EQUAL width side by side (mirrors .js-pay-row). Uniform
   padding + flex:1 1 0 so their border-box widths match; same 9-slice jps_button. */
.js-pick-actions {
  display: flex;
  gap: var(--space-sm);
  align-items: stretch;
  margin-top: var(--space-sm);
}
.js-pick-actions > button {
  flex: 1 1 0;
  min-width: 0;
  width: auto;
  padding: 14px var(--space-md);
  font-size: 0.95rem;
}
/* └── end HAND-TUNED ──┘ */

/* Number grid */
.js-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--space-xs);
}
.js-grid-empty { grid-column: 1 / -1; text-align: center; color: var(--text-secondary); padding: var(--space-md); }
/* Number tiles — western art (jps_unselected / jps_selected). The art URLs are
   set once as CSS vars on #screen-jackpot-strike by _paintArt(jpsAsset), so the
   .js-num--on class toggle swaps the tile art purely via CSS (no per-tile JS). */
.js-num {
  aspect-ratio: 1 / 1;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  font-weight: 700;
  color: #f4e3c0;                                  /* cream — reads on the dark unselected tile */
  background: var(--jps-tile-off) center / 100% 100% no-repeat;   /* jps_unselected */
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: transform 0.08s ease;
  text-shadow: 0 1px 2px rgba(0,0,0,0.85);
}
.js-num:active { transform: scale(0.92); }
.js-num--on {
  color: #3a1d04;                                  /* dark — reads on the orange selected tile */
  background: var(--jps-tile-on) center / 100% 100% no-repeat;    /* jps_selected */
  text-shadow: 0 1px 1px rgba(255,225,150,0.5);
}
.js-num--locked { opacity: 0.32; pointer-events: none; }

/* JPS action buttons — gold jps_button art. SCOPED to the three JPS ids (id
   specificity beats the shared .btn-primary), so .btn-primary elsewhere
   (onboarding / mini-games) is UNAFFECTED. Only the visual fill changes — the
   pill shape / padding / handlers / disabled logic come from btn-primary. */
#js-quick-pick-btn,
#js-team-play-btn,
#js-add-row-btn,
#js-buy-btn,
#js-redeem-btn {
  /* 9-SLICE the jps_button so the rounded corners NEVER stretch — corners fixed,
     middle stretches. LAYOUT-NEUTRAL: border-width 0 but border-image-width 16px,
     so the slice paints ~16px inward over the padding WITHOUT shrinking the label
     box (label stays centered on top). slice 40 of the 128px art captures the
     rounded corner. Scoped to the JPS ids → global .btn-primary untouched. */
  background: none;
  border-style: solid;
  border-width: 0;
  border-image: var(--jps-btn) 40 fill / 20px / 0 stretch;   /* jps_button (gold) */
  color: #3a1d04;                        /* dark label — reads on the gold art */
  text-shadow: 0 1px 1px rgba(255, 225, 150, 0.5);
  box-shadow: none;
}
#js-quick-pick-btn:disabled,
#js-team-play-btn:disabled,
#js-add-row-btn:disabled,
#js-buy-btn:disabled,
#js-redeem-btn:disabled { opacity: 0.5; }

/* Buy + Use tickets — EQUAL width, aligned side by side. When redeem is hidden
   (display:none) Buy grows to full width. Uniform padding on both so their
   border-box widths match (btn-primary and btn-ticket ship different padding,
   which otherwise left the two ~16px apart at flex:1). */
/* ┌── HAND-TUNED by Mattias (Jackpot Strike) — DO NOT REGENERATE these values. ──┐
   If a future task must restructure this area, PRESERVE these exact position/size
   values. Only change them on Mattias's explicit request. */
.js-pay-row {
  display: flex; gap: 24px; align-items: stretch;
  /* nudge: total width of the Buy/Use button row — adjust to taste */
  max-width: 80%;
  margin-inline: auto;   /* keep it centered as it narrows */
}
.js-pay-row > button {
  flex: 1 1 0;
  min-width: 10;
  width: auto;
  padding: 14px var(--space-md);
}
/* └── end HAND-TUNED ──┘ */

.js-section-label {
  font-family: var(--font-display);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

/* Staged rows + My Rows shared bits */
.js-staged-empty { text-align: center; color: var(--text-secondary); font-size: 0.85rem; padding: var(--space-sm); }
.js-staged-list { display: flex; flex-direction: column; gap: var(--space-sm); }
/* Staged row — the jps_numbers_selected strip art (880×130) as the row bg, with
   the index (left), 7 numbers (spread across the slots), and remove ✕ (right)
   laid ON it. Strip URL is the --jps-strip var set by _paintArt (absolutized). */
.js-staged-row {
  display: flex; align-items: center; gap: var(--space-xs);
  aspect-ratio: 880 / 130;
  background: var(--jps-strip) center / 100% 100% no-repeat;
  border: none;
  border-radius: 0;
  padding: 0 3.5%;                 /* inset idx/remove off the strip's frame edges */
  overflow: hidden;
}
.js-staged-idx {
  font-family: var(--font-display); font-size: 0.85rem; font-weight: 700;
  color: var(--gold-bright);
  min-width: 1.2rem; text-align: center;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85);
  flex: 0 0 auto;
}
.js-row-nums {
  display: flex; flex: 1; flex-wrap: nowrap;
  align-items: center; justify-content: space-around; gap: 2px;
}
.js-ball {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.7rem; height: 1.7rem; padding: 0 4px;
  font-family: var(--font-mono); font-size: 0.8rem; font-weight: 700;
  color: var(--gold-light);
  background: var(--bg-mid);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-full);
}
/* Numbers ON the strip — scoped to staged rows so the My Rows .js-ball (4c-3) is
   UNTOUCHED. Drop the chip chrome; cream numbers spaced across the strip slots. */
.js-staged-row .js-ball {
  min-width: 0; width: auto; height: auto; padding: 0;
  font-size: 0.98rem;
  color: #fff8e6;
  background: none; border: none; border-radius: 0;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
}
.js-qp-tag { flex: 1; color: var(--teal-light); font-family: var(--font-mono); font-size: 0.85rem; }
.js-remove-row {
  background: none; border: none; color: var(--gold-bright);
  font-size: 1rem; cursor: pointer; padding: 4px;
  flex: 0 0 auto;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.85);
}

/* ── My Rows 3-SLICE EXPANDING FRAME ─────────────────────────────────────────
   Fixed top/bottom caps keep the ornate corners undistorted; the mid strip
   STRETCHES to fit however many draw-groups/rows the list holds. Slice art is
   set as CSS vars by _paintArt (jpsAsset, absolutized). The mid overlaps the caps
   by ~1px so no seam shows. */
/* ┌── HAND-TUNED by Mattias (Jackpot Strike) — DO NOT REGENERATE these values. ──┐
   If a future task must restructure this area, PRESERVE these exact position/size
   values. Only change them on Mattias's explicit request. */
.js-myrows-frame {
  display: flex;
  flex-direction: column;
  width: 100%;
}
.js-myrows-frame-top {
  aspect-ratio: 880 / 110;              /* fixed cap — top corners + right tab notch */
  background: var(--jps-frame-top) center / 100% 100% no-repeat;
}
.js-myrows-frame-mid {
  flex: 1 1 auto;                        /* GROWS with the list content */
  margin: -1px 0;                        /* overlap caps → seamless join */
  padding: var(--space-xs) 5% var(--space-xs);   /* content sits inside the baked gold edges */
  background: var(--jps-frame-mid) center / 100% 100% repeat-y;  /* 4px strip stretches */
}
.js-myrows-frame-bottom {
  aspect-ratio: 880 / 20;               /* fixed cap — bottom corners */
  background: var(--jps-frame-bottom) center / 100% 100% no-repeat;
}
/* └── end HAND-TUNED ──┘ */

/* My Rows view */
.js-my-rows-list { display: flex; flex-direction: column; gap: var(--space-md); }
/* ┌── HAND-TUNED by Mattias (Jackpot Strike) — DO NOT REGENERATE these values. ──┐
   If a future task must restructure this area, PRESERVE these exact position/size
   values. Only change them on Mattias's explicit request. */
.js-draw-group {
  /* Transparent — numbers sit directly on the maroon 3-slice frame (no dark card).
     (.js-draw-group--won keeps its gold glow via box-shadow.) */
  background: transparent;
  border: none;
  border-radius: var(--radius-md);
  padding: var(--space-md);
  display: flex; flex-direction: column; gap: var(--space-sm);
}
/* └── end HAND-TUNED ──┘ */
.js-draw-head { display: flex; align-items: center; gap: var(--space-sm); }
.js-draw-date { font-family: var(--font-mono); font-size: 0.9rem; color: var(--gold-light); }
.js-draw-count { margin-left: auto; font-size: 0.75rem; color: var(--text-secondary); }
.js-status-badge {
  font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.06em;
  padding: 2px 8px; border-radius: var(--radius-full);
  background: var(--bg-mid); color: var(--text-secondary); border: 1px solid var(--border-teal);
}
.js-status-open { color: var(--teal-light); border-color: var(--teal-light); }
.js-myrow { display: flex; }

/* Jackpot Strike — live draw countdown */
.js-countdown {
  display: flex;            /* toggled to none by JS when there's no open draw */
  flex-direction: column;
  align-items: center;
  gap: 2px;
  background: var(--bg-card);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-md);
  padding: var(--space-sm) var(--space-md);
  box-shadow: inset 0 0 14px var(--teal-glow);
}
.js-countdown-label {
  font-family: var(--font-display);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.js-countdown-time {
  font-size: 1.35rem;
  letter-spacing: 0.06em;
  color: var(--gold-bright);
  text-shadow: var(--shadow-gold);
}

/* ── Jackpot Strike — LIVE-DRAW TAKEOVER (freeze window + drawing) ──────────
   Replaces the pick/buy surface once entries close. Prominent neon panel with
   the dramatic 5-minute MM:SS countdown as the centrepiece. CSS-only (L1a);
   art can enhance later via the same slot approach. */
.js-takeover {
  display: none;                /* toggled to block by JS on the Pick tab in freeze/drawing */
  text-align: center;
  padding: var(--space-xl) var(--space-lg);
  background:
    radial-gradient(120% 120% at 50% 0%, rgba(245,166,35,0.10), transparent 70%),
    var(--bg-card);
  border: 2px solid var(--gold);
  border-radius: var(--radius-lg);
  box-shadow: 0 0 26px var(--gold-glow), inset 0 0 22px rgba(245,166,35,0.10);
  animation: js-takeover-pulse 2.4s ease-in-out infinite;
}
@keyframes js-takeover-pulse {
  0%, 100% { box-shadow: 0 0 22px var(--gold-glow),    inset 0 0 18px rgba(245,166,35,0.08); }
  50%      { box-shadow: 0 0 38px var(--gold-bright),  inset 0 0 26px rgba(245,166,35,0.16); }
}
.js-takeover-badge {
  font-family: var(--font-display);
  font-size: 1.55rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  color: var(--gold-bright);
  text-shadow: 0 0 14px var(--gold-glow), 0 0 30px rgba(245,166,35,0.45);
}
.js-takeover-sub {
  margin-top: 2px;
  font-family: var(--font-display);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.js-takeover-clock {
  display: flex;                /* toggled to none by JS in the 'drawing' phase */
  flex-direction: column;
  align-items: center;
  gap: 2px;
  margin: var(--space-lg) auto var(--space-md);
}
.js-takeover-time {
  font-size: 3.4rem;
  line-height: 1;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--gold-bright);
  text-shadow: 0 0 18px var(--gold-glow), 0 0 40px rgba(245,166,35,0.4);
}
.js-takeover-unit {
  font-family: var(--font-display);
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.js-takeover-msg {
  margin: var(--space-md) auto 0;
  max-width: 22rem;
  font-size: 0.95rem;
  color: var(--text-primary);
}
.js-takeover-hint {
  margin-top: var(--space-md);
  font-size: 0.78rem;
  color: var(--text-secondary);
}
.js-takeover-hint strong { color: var(--gold); }

/* Jackpot Strike — "Use a ticket" redeem button (distinct from the cash Buy
   button: gold OUTLINE / voucher treatment vs the solid-gold Buy). */
.btn-ticket {
  width: 100%;
  padding: var(--space-md);
  font-family: var(--font-display);
  font-size: 0.92rem;
  letter-spacing: 0.04em;
  color: var(--gold-bright);
  background: rgba(245,166,35,0.08);
  border: 1px dashed var(--gold);
  border-radius: var(--radius-md);
  box-shadow: inset 0 0 14px var(--gold-glow);
  cursor: pointer;
  transition: background 0.12s ease;
}
.btn-ticket:hover:not(:disabled) { background: rgba(245,166,35,0.14); }
.btn-ticket:active:not(:disabled) { filter: brightness(0.92); }
.btn-ticket:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; }
.btn-ticket .js-ticket-count {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  opacity: 0.85;
}

/* Jackpot Strike — My Rows results (published draws) */
.js-draw-group--won { border-color: var(--gold); box-shadow: 0 0 14px rgba(245,166,35,0.18); }

.js-results {
  display: flex; flex-direction: column; gap: var(--space-sm);
  padding: var(--space-sm) 0;
  border-top: 1px solid var(--border-teal);
}
.js-results-row { display: flex; flex-direction: column; gap: 4px; }
.js-results-label {
  font-family: var(--font-display);
  font-size: 0.6rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-secondary);
}

/* Ball treatments: drawn (header), player-match, supplementary-match */
.js-ball--drawn,
.js-ball--match {
  color: var(--bg-deep);
  background: var(--gold);
  border-color: var(--gold-bright);
  box-shadow: 0 0 8px var(--gold-glow);
  font-weight: 700;
}
.js-ball--supp {
  color: var(--teal-light);
  border-color: var(--teal-light);
  box-shadow: inset 0 0 6px var(--teal-glow);
}

/* ── My Rows entry numbers on the jps_selected orange tile ────────────────────
   Reuses the pick-tab selected var (--jps-tile-on, set on #screen-jackpot-strike
   by _paintArt). Scoped to plain entry balls via :not(--match):not(--supp) so the
   published-results highlights (gold match / teal supplementary) AND the header
   .js-ball--drawn stay intact — only the un-highlighted entry numbers get the
   tile. Staged-row balls (.js-staged-row .js-ball, 4c-2) are also unaffected. */
/* ┌── HAND-TUNED by Mattias (Jackpot Strike) — DO NOT REGENERATE these values. ──┐
   If a future task must restructure this area, PRESERVE these exact position/size
   values. Only change them on Mattias's explicit request. */
.js-myrow .js-ball:not(.js-ball--match):not(.js-ball--supp) {
  background-color: transparent;
  background-image: var(--jps-tile-on);   /* jps_selected — same as pick-tab selected */
  background-size: 100% 100%;
  background-repeat: no-repeat;
  border: none;
  border-radius: 0;
  color: #fff;                            /* white number, readable on orange */
}
/* My Rows entries ~40% larger than the base ball (1.7rem→2.4rem, 0.8→1.12rem).
   Sizes ALL entry balls (incl. match/supp) so the row stays uniform; the tile art
   scales with the box via background-size:100% 100%. 7 × 2.4rem + gaps fit the
   frame (space-around). Scoped to .js-myrow → pick tiles/staged/drawn unchanged. */
.js-myrow .js-ball {
  min-width: 2.4rem;
  height: 2.4rem;
  font-size: 1.12rem;
}

/* Divider line between each entry row (none after the last row of a group). */
.js-myrow {
  border-bottom: 1px solid rgba(255, 215, 150, 0.25);
  padding-bottom: var(--space-sm);
}
.js-myrow:last-child { border-bottom: none; padding-bottom: 0; }
/* └── end HAND-TUNED ──┘ */

/* Win / no-win banners */
.js-win-banner {
  font-family: var(--font-display);
  font-size: 1rem; letter-spacing: 0.03em; text-align: center;
  color: var(--gold-bright); text-shadow: var(--shadow-gold);
  background: rgba(245,166,35,0.08);
  border: 1px solid var(--gold);
  border-radius: var(--radius-md);
  padding: var(--space-sm) var(--space-md);
}
.js-paid-tag {
  font-family: var(--font-mono); font-size: 0.58rem; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--teal-light);
  border: 1px solid var(--teal-light); border-radius: var(--radius-full);
  padding: 1px 6px; margin-left: 6px; vertical-align: middle;
}
.js-nowin-banner {
  font-size: 0.8rem; color: var(--text-secondary); text-align: center; padding: 4px 0;
}

/* Per-row win treatment + meta (match count + prize badge) */
.js-myrow { align-items: center; gap: var(--space-sm); }
.js-myrow--win {
  border: 1px solid var(--gold);
  border-radius: var(--radius-md);
  background: rgba(245,166,35,0.06);
  box-shadow: inset 0 0 12px var(--gold-glow);
  padding: 4px var(--space-sm);
}
.js-row-meta {
  margin-left: auto; flex-shrink: 0;
  display: flex; align-items: center; gap: var(--space-sm);
}
.js-match-count { font-family: var(--font-mono); font-size: 0.7rem; color: var(--text-secondary); white-space: nowrap; }
.js-prize-badge {
  font-family: var(--font-display); font-size: 0.7rem; white-space: nowrap;
  color: var(--bg-deep); background: var(--gold);
  border-radius: var(--radius-full); padding: 2px 8px;
  box-shadow: 0 0 8px var(--gold-glow);
}

/* Jackpot Strike — "results are in" nudge banner (home + Pick tab) */
.js-results-banner {
  display: block;               /* toggled to none by JS when acknowledged */
  width: 100%;
  max-width: 440px;
  margin: 0 auto var(--space-md);
  text-align: center;
  font-family: var(--font-display);
  font-size: 0.88rem;
  letter-spacing: 0.02em;
  color: var(--bg-deep);
  background: var(--grad-teal);
  border: 1px solid var(--teal-light);
  border-radius: var(--radius-md);
  padding: var(--space-sm) var(--space-md);
  box-shadow: var(--shadow-teal);
  cursor: pointer;
  animation: jsBannerPulse 2.4s ease-in-out infinite;
}
.js-results-banner:active { filter: brightness(0.94); }
@keyframes jsBannerPulse {
  0%, 100% { box-shadow: var(--shadow-teal); }
  50%      { box-shadow: 0 0 22px var(--teal-light), 0 0 40px rgba(0,196,167,0.3); }
}

/* =============================================================
   JACKPOT STRIKE — v180 REDESIGN (migration #68)
   Every rule here is scoped to a class _paintRedesign() sets on
   #screen-jackpot-strike only when that piece's art is present, so a
   missing key — or a theme without #68, or no theme at all — renders
   exactly as before. The HAND-TUNED blocks above are UNTOUCHED: they
   describe the OLD art, still apply to the fallback, and are overridden
   here only while the new art is on screen.

   ╔══ MEASURED AGAINST (live CMS, re-measured for v181, 2026-09-16) ════╗
   ║ gameIcons.jackpot.<key>             px        uploaded (UTC)         ║
   ║  header        …header-1789575402114.webp 940x573   16:16            ║
   ║  panel_top     …panel_top-1789583365692   893x926   18:29 (re-upload)║
   ║  panel_strip   (18:37 re-upload)          867x1                      ║
   ║  panel_bottom  …panel_bottom-1789583384494 893x159  18:29 (was 1367) ║
   ║  rows_top      …rows_top-1789581379592    886x150   17:56 (was 893)  ║
   ║  rows_strip / rows_bottom                 886x1 / 886x52   17:56     ║
   ║  number_circle / _selected 83x83 · row_slot 77x78 · sponsor 896x190  ║
   ║  btn_random_pick 259x82 (17:33) · btn_team_play 276x82 (17:33)       ║
   ║  btn_add_row 257x81 (17:33) · btn_buy_rows 412x93 / btn_tickets      ║
   ║  410x93 (19:13, label NOT baked; buy keeps a faint ghost of it)      ║
   ║                                                                      ║
   ║ WHY STALE VALUES WOULD STILL LOOK PLAUSIBLE: every coordinate below ║
   ║ is a % of its piece, so a re-export at a new size keeps landing     ║
   ║ NEAR its feature — the way a Poker King cabinet swap sat unnoticed  ║
   ║ for a week, and the way six of these files were replaced between    ║
   ║ v180 and v181. The sizes are CHECKED at runtime (JPS2_ART in         ║
   ║ jackpot-strike.js) — that check is what caught the re-uploads; for  ║
   ║ `header` (a REUSED key) the shape decides old vs new layout.        ║
   ╚══════════════════════════════════════════════════════════════════════╝

   MEASURED (mirror pairs, not a brightness cut — both sides must sum to
   the piece's width):
   · PICK NUMBERS has a WAIST. panel_top: rails x 3→889 (sum 893) from y 93
     down to y 853, stepping in over y 853–885 to x 23→867 (sum 891).
     panel_strip is 867 wide and NOT centred in its own canvas (rails
     13→857): placed at x = 10 of 893 it lands on 23→867 exactly (v180:
     mean RGBA difference 0.03 there vs 1.6–3.8 one px off). panel_bottom
     is now the 159-row cap alone (v180's 1367-row canvas carried a ghost
     of the whole layout above it): row 0 is 23→867 = the strip; red 95 vs
     the strip's 97.
   · MY ROWS has no waist and is 886 wide — the pick panel's wide body
     (x 3→889) — so it is drawn at x 3 of 893. rows_top ends at red 82,
     the strip is 82, rows_bottom starts at 81: v180's 11-level step at
     the top join is gone in the re-export. Interior x 24–862 of 886.
   · Both strips are ONE pixel tall, so every tile is identical and there
     is no tile-to-tile seam to have. They are drawn STRETCHED
     (100% 100%, no-repeat) — pixel-for-pixel the same as tiling a 1px
     row, with no sub-pixel tile boundaries. `round` is used nowhere.
   · Tabs (mirror about x 445.5, in 893 units): active red 60–456 (pick)
     / 435–831 (rows), y 17–76; inactive dark to y 80. Body from y 85.
     The notch boxes (61–446 / 446–831, from y 14) still cover them.
   · Header wells (mirror about x 470): WALLET plaque x 30–170 and TICKETS
     x 771–910, free band under the baked label y 487–542; CURRENT JACKPOT
     well x 217–721, label y 375–397, value area y 402–508; NEXT DRAW bar
     x 238–703 y 520–566, baked label x 268–462 → value x 466–700. The
     header's last row (572) and panel_top's row 0 are both opaque.
   Units: cqw of .jps-content (the panel width); 1 source px of a
   893-wide piece = 0.11198cqw.
   ============================================================= */

/* Container for everything sized in panel units (only while new art shows). */
#screen-jackpot-strike.jps--art .jps-content { container-type: inline-size; }

/* ── COLUMN FIT (new header or panel; K/FIXED per combination) ───────────
   The new header (0.6096 x W tall) plus the pick panel's caps
   (1.0370 + 0.1781 x W) are far taller than the old pieces, so at full width
   the screen would scroll where it did not before. The column is sized from
   the viewport HEIGHT so the header, the whole pick panel and one staged row
   fit above the nav wherever the old screen fitted; it never exceeds the old
   440px. The floor is the column at which the old screen's shortest fit (no
   rows, 698px tall at 360px wide) is still met: 264px in v180, 284px since
   the v181 order (sponsor inside the panel, header flush) — circles 30.5px. */
#screen-jackpot-strike.jps--v2 {
  padding-top: 10px;
  /* The screen rule above drops .screen's nav clearance (padding-bottom:
     var(--space-md)), so on a long, scrolled list the LAST content ended under
     the fixed nav — in the old layout that is the Buy button with ~10 staged
     rows. Restored here for the redesign. */
  padding-bottom: var(--nav-height);
  /* Height = K x column + FIXED, measured with ONE staged row (the taller
     case at every column from the floor to 440px): column pinned at 300 and
     400px, natural height incl. the 68px nav clearance, +3px for rounding.
     v181 order (sponsor inside the panel) — new header + new panel, no
     sponsor: 662.38 / 819.70 → K 1.5732, FIXED 194px. */
  --jps2-k: 1.5732;
  --jps2-fixed: 194px;   /* ← nudge to retune the fit (no sponsor) */
  --jps2-min: 284px;
}
/* …with the sponsor (v183, banner gaps included): natural height with one
   row at columns 284 / 300 / 358 / 400 / 440 = 703.66 / 733.55 / 843.14 /
   923.21 / 999.44 — slightly convex (the gaps' px parts fade as the column
   grows), so the line through 300 and 400 is used: K 1.8966, 164.6px, +3px
   → 168px; it is within 0.5px of every point, inside the 3px.
   (v182: K 1.7672, FIXED 194px.) */
#screen-jackpot-strike.jps--v2.jps--sponsor {
  --jps2-k: 1.8966;
  --jps2-fixed: 168px;   /* ← nudge to retune the fit (with the sponsor) */
}
/* Partial themes (v181: the fit and the nav clearance apply whenever EITHER
   tall piece is new — with the header art missing, the pay buttons had
   ended up under the nav with no way to scroll them clear).
   New header over the OLD panel: 717.51 / 888.15 → K 1.7064, FIXED 209px;
   with the sponsor (at the foot of the column) 789.12 / 980.97 → K 1.9185,
   FIXED 217px, and its floor is 250px so the old screen's shortest fit
   (no rows, 698px at 360 wide) is still met.
   OLD header over the new panel: 623.19 / 751.44 → K 1.2825, FIXED 242px;
   with the sponsor (v183, banner gaps included) 694.36 / 854.95 → K 1.6059,
   FIXED 216px (v182: 1.4764 / 242). */
#screen-jackpot-strike.jps--v2:not(.jps--panel2) {
  --jps2-k: 1.7064;
  --jps2-fixed: 209px;
}
#screen-jackpot-strike.jps--v2:not(.jps--hdr2) {
  --jps2-k: 1.2825;
  --jps2-fixed: 242px;
}
#screen-jackpot-strike.jps--v2.jps--sponsor:not(.jps--panel2) {
  --jps2-k: 1.9185;
  --jps2-fixed: 217px;
  --jps2-min: 250px;
}
#screen-jackpot-strike.jps--v2.jps--sponsor:not(.jps--hdr2) {
  --jps2-k: 1.6059;
  --jps2-fixed: 216px;
}
/* A pay-row button without its art falls back to the current text button,
   up to 51.5px tall (47.5 inside the v181 panel) against the art's 44 — the
   fit allows for it. */
#screen-jackpot-strike.jps--v2:has(.js-pay-row > button:not(.jps-art-btn)) { --jps2-extra: 8px; }
#screen-jackpot-strike.jps--v2 > .jps-content {
  max-width: clamp(var(--jps2-min), calc((100dvh - var(--jps2-fixed) - var(--jps2-extra, 0px)) / var(--jps2-k)), 440px);
  gap: 8px;
}

/* ── HEADER (new 940x573 art) ─────────────────────────────────────────── */
.jps-hdr-jackpot { display: none; }
#screen-jackpot-strike.jps--hdr2 .jps-header {
  aspect-ratio: 940 / 573;
  margin-top: 0;
  /* v181: flush with the pick panel, as in the mockup. Both edges are opaque
     to the pixel (header row 572, panel_top row 0), so 0 = touching. The
     column's own 8px gap is cancelled here. */
  margin-bottom: calc(var(--jps2-hdr-gap) - 8px);
  container-type: inline-size;
}
#screen-jackpot-strike.jps--hdr2 { --jps2-hdr-gap: 0px; }   /* ← tunable: header → PICK NUMBERS gap */
#screen-jackpot-strike.jps--hdr2 .jps-logo { display: none; }   /* the title is baked into the header */
#screen-jackpot-strike.jps--hdr2 .jps-hdr-wallet,
#screen-jackpot-strike.jps--hdr2 .jps-hdr-tickets {
  top: 84.99%;  height: 9.599%;  width: 14.894%;
  right: auto;
  display: flex; align-items: center; justify-content: center;
  font-size: 3.5cqw;
}
#screen-jackpot-strike.jps--hdr2 .jps-hdr-wallet  { left: 3.191%; }
#screen-jackpot-strike.jps--hdr2 .jps-hdr-tickets { left: 81.915%; }   /* mirror of the wallet plaque */
#screen-jackpot-strike.jps--hdr2 .jps-hdr-jackpot {
  display: flex; align-items: center; justify-content: center;
  position: absolute;
  left: 23.085%; width: 53.617%;
  top: 70.157%;  height: 18.325%;
  font-family: var(--font-mono);
  font-size: 7cqw;
  font-weight: 700;
  color: var(--gold-bright);
  text-shadow: 0 0 10px var(--gold-glow), 0 1px 3px rgba(0,0,0,0.9);
  white-space: nowrap;
}
#screen-jackpot-strike.jps--hdr2 .js-countdown.jps-hdr-countdown {
  left: 49.574%; width: 24.894%;
  top: 90.925%;  height: 7.853%;
  bottom: auto;
  transform: none;
  justify-content: center;
  align-items: center;
}
#screen-jackpot-strike.jps--hdr2 .jps-hdr-countdown .js-countdown-time {
  font-size: 2.9cqw;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
/* The game hides the countdown outside the normal phase (freeze, drawing, no
   open draw). NEXT DRAW is baked in, so the well would read "NEXT DRAW ·" and
   stop — a dash fills it instead, like the jackpot's "€—". */
#screen-jackpot-strike.jps--hdr2 .jps-header:has(> #js-countdown[style*="none"])::after {
  content: '—';
  position: absolute;
  left: 49.574%; width: 24.894%;
  top: 90.925%;  height: 7.853%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono);
  font-size: 2.9cqw;
  font-weight: 700;
  color: var(--gold-light);
  pointer-events: none;
}

/* ── PICK NUMBERS PANEL (three slices + the waist) ───────────────────────
   One element carries all three: the top cap is its own background (drawn
   once, from the top), the strip is ::before (at the NARROW width, x = 10 of
   893), the bottom cap is ::after (v181: panel_bottom is now the 159-row cap
   alone). The strip overlaps each cap by 1px so no sub-pixel
   gap can open; the caps paint over it. min-height = both caps, so the strip
   is zero-height until the staged rows push the panel taller. */
#screen-jackpot-strike.jps--panel2 .js-pick-frame {
  position: relative;
  isolation: isolate;
  padding: 0;
  gap: 0;
  background: var(--jps2-panel-top) center top / 100% auto no-repeat;
  min-height: calc(103.695cqw + 17.805cqw);
}
#screen-jackpot-strike.jps--panel2 .js-pick-frame::before {
  content: '';
  position: absolute;
  z-index: 0;
  top: calc(103.695cqw - 1px);
  bottom: calc(17.805cqw - 1px);
  left: 1.1198cqw;
  width: 97.088cqw;
  background: var(--jps2-panel-strip) center / 100% 100% no-repeat;
  pointer-events: none;
}
#screen-jackpot-strike.jps--panel2 .js-pick-frame::after {
  content: '';
  position: absolute;
  z-index: 0;
  left: 0; right: 0; bottom: 0;
  height: 17.805cqw;
  background: var(--jps2-panel-bottom) center bottom / 100% auto no-repeat;
  pointer-events: none;
}
#screen-jackpot-strike.jps--panel2 .js-pick-frame > * { position: relative; z-index: 1; }
/* Bottom clearance. panel_bottom's interior runs on to row ~144 of 159 (it
   only darkens, 95→75 red); the horizontal rail starts at ~148 and the
   side-rail flare (rows 26–48) steps OUTWARD. So content may run down to row
   ~127: 32 rows = 3.5cqw, not the whole 17.8cqw cap. (Re-checked on the v181
   re-upload.) */
#screen-jackpot-strike.jps--panel2 { --jps2-cap-clear: 3.5cqw; }

/* The wide section: tabs 0–85, content from y 100, rails' inner edge x 40.
   The grid and its buttons sit in it; the waist (v181 art) steps in over
   y 853–885. The tail is NOT held
   below the waist: the rails step in from the OUTSIDE (x 3→23) while the inner
   edge stays left of x 55, where the tail's padding already keeps it — so the
   tail follows the buttons directly (that wait cost ~30px of empty panel). */
#screen-jackpot-strike.jps--panel2 #js-pick-view {
  box-sizing: border-box;
  padding: 11.198cqw 4.479cqw 2.5cqw;
}
/* The takeover is taller than the wide section at narrow columns (its last
   line, the My Rows hint, measured past it) — so it is not capped: the panel
   grows around it like it does for staged rows. The tail (hidden while
   frozen) normally clears the bottom cap, so the takeover does it here;
   flow-root keeps that margin inside the frame. */
#screen-jackpot-strike.jps--panel2 .js-pick-frame { display: flow-root; }
#screen-jackpot-strike.jps--panel2 #js-takeover {
  margin: 11.198cqw 4.479cqw var(--jps2-cap-clear);
}
#screen-jackpot-strike.jps--panel2 .js-grid {
  grid-template-columns: repeat(7, 10.75cqw);
  gap: 0.8959cqw;
  justify-content: center;
}
#screen-jackpot-strike.jps--panel2 .js-num { width: 10.75cqw; font-size: 4.2cqw; }

/* The narrow section (x 55–838) and the bottom cap below it. */
#screen-jackpot-strike.jps--panel2 .js-pick-tail {
  margin-top: 0;
  gap: 1.8cqw;
  padding: 0.5cqw 6.159cqw var(--jps2-cap-clear);
}
#screen-jackpot-strike.jps--panel2 .js-section-label { text-align: center; color: var(--gold-light); }
#screen-jackpot-strike.jps--panel2 .js-staged-empty { padding: 0; }
#screen-jackpot-strike.jps--panel2 .js-staged-list { gap: 0; }
/* The numbers_selected strip is superseded — rows sit on the panel itself. */
#screen-jackpot-strike.jps--panel2 .js-staged-row {
  aspect-ratio: auto;
  background: none;
  padding: 0.9cqw 0;
  border-bottom: 1px solid rgba(255, 215, 150, 0.22);
}
#screen-jackpot-strike.jps--panel2 .js-staged-row:last-child { border-bottom: none; }
#screen-jackpot-strike.jps--panel2 .js-staged-row .js-ball {
  width: 8.2cqw; height: 8.2cqw;
  font-size: 3.4cqw;
  color: #fff3dc;
  background: var(--jps-tile-on) center / 100% 100% no-repeat;
  border-radius: 50%;
}
/* v182: queued rows read like the selection row and My Rows — the dashed
   row_slot with white text. Same 8.2cqw box, so the row height is unchanged
   (the slot's 77x78 is drawn 100% 100%, a 1.3% squash, as in My Rows).
   Without row_slot art they keep the filled circle above. */
#screen-jackpot-strike.jps--panel2.jps--slot2 .js-staged-row .js-ball {
  background-image: var(--jps2-slot);
  color: #fff;
}

/* ── MY ROWS PANEL (three slices, no waist) ──────────────────────────────
   The hand-tuned 3-slice structure is reused; only the art and the caps'
   proportions change. The 1px strip is stretched, as above. */
/* v181: the re-exported My Rows art is 886 wide — exactly the pick panel's
   wide body (rails x 3→889 of 893) — so it is drawn there, not stretched to
   893: its tabs then land on the pick panel's (active 435–831 vs 60–456,
   mirrored about x 445.5). */
#screen-jackpot-strike.jps--rows2 .js-myrows-frame-top,
#screen-jackpot-strike.jps--rows2 .js-myrows-frame-mid,
#screen-jackpot-strike.jps--rows2 .js-myrows-frame-bottom {
  width: 99.2161cqw;
  margin-left: 0.3359cqw;
  margin-right: 0;
}
#screen-jackpot-strike.jps--rows2 .js-myrows-frame-top {
  aspect-ratio: 886 / 150;
  background-image: var(--jps2-rows-top);
}
#screen-jackpot-strike.jps--rows2 .js-myrows-frame-mid {
  background-image: var(--jps2-rows-strip);
  background-repeat: no-repeat;
  background-size: 100% 100%;
}
#screen-jackpot-strike.jps--rows2 .js-myrows-frame-bottom {
  aspect-ratio: 886 / 52;
  background-image: var(--jps2-rows-bottom);
}
/* The HAND-TUNED 2.4rem entry ball (38.4px) was sized for the old, wider
   column: at a 307px fitted column seven of them measured 280.8px in a
   244.6px row and the last ball crossed the right rail (353 vs 340). Capped
   from the column width instead — min() so it never exceeds the tuned size. */
#screen-jackpot-strike.jps--rows2 .js-myrow .js-ball {
  min-width: min(2.4rem, 9.4cqw);
  height: min(2.4rem, 9.4cqw);
  font-size: min(1.12rem, 4.3cqw);
}
/* v181: My Rows entry numbers use the row_slot circle with white text (the
   HAND-TUNED rule paints them on the selected tile). The published-results
   highlights (match / supplementary) keep their own treatment. */
#screen-jackpot-strike.jps--slot2 .js-myrow .js-ball:not(.js-ball--match):not(.js-ball--supp) {
  background-image: var(--jps2-slot);
  color: #fff;
}

/* ── TABS (both panels new) ───────────────────────────────────────────────
   Each new cap carries BOTH tab shapes, so only the active panel is shown —
   the old layered stack (inactive frame at 0.6 behind) would double them.
   The labels sit exactly on the baked tabs. A tab is ~19–31px tall, so its
   touch area is extended to 44px — 6px down (the grid starts 7px below it at
   the narrowest, 284px column: 1px clear) and the rest upward, over the
   non-interactive header. */
#screen-jackpot-strike.jps--panel2.jps--rows2 .js-frame:not(.js-frame--active) { display: none; }
#screen-jackpot-strike.jps--panel2.jps--rows2 .js-frame-stack { overflow: visible; }
#screen-jackpot-strike.jps--panel2.jps--rows2 .js-frame-notch {
  top: 1.5678cqw;
  height: 7.1669cqw;
  width: 43.113cqw;
  right: auto;
  padding: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 3.3cqw;
  opacity: 0.72;
}
#screen-jackpot-strike.jps--panel2.jps--rows2 .js-frame-notch--pick { left: 6.8309cqw; }
#screen-jackpot-strike.jps--panel2.jps--rows2 .js-frame-notch--rows { left: 49.944cqw; }
#screen-jackpot-strike.jps--panel2.jps--rows2 .js-frame-notch--active { opacity: 1; }
#screen-jackpot-strike.jps--panel2.jps--rows2 .js-frame-notch::before {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: calc(-1 * max(0px, 44px - 100% - 6px));
  bottom: -6px;
}

/* ── NUMBER CIRCLES ───────────────────────────────────────────────────────
   The art arrives through the existing --jps-tile-off / --jps-tile-on vars.
   The GREYED state (seven chosen) is DERIVED from number_circle — no third
   asset exists. */
#screen-jackpot-strike.jps--num2 .js-num {
  color: #5b2b07;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.35);
  border-radius: 50%;
}
#screen-jackpot-strike.jps--numsel2 .js-num--on {
  color: #fff3dc;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
#screen-jackpot-strike.jps--num2 .js-num--locked {
  opacity: 0.55;
  filter: grayscale(1) brightness(0.8);
}

/* ── ROW SLOTS (the row being built) ─────────────────────────────────────*/
.jps-row-slots { display: none; }
#screen-jackpot-strike.jps--slot2 .jps-row-slots {
  display: flex;
  justify-content: center;
  gap: 1.3438cqw;
  margin-top: 2.2cqw;
}
.jps-slot {
  width: 8.7346cqw;
  aspect-ratio: 77 / 78;
  display: flex; align-items: center; justify-content: center;
  background: var(--jps2-slot) center / 100% 100% no-repeat;
  font-family: var(--font-mono);
  font-size: 3.6cqw;
  font-weight: 700;
  color: #fff3dc;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
/* v181: a picked number keeps the dashed row_slot circle, in white. */
.jps-slot--filled { color: #fff; }
/* The slots say what the counter said; it stays for screen readers. */
#screen-jackpot-strike.jps--slot2 .js-counter {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  margin: 0;
}

/* ── BUTTONS (labels BAKED into every piece of art) ───────────────────────
   The live label stays in the DOM (accessible name; BUY/TICKETS keep writing
   their count / price / state into it) but is not drawn over the art. Each
   box is the art's width and at least 44px tall; the art is centred in it.
   Pressed = darker + art nudged 2px down; disabled = faded + desaturated.
   Neither changes the box, and nothing here uses a transform (v151). */
#screen-jackpot-strike .jps-art-btn {
  flex: 0 0 auto;
  min-width: 0;
  padding: 0;
  border: none;
  border-image: none;
  border-radius: 0;
  box-shadow: none;
  background: var(--jps2-btn) center / 100% auto no-repeat;
  font-size: 0;
  color: transparent;
  text-shadow: none;
  transform: none;
  transition: filter 0.12s ease;
}
#screen-jackpot-strike .jps-art-btn:active {
  filter: brightness(0.88);
  background-position: center calc(50% + 2px);
  transform: none;
}
#screen-jackpot-strike .jps-art-btn:disabled {
  opacity: 0.55;
  filter: saturate(0.15) brightness(0.92);
  background-position: center;
  transform: none;
}
#screen-jackpot-strike .jps-art-btn .ripple { display: none; }
/* The game's inline spans (ticket count, price) carry their own font-size —
   collapsed too, so no invisible text box sits inside the art. */
#screen-jackpot-strike .jps-art-btn * { font-size: 0; }
#screen-jackpot-strike .jps-art-btn--random_pick { width: 26.103cqw; height: max(44px, 8.2643cqw); }
#screen-jackpot-strike .jps-art-btn--team_play   { width: 27.815cqw; height: max(44px, 8.2640cqw); }   /* 276x82 (v181) */
#screen-jackpot-strike .jps-art-btn--add_row     { width: 25.900cqw; height: max(44px, 8.1632cqw); }   /* 257x81 (v181) */
#screen-jackpot-strike .jps-art-btn--buy_rows    { width: 41.523cqw; height: max(44px, 9.3729cqw); }
#screen-jackpot-strike .jps-art-btn--tickets     { width: 41.321cqw; height: max(44px, 9.3729cqw); }

#screen-jackpot-strike .js-pick-actions:has(> .jps-art-btn) {
  justify-content: center;
  align-items: center;
  gap: 1.3438cqw;
  margin-top: 2.2cqw;
}
#screen-jackpot-strike .js-pay-row:has(> .jps-art-btn) {
  max-width: none;
  justify-content: center;
  align-items: center;
  gap: 1.3438cqw;
}
/* Fallback row with Team Play added: three text buttons must fit one line. */
#screen-jackpot-strike .js-pick-actions > button:not(.jps-art-btn) {
  padding-inline: 6px;
  font-size: 0.82rem;
  white-space: nowrap;
}
/* v181: the pay row now sits inside the panel's narrow section, where a
   fallback "Buy 1 row €5.00" wrapped to two lines (75px) — kept to one. */
#screen-jackpot-strike.jps--panel2 .js-pay-row { max-width: none; gap: 1.3438cqw; }
#screen-jackpot-strike.jps--panel2 .js-pay-row > button:not(.jps-art-btn) {
  padding-inline: 8px;
  font-size: 0.82rem;
  white-space: nowrap;
}

/* v181: BUY ROWS / TICKETS art is BLANK — the label is live, drawn from
   data-label (set by _syncPayLabels) so the game's own button text stays the
   accessible name and is not read twice ("/ ''" = no alt text). Measured on
   the re-exports: the ticket icon ends at x 101 (buy) / 103 (tickets), the
   face's inner edge is ~x 396, so the label box is x 109–388 (centre 248.5).
   The old baked label had 28px caps → Oswald (cap 0.82em) at 34 art px =
   3.43cqw. Pressed: the label follows the art's 2px nudge. */
#screen-jackpot-strike .jps-art-btn--buy_rows,
#screen-jackpot-strike .jps-art-btn--tickets { position: relative; }
#screen-jackpot-strike .jps-art-btn--buy_rows::after,
#screen-jackpot-strike .jps-art-btn--tickets::after {
  content: attr(data-label);
  content: attr(data-label) / '';
  position: absolute;
  left: 26.46%;
  width: 67.72%;
  top: 0;
  bottom: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-size: 3.43cqw;
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1;
  color: #241200;
  white-space: nowrap;
  pointer-events: none;
}
#screen-jackpot-strike .jps-art-btn[data-label-long]::after { font-size: 3.0cqw; }
#screen-jackpot-strike .jps-art-btn--buy_rows:active::after,
#screen-jackpot-strike .jps-art-btn--tickets:active::after { top: 2px; bottom: -2px; }
/* v181: never dimmed. They are disabled only while a request is in flight
   (their label then reads BUYING… / REDEEMING…). */
#screen-jackpot-strike .jps-art-btn--buy_rows:disabled,
#screen-jackpot-strike .jps-art-btn--tickets:disabled {
  opacity: 1;
  filter: none;
}

/* ── SPONSOR PANEL (static) ──────────────────────────────────────────────*/
.jps-sponsor { display: none; }
#screen-jackpot-strike.jps--sponsor .jps-sponsor {
  display: block;
  aspect-ratio: 896 / 190;
  background: var(--jps2-sponsor) center / 100% 100% no-repeat;
}
/* v181: inside the pick panel's narrow section (x 55–838), between YOUR ROWS
   and the pay buttons — moved there by _paintRedesign. */
#screen-jackpot-strike.jps--panel2 .js-pick-tail > .jps-sponsor {
  width: 100%;
  /* v183: breathing room, as VISIBLE gaps (the tail is display:block, so
     these margins are the only spacing). Above = from the last queued ball
     (or the YOUR ROWS line when nothing is queued) to the banner; below =
     from the banner to the pay-button ART. The banner's and the buttons'
     art is opaque to its box edges, so equal numbers look equal once the
     transparent slack each side carries is taken off:
     · above: the queued row's slack under its ball — the row is
       max(8.2cqw ball, 26.5px remove button) + 2 x 0.9cqw padding tall;
     · below: the pay buttons' 44px tap box is taller than their art
       (9.3729cqw) at most widths, and the art is centred in it. */
  --jps2-sponsor-row-slack: calc(0.9cqw + (max(8.2cqw, 26.5px) - 8.2cqw) / 2);
  --jps2-pay-art-slack: calc((max(44px, 9.3729cqw) - 9.3729cqw) / 2);
  margin-top: var(--jps2-sponsor-gap-above);
  margin-bottom: max(0px, calc(var(--jps2-sponsor-gap-below) - var(--jps2-pay-art-slack)));
}
#screen-jackpot-strike.jps--panel2 .js-pick-tail:has(.js-staged-row) > .jps-sponsor {
  margin-top: max(0px, calc(var(--jps2-sponsor-gap-above) - var(--jps2-sponsor-row-slack)));
}
#screen-jackpot-strike.jps--panel2 {
  --jps2-sponsor-gap-above: 4.5cqw;   /* ← tunable: visible gap, YOUR ROWS → banner */
  --jps2-sponsor-gap-below: 4.5cqw;   /* ← tunable: visible gap, banner → BUY ROWS / TICKETS */
}

/* =============================================================
   JACKPOT STRIKE — v184 HISTORY SPLIT (migration #69)
   My Rows lists only current draws; settled, acknowledged draws are in
   Profile → Past draws. Group labels come back as one quiet line.
   ============================================================= */

/* Group label: what state the draw is in, and its date (v131 had removed the
   whole draw-head). Small caps with a status dot — not a header. */
.js-group-label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-display);
  font-size: 0.68rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: rgba(255, 232, 196, 0.78);
}
.js-group-label::before {
  content: '';
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.js-group-label--open::before     { background: var(--teal-light); }
.js-group-label--awaiting::before { background: #ffb347; }
.js-group-label--results::before  { background: var(--gold-bright); }

/* MY ROWS → Profile past draws. A text link, 44px tall to tap. */
.js-history-link {
  display: block;
  min-height: 44px;
  margin: var(--space-xs) auto 0;
  padding: 0 12px;
  background: none;
  border: none;
  color: var(--gold-light);
  font-family: var(--font-display);
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.js-history-link:active { opacity: 0.75; }

/* Profile was a centred placeholder; with a list under it the screen reads
   from the top, and it keeps the nav clearance (its own padding had
   replaced .screen's). */
#screen-profile {
  justify-content: flex-start;
  padding-bottom: calc(var(--nav-height) + var(--space-md));
}
/* .btn-primary carries flex: 1, so in this column Log Out grew to fill the
   screen (439px tall before v184, 196px with the list under it). Its own
   height again. */
#screen-profile #logout-btn { flex: 0 0 auto; }

.jsh {
  width: 100%;
  max-width: 440px;
  margin-top: var(--space-lg);
  text-align: left;
  container-type: inline-size;
}
.jsh-title {
  margin: 0 0 var(--space-sm);
  text-align: center;
  font-family: var(--font-display);
  font-size: 0.95rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold-light);
}
.jsh-list { display: flex; flex-direction: column; gap: var(--space-md); }
/* Each past draw on the game's maroon, so the rows read as the same product. */
.jsh-card {
  background: linear-gradient(180deg, #6a1409 0%, #4c0c05 100%);
  border: 1px solid rgba(255, 200, 90, 0.45);
  border-radius: var(--radius-lg);
  box-shadow: inset 0 0 18px rgba(0, 0, 0, 0.35);
}
/* Entry balls: the HAND-TUNED 2.4rem, capped for narrow phones; the dashed
   row_slot with white text when that art exists (as on the game screen);
   match / supplementary highlights untouched. */
.jsh .js-myrow .js-ball {
  min-width: min(2.4rem, 11.5cqw);
  height: min(2.4rem, 11.5cqw);
  font-size: min(1.12rem, 5.2cqw);
}
.jsh.jsh--slot2 .js-myrow .js-ball:not(.js-ball--match):not(.js-ball--supp) {
  background-image: var(--jps2-slot);
  color: #fff;
}
/* A settled row always has its match count (and a prize badge if it won):
   the meta takes its own line so it never runs off the card. */
.jsh .js-myrow { flex-wrap: wrap; row-gap: 4px; }
.jsh .js-row-nums { flex: 1 1 100%; }
.jsh .js-row-meta { margin-left: auto; }
.jsh-status {
  margin: var(--space-md) 0 0;
  text-align: center;
  font-size: 0.85rem;
  color: var(--text-secondary);
}
.jsh-more {
  display: block;
  min-height: 44px;
  margin: var(--space-md) auto 0;
  padding: 0 var(--space-lg);
  background: transparent;
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-full);
  color: var(--teal-light);
  font-family: var(--font-display);
  font-size: 0.85rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
.jsh-more:active { background: rgba(0, 196, 167, 0.12); }
.jsh-status[hidden],
.jsh-more[hidden] { display: none; }

/* =============================================================
   HOME — L1a structural restyle (Lucky Valley composition).
   Art = [data-slot] placeholders filled by applyThemeSlots().
   PANEL RULE: every dynamic value (jackpot, countdown, tickets,
   players) sits on a solid/semi-opaque panel for legibility once
   art goes behind it in L1b.
   ============================================================= */
#screen-home {
  padding: 0;
  align-items: center;
  /* Transparent — the landscape now comes from the persistent global #app-bg
     behind every screen (was an inner .home-bg + bg-deep fallback). Home looks
     identical; the bg is simply global now. */
  background: transparent;
}

/* Centred content column; bg slot sits behind it */
.home-content {
  position: relative;
  width: 100%;
  max-width: 440px;
  margin: 0 auto;
  /* Reserve the fixed nav (#screen-home sets padding:0, so .home-content owns
     the clearance) + pentagon headroom. NO safe-area inset here — the nav banner
     fills its own inset zone (art to the bottom edge), so reserving the inset as
     extra empty space would push the countdown up on iPhone (v127). */
  padding: 0 0 calc(var(--nav-height) + 22px);
  /* flex:1 fills the .screen flex column (percentage min-height won't resolve
     against a flex-sized parent); the inner flex column lets the countdown
     anchor toward the bottom (margin-top:auto) — compact top, no void above
     the nav. Freed space shows as the desert-road scenery. */
  flex: 1 0 auto;
  display: flex;
  flex-direction: column;
}
.home-content > :not(.home-bg) { position: relative; z-index: 1; }

/* ---- Art-slot base (placeholder container) ---- */
.art-slot {
  display: flex;
  align-items: center;
  justify-content: center;
  background-size: cover;
  background-position: center;
  overflow: hidden;
}
.art-slot .slot-emoji { font-size: 1.6rem; line-height: 1; }
.art-slot .slot-text  {
  font-family: var(--font-display);
  color: var(--gold-bright);
  letter-spacing: 0.04em;
}

/* slot: bg_landscape — full-bleed behind the whole screen (NOT just the 440
   content column), so it bleeds edge-to-edge with no black side bars on any
   phone. cover + top-center never stretches and keeps the sky/logo backdrop
   anchored; position:fixed keeps it put as the page scrolls. Centred + capped
   to the 480px app width so it never spills past the app card on desktop. */
.home-bg {
  position: fixed;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100vw;
  max-width: 480px;
  height: 100%;
  z-index: 0;
  background-size: cover;
  background-position: top center;
  border-radius: 0;
}

/* ---- Hero (logo + signs + players pill) ---- */
.home-hero {
  position: relative;
  width: 100%;
  height: 160px;             /* tightened from 208 (logo art unchanged) */
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* slot: logo_lockup — 280 × 140 (2:1) */
.logo-lockup {
  width: min(280px, 72%);
  aspect-ratio: 2 / 1;
  filter: drop-shadow(0 0 18px rgba(0,196,167,0.45)) drop-shadow(0 4px 12px rgba(0,0,0,0.6));
}
.logo-lockup .slot-text {
  font-size: 1.9rem;
  text-align: center;
  -webkit-text-stroke: 1.5px var(--teal-dark);
  paint-order: stroke fill;
  text-shadow: 0 0 10px var(--gold-glow), 0 2px 0 rgba(0,0,0,0.8);
}

/* (welcome_sign + route_badge overlays removed in L1b polish — now baked into
   bg_landscape; their slots/elements/styles were dropped.) */

/* Players-online pill (top-right) — SOLID panel; SIMULATED count */
.players-pill {
  position: absolute;
  top: 12px; right: 10px;
  display: flex; align-items: center; gap: 6px;
  display: none;   /* hidden for now — re-enable by removing this display:none line */
  background: rgba(8, 24, 22, 0.82);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-full);
  padding: 5px 12px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.4);
}
.players-pill-text { font-family: var(--font-body); font-size: 0.74rem; font-weight: 700; color: var(--teal-light); }
.players-pill #player-count { display: inline-block; }

/* ---- Jackpot hero panel (slot: panel_frame_jackpot) ---- */
/* Container: 408 × ~150 at the 440 column (≈2.7:1, treat as 9-slice frame) */
.jackpot-panel {
  display: block;
  width: auto;
  margin: 4px var(--space-md) 0;
  border: 2px solid var(--gold);
  border-radius: var(--radius-lg);
  padding: var(--space-sm) var(--space-md) var(--space-md);
  text-align: center;
  box-shadow: var(--shadow-gold), inset 0 0 22px rgba(0,0,0,0.5);
}
.jackpot-panel-label {
  font-family: var(--font-display);
  font-size: 0.72rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--gold-light); margin-bottom: 6px;
}
/* SOLID inner plate for the live amount (panel rule) */
.jackpot-amount-plate {
  background: var(--bg-deep);
  border: 1px solid var(--border-gold);
  border-radius: var(--radius-md);
  padding: 6px var(--space-sm);
}
.jackpot-panel #jackpot-value {
  font-family: var(--font-mono);
  font-size: 2.2rem; font-weight: 900;
  color: var(--gold-bright);
  text-shadow: 0 0 12px var(--gold-glow);
  letter-spacing: 0.03em; display: block;
}
.jackpot-sub-pill {
  display: inline-block; margin-top: 8px;
  font-family: var(--font-display); font-size: 0.66rem; letter-spacing: 0.06em;
  color: var(--bg-deep); background: var(--grad-teal);
  border-radius: var(--radius-full); padding: 3px 12px;
  box-shadow: var(--shadow-teal);
}

/* ---- Section header ---- */
.tier-section-header {
  display: flex; align-items: center; justify-content: center; gap: var(--space-sm);
  margin: var(--space-lg) var(--space-md) var(--space-lg);   /* even rhythm: 24px above + below CHOOSE YOUR TIER */
  font-family: var(--font-display); font-size: 0.8rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--gold-light);
}
.tier-section-header::before, .tier-section-header::after {
  content: ''; flex: 1; height: 1px;
  background: linear-gradient(90deg, transparent, var(--border-gold), transparent);
}

/* ---- Tier cards (restyled: chip + art + info + CTA) ---- */
/* width:auto (not the base 100%) so the side margins don't push total width
   past the column and right-shift the cards. Centred, cards aligned identically. */
.tier-cards-grid { width: auto; margin: 0 var(--space-md); align-items: center; gap: var(--space-lg); }
.tier-card { padding: var(--space-md); gap: var(--space-sm); }
/* tier3 is playable (override the dimmed .tier-high) */
#tier3-card { opacity: 1; cursor: pointer; box-shadow: var(--shadow-crimson); }
#tier3-card:active { transform: scale(0.97); box-shadow: var(--shadow-crimson), 0 0 0 3px rgba(220,28,46,0.3); }

/* Circular price chip (dynamic text on a solid chip) */
.tier-chip {
  flex-shrink: 0;
  width: 56px; height: 56px;
  border-radius: var(--radius-full);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 1.15rem; font-weight: 900;
  color: var(--bg-deep); background: var(--grad-teal);
  box-shadow: 0 2px 12px rgba(0,196,167,0.45);
  border: 2px solid rgba(255,255,255,0.25);
}
.tier-card.tier-mid  .tier-chip { background: var(--grad-gold);    box-shadow: 0 2px 12px rgba(245,166,35,0.45); }
.tier-card.tier-high .tier-chip { background: var(--grad-crimson); box-shadow: 0 2px 12px rgba(220,28,46,0.45); color: var(--text-white); }

/* slot: tier_art_* — 76 × 76 (1:1) */
.tier-art {
  flex-shrink: 0;
  width: 76px; aspect-ratio: 1 / 1;
  border-radius: var(--radius-md);
  border: 1px solid rgba(255,255,255,0.12);
}
.tier-art .slot-emoji { font-size: 2.1rem; }

.tier-cta {
  display: inline-block; margin-top: 6px; align-self: flex-start;
  font-family: var(--font-display); font-size: 0.66rem; letter-spacing: 0.05em;
  color: var(--bg-deep); background: var(--grad-gold);
  border-radius: var(--radius-full); padding: 3px 12px;
  box-shadow: 0 0 10px var(--gold-glow);
}
.tier-cta--alt { background: var(--grad-teal); box-shadow: var(--shadow-teal); }

/* ---- Countdown panel (slot: panel_frame_countdown) + mascot ---- */
.countdown-row {
  position: relative;
  /* margin-top:auto pushes the countdown toward the bottom (above the nav),
     closing the void; the slack appears as desert scenery above it. */
  margin: auto var(--space-md) 0;
}
/* Container: ~408 × ~100 at the 440 column (≈4:1, 9-slice frame) */
.countdown-panel {
  display: block;
  border: 2px solid var(--teal);
  border-radius: var(--radius-lg);
  padding: var(--space-sm) var(--space-md);
  box-shadow: var(--shadow-teal), inset 0 0 18px rgba(0,0,0,0.5);
}
.countdown-title {
  font-family: var(--font-display); font-size: 0.7rem; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--teal-light); text-align: center; margin-bottom: 8px;
}
.countdown-segments { display: flex; gap: var(--space-sm); justify-content: center; }
/* Each segment is a SOLID digit box (panel rule) */
.cd-seg {
  flex: 1;
  background: var(--bg-deep);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-sm);
  padding: 6px 2px;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
}
.cd-num  { font-family: var(--font-mono); font-size: 2rem; font-weight: 900; color: var(--gold-bright); line-height: 1; }
.cd-unit { font-family: var(--font-body); font-size: 0.52rem; letter-spacing: 0.08em; color: var(--text-secondary); }

/* slot: mascot_prop — 64 × 84, overlapping the countdown's right edge */
.mascot-slot {
  position: absolute;
  right: -4px; bottom: -6px;
  width: 64px; height: 84px;
  pointer-events: none;
}
.mascot-slot .slot-emoji { font-size: 2.6rem; filter: drop-shadow(0 4px 8px rgba(0,0,0,0.6)); }

/* ---- Bottom-nav icons from slots (slot: nav_icon_*) — 24 × 24 ---- */
.nav-icon {
  width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto 2px;
}
.nav-icon .slot-emoji { font-size: 1.15rem; }

/* =============================================================
   HOME — L1b ART LAYER (real assets dropped into the L1a slots).
   Overrides the L1a placeholders where the art changes the box
   model (composite tier cards, framed panels, the banner nav).
   The PANEL RULE still holds: live values keep their solid plates.
   ============================================================= */

/* ---- Tier cards: each is a single 815×185 composite (slot fills the bg) ----
   The chip / art / title / tagline / CTA are baked into the image, so the card
   is just the picture; the whole element stays the tap target. */
.tier-card--composite {
  display: block;
  padding: 0;
  border: none;
  background: transparent;            /* the slot's inline bg-image paints the art */
  border-radius: var(--radius-lg);
  overflow: visible;                  /* glow box-shadow draws outside */
  width: 100%;
  max-width: 100%;                    /* never exceed the column */
  margin-inline: auto;               /* centre in the column; all three identical */
  aspect-ratio: 815 / 185;            /* scale the composite to the column width */
  gap: 0;
}
.tier-card--composite::before { display: none; }   /* no CSS shimmer over the art */
.tier-card--composite:active  { transform: scale(0.985); }

/* L1b: image-slot CONTAINERS carry NO backing — only the transparent PNG art
   shows. No bg tint, no box-shadow halo, no opacity layer. (The art itself
   carries its shape + glow.) The ID+class selectors below also kill the
   glow-pulse ANIMATION on the tier composites, which would re-add a box-shadow
   rectangle behind the card. */
.art-slot.slot--image { background-color: transparent; box-shadow: none; opacity: 1; }
#tier1-card.tier-card--composite,
#tier2-card.tier-card--composite,
#tier3-card.tier-card--composite { box-shadow: none; animation: none; }

/* Tier 3 LIVE overlay — the only card with live data, positioned in the open
   red zone beneath the baked "Jackpot Strike" title ("{tickets} tickets · N/7"
   + 7 dots). pointer-events:none so taps fall through to the card (navigateTo). */
.tier3-live-overlay {
  position: absolute;
  left: 62.8%;               /* status/dots left edge aligned UNDER the baked "Jackpot Strike" title (~54% of the card in fixed.png), clear of the vault. (41% lands over the vault — verified against fixed.png.) */
  right: 6%;
  bottom: 22%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;   /* left-align the column (status + dots share this left edge), NOT centred */
  gap: 4px;
  /* No CSS panel — sits directly on the composite's baked dark-red zone;
     text-shadow on the values carries legibility. */
  pointer-events: none;
}
.tier3-live-status {
  font-family: var(--font-body);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-align: center;
  color: var(--gold-bright);
  text-shadow: 0 1px 3px rgba(0,0,0,0.85);
}
/* The frag dots inherit .frag-progress/.frag-dot; nudge spacing for the overlay */
.tier3-live-overlay .frag-progress { gap: 5px; margin: 0; justify-content: flex-start; }

/* ---- Framed panels: the frame art bakes its own border, title, AND sub-pill,
   so hide those HTML placeholders and drop the L1a border/inset (no double
   frame). The panel becomes a fixed-ratio box; only the SOLID live plate /
   countdown digits render on top, centred in the frame's recessed window
   (PANEL RULE preserved). The baked title/pill keep their a11y via aria-label
   on the panel. ---- */
.jackpot-panel.slot--image {
  position: relative;
  aspect-ratio: 860 / 280;
  padding: 0;
  margin: 4px var(--space-md) 0;
  border: none;
  box-shadow: none;          /* art's own frame carries the glow — no CSS backing */
  display: flex;
  align-items: center;
  justify-content: center;
}
.jackpot-panel.slot--image .jackpot-panel-label,
.jackpot-panel.slot--image .jackpot-sub-pill { display: none; }   /* baked into the art */
/* Live €-value sits DIRECTLY on the frame art (dark plate baked in) — no CSS
   panel. Centred in the recessed window; text-shadow carries legibility. */
.jackpot-panel.slot--image .jackpot-amount-plate {
  width: 70%;
  margin-top: 0%;            /* centred in the recessed window, raised slightly for room above the pill */
  background: transparent;
  border: none;
  padding: 0;
}
.jackpot-panel.slot--image #jackpot-value {
  font-size: 2.5rem;        /* ~11% smaller than the base 2.2rem — more side margin in the red plate */
  text-shadow: 0 0 12px var(--gold-glow), 0 2px 5px rgba(0, 0, 0, 0.9);
}

.countdown-panel.slot--image {
  position: relative;
  aspect-ratio: 816 / 190;
  padding: 0;
  border: none;
  box-shadow: none;          /* art's own frame carries the glow — no CSS backing */
  display: flex;
  align-items: center;       /* vertical middle of the panel */
  justify-content: center;   /* horizontally centred digit row */
}
.countdown-panel.slot--image .countdown-title { display: none; }   /* baked into the art */
.countdown-panel.slot--image .countdown-segments {
  width: 64%;                /* spread + centred; stays clear of the mascot lamp at the right */
  gap: 2px;                  /* tight — the colons provide the visual separation */
  position: relative;
  top: 8%;                   /* nudge down into the vertical middle of the open area (below the baked title) */
}
/* Digits sit DIRECTLY on the frame art (dark window baked in) — drop the solid
   .cd-seg boxes; text-shadow keeps them legible. */
.countdown-panel.slot--image .cd-seg {
  background: transparent;
  border: none;
  box-shadow: none;
  padding: 2px;
}
.countdown-panel.slot--image .cd-num  { text-shadow: 0 0 8px var(--teal-glow), 0 2px 4px rgba(0, 0, 0, 0.9); }
.countdown-panel.slot--image .cd-unit { text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9); }

/* Colon separators between the number groups (DAYS:HOURS:MINS:SECS), styled to
   match the yellow digits + glow. align-self:flex-start aligns the ":" with the
   digit row (top of each seg), not the labels below. */
.cd-colon {
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: 2rem;          /* match .cd-num */
  font-weight: 900;
  line-height: 1;
  color: var(--gold-bright);
  margin-top: 2px;            /* nudge onto the digit baseline (seg has 2px pad) */
}
.countdown-panel.slot--image .cd-colon {
  text-shadow: 0 0 8px var(--teal-glow), 0 2px 4px rgba(0, 0, 0, 0.9);
}

/* Bottom clearance = fixed nav + rising-pentagon headroom (#screen-home pad:0).
   No safe-area inset — the nav banner covers its own inset zone (v127). */
.home-content { padding-bottom: calc(var(--nav-height) + 22px); }

/* ---- Bottom nav: banner frame + in-place highlight icons ----
   The banner (teal frame + dividers) is the bar background; each tab's icon
   (220×155, label baked in) sits ENTIRELY WITHIN the bar. The active (lit) and
   inactive (unlit) states occupy the IDENTICAL box, so navigating only changes
   which tab is lit — with zero resize or vertical shift of any icon. */
#bottom-nav {
  background-color: transparent;      /* banner art paints the bar */
  background-image: none;             /* set inline by applyNavBanner() */
  background-repeat: no-repeat;
  /* Fill the WHOLE bar (nav-height + safe-area inset) down to the bottom edge, so
     no desert BG shows through below on iPhone (no dark strip). The ~34px vertical
     "stretch" of a wide horizontal banner is visually negligible. The home
     countdown push-up is instead fixed by dropping the inset from .home-content's
     padding-bottom (the bar itself now covers the inset zone). [v127 — reverts the
     v126 top-pin, which had left the inset unpainted.] */
  background-position: center bottom;
  background-size: 100% 100%;
  border-top: none;                   /* frame is baked into the banner */
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  overflow: hidden;                   /* icons live inside the bar — no rise to keep visible */
}
.nav-btn { overflow: hidden; gap: 0; }
/* The icon image — a FIXED, image-independent box pinned inside the bar.
   The box geometry comes only from `height` + `aspect-ratio` (never the image),
   and `background-size: 100% 100%` makes every image — active or inactive, of
   ANY pixel dimensions or opaque-content position — fill that exact box. So the
   lit and unlit states share one footprint: no per-image contain/letterbox, no
   bottom-anchored rise. (Was: height nav+20px + bottom:-1 + center-bottom +
   contain, which let a full-canvas active PNG paint up into a 19px headroom
   above the bar while a low-glyph inactive PNG sat low → the active "rose".) */
.nav-btn .nav-icon {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  margin: 0 auto;
  width: auto;
  height: var(--nav-height);
  aspect-ratio: 220 / 155;
  max-width: 100%;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
  pointer-events: none;               /* taps go to the .nav-btn */
  filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
}
.nav-btn.active::after { display: none; } /* the travelling pentagon IS the indicator */

/* =============================================================
   SHARED NINE-SLICE POPUP CHROME  (js/ui-popup.js)
   App-wide, not tied to any one popup. These rules apply ONLY
   once the component has proven the CMS art usable and added the
   class — until then every popup keeps the styling it has today.
   Nothing here references a specific popup or game.
   ============================================================= */

/* --- The frame -------------------------------------------------
   border-width / border-image are set inline by _paint(), derived
   from the CMS slice values by ONE shared factor so the corners keep
   the source's proportions. Override the depth per popup with
   --uipop-scale on the element. The art replaces the fallback's own
   chrome, so the two never stack. */
.uipop-frame {
  background-color: transparent;
  background-image: none;
  box-shadow: none;
  border-radius: 0;                  /* the corner shape is in the art */
  /* The centre slice is drawn with `fill`, so the frame expands to any
     content height and width with no min/max of its own. */
  box-sizing: border-box;
}

/* --- Buttons ---------------------------------------------------
   ONE image per style; rest / pressed / disabled are all derived
   from it. The nine-slice lets a label be any length. */
.uipop-btn {
  /* A button's chrome is a smaller share of a smaller box than a frame's is,
     so the shared scale factor is lower here. Still ONE factor for all four
     sides — the corners keep the source's proportions either way. */
  --uipop-scale: 0.22;
  background-color: transparent;
  background-image: none;
  box-shadow: none;
  border-radius: 0;
  box-sizing: border-box;
  /* The press shift moves the LABEL, never the box: the two paddings
     always sum to 2 x --uipop-pad-y, so the button's rect is identical
     in every state and nothing around it reflows. */
  --uipop-pad-y: 5px;
  /* v186: the art no longer insets the label (buttons have no layout border —
     see UIPopup._paint), so the whole horizontal inset is this padding and
     does not change with the art. 17px = the 7px art border + 10px padding
     the two-up row (Play Now / Save Ticket) was tuned on. */
  --uipop-pad-x: 17px;
  --uipop-press: 2px;
  padding-top: var(--uipop-pad-y);
  padding-bottom: var(--uipop-pad-y);
  padding-left: var(--uipop-pad-x);
  padding-right: var(--uipop-pad-x);
  font-size: 0.85rem;
  /* Smaller footprint, but never below the 44px tap target. v186: the art is
     drawn inside this box and adds nothing to it, so the height is padding +
     label (at least 44px), whatever the slice. */
  min-height: 44px;
  min-width: 0;
  /* Label: white, uppercase (v157). text-transform, never a rewritten string. */
  color: #fff;
  text-transform: uppercase;
  /* Uppercase is wider, and .btn-primary's 0.06em tracking pushed the two-up
     free-ticket row (PLAY NOW / SAVE TICKET) onto two lines at 69px. Caps need
     less tracking than mixed case anyway; 0.02em brings both back to one line
     at 49px without touching the padding or the type size. */
  letter-spacing: 0.02em;
  transition: filter 0.12s ease;
  /* Positioning must NOT depend on transform. v151: a stray
     `transform: none !important` on :disabled elsewhere in this file
     killed a translate that was doing layout and threw the button
     half its own width across the screen. Nothing here places the
     button with transform, so no rule can move it by removing one. */
}

/* Pressed — slight darken + a small inward offset of the label. */
.uipop-btn:active {
  filter: brightness(0.88);
  padding-top: calc(var(--uipop-pad-y) + var(--uipop-press));
  padding-bottom: calc(var(--uipop-pad-y) - var(--uipop-press));
  transform: none;                   /* cancels .btn-primary:active scale(0.97) */
}

/* Disabled — reduced opacity AND desaturation.
   ⚠️ THIS IS DELIBERATE, NOT A PLACEHOLDER. It is how a disabled
   state is derived from the single button image, and it is meant to
   stay. Do not confuse it with the temporary grayscale on the mini
   game's SPIN button (.mga-action-btn:disabled), which IS a
   placeholder awaiting distinct art. Opacity alone is not enough:
   over a busy background a merely faded button still reads as live,
   because it keeps its full colour. Draining the colour is what says
   "not available". */
.uipop-btn:disabled,
.uipop-btn[disabled] {
  opacity: 0.55;
  filter: saturate(0.15) brightness(0.92);
  cursor: default;
  /* Rest padding, unchanged — a disabled button must not shift either. */
  padding-top: var(--uipop-pad-y);
  padding-bottom: var(--uipop-pad-y);
  box-shadow: none;
}

/* --- Lucky Reel session summary, converted in v154 -------------
   .mga-lr-summary keeps its role as the full-bleed dim over the cabinet;
   this inner panel is what carries the shared nine-slice frame, so the art
   wraps the summary's content instead of the whole machine. It has no chrome
   of its own — when the CMS art is absent .uipop-frame never lands and this
   is an ordinary transparent column, exactly as the summary looked before. */
.mga-lr-summary-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .5em;
  width: 100%;
  max-width: 300px;
  box-sizing: border-box;
}
.mga-lr-summary-panel.uipop-frame { padding: 4px 10px; }

/* THE LIFT (v155). With the dim gone the popup sits on a bright, busy, gold
   cabinet, so it has to separate itself. filter: drop-shadow() — NOT box-shadow
   — because the nine-slice frame has rounded, partly transparent corners and a
   box-shadow would draw a rectangle behind them. Same reasoning as the cabinet's
   own shadow in v152. Two stacked shadows: a tight core that defines the edge
   and a wider falloff that reads as depth.

   ⚠️ Judged against the REAL gold cabinet: a dark shadow is the weaker of the
   two there — the art is bright and busy, and a soft black blur mostly reads as
   more of the cabinet's own shading. A LIGHT halo separates far better, because
   nothing in the art is that pale. The light treatment ships; switch by setting
   --lr-sum-lift-rgb to 0 0 0 (and raising --lr-sum-lift) on .mga-lr-summary. */
.mga-lr-summary-panel,
.pp-popup-panel,
.mga-pending-panel,
.settings-section,
.pk-result-panel {
  /* ── TUNABLES ── */
  --lr-sum-lift:      0.55;      /* ← strength (alpha of both shadows) */
  --lr-sum-lift-rgb:  255 246 214; /* ← shadow colour: pale gold halo.
                                        0 0 0 gives a conventional dark shadow */
  --lr-sum-lift-blur: 18px;      /* ← size of the outer falloff */
  --lr-sum-lift-y:    4px;       /* ← vertical offset of the core */

  filter:
    drop-shadow(0 var(--lr-sum-lift-y) calc(var(--lr-sum-lift-blur) / 3)
                rgba(var(--lr-sum-lift-rgb) / var(--lr-sum-lift)))
    drop-shadow(0 0 var(--lr-sum-lift-blur)
                rgba(var(--lr-sum-lift-rgb) / calc(var(--lr-sum-lift) * 0.7)));
}

/* =============================================================
   CONVERTED-POPUP TYPOGRAPHY  (v157)
   Scoped to the popups actually converted to the shared component,
   so nothing else in the app shifts. Uppercasing is done with
   text-transform — the underlying strings stay sentence case, so
   they remain reusable and translatable.
   ============================================================= */

/* HEADLINE — one colour for every outcome. A loss used to render in
   --text-secondary and a free ticket in teal, so the headline was doing
   tone work; the frame and the wording carry that now, not the colour. */
#game-result .result-label,
#game-result .result-label.win-label,
#game-result .result-label.lose-label,
#game-result .result-label.free-label,
#pp-popup .result-label,
#js-popup .result-label,
#mg-pending-popup .result-label,
#jackpot-modal .result-label,
#settings-modal .settings-title,
#settings-modal .settings-section-title,
#settings-modal .settings-rules-title {
  color: var(--gold-bright);
  text-shadow: 0 0 12px var(--gold-glow), 0 0 30px rgba(245, 166, 35, 0.5);
}

/* BODY COPY — white, uppercase. The prize amount (#result-prize) and the
   Lucky Reel outcome line are NOT body: they are the headline of their popup
   and stay gold in their own casing. */
#game-result .result-sub,
#pp-popup .result-sub,
#js-popup .result-sub,
#mg-pending-popup .result-sub,
#jackpot-modal .result-sub,
.mga-lr-summary-panel .mga-lr-summary-note {
  color: #fff;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* --- Power Play result / mini-game popup (v159) ------------------
   The outer element is a full-screen tap shield: it covers the board so
   nothing behind it is reachable, and it does NOT dim — the popup floats
   on its own halo, exactly as the Lucky Reel summary does. */
.pp-popup {
  position: fixed;
  inset: 0;
  z-index: 1200;                     /* over the game chrome and the nav, under the modal (4000) */
  display: flex;                     /* toggled from none by the game */
  align-items: center;
  justify-content: center;
  padding: var(--space-lg);
  /* v161 — DIM. These popups cover a whole game screen, so without it the game
     stays bright and fully readable beside the panel and the popup does not
     read as modal at all. (The Lucky Reel summary is the deliberate exception:
     it sits INSIDE the cabinet and the machine is meant to show — see v155.) */
  --pp-popup-scrim: 0.62;            /* ← how far the game behind is dimmed */
  background: rgba(0, 0, 0, var(--pp-popup-scrim));
  /* Sized by inset alone. Never give this a width — a width plus left:0/right:0
     is over-constrained, and the box then anchors to one edge instead of
     filling. That is exactly the v160 fault. */
  width: auto;
  max-width: none;
}
.pp-popup-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  width: 100%;
  max-width: 320px;
  box-sizing: border-box;
  text-align: center;
}
.pp-popup-panel.uipop-frame { padding: 6px 12px; }
/* No art yet → the panel would be an unreadable transparent column over the
   board, so the fallback gets the same card treatment the popups had before
   the nine-slice chrome existed. Dropped the moment .uipop-frame lands. */
.pp-popup-panel:not(.uipop-frame),
.settings-section:not(.uipop-frame) {
  background: rgba(12, 6, 20, 0.94);
  border: 1px solid var(--border-teal);
  border-radius: var(--radius-xl);
  padding: var(--space-lg);
}

/* --- v197: "Bonus game waiting" pending-award popup ----------------
   Same shield, frame, sizing, typography and buttons as the Power Play and
   Jackpot Strike popups above — this file only adds the picture, the close
   control's placement and the two button widths. */
.mga-pending-panel { position: relative; }
.mga-pending-art,
.popup-art {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 92px;
}
.mga-pending-art-img,
.popup-art-img {
  display: block;
  max-width: 74%;
  max-height: 120px;
  object-fit: contain;      /* the game's own art, never cropped or stretched */
}
/* TALL art (a cabinet): show its top band — the marquee that names the game —
   instead of a 57px sliver of the whole machine. Set by measurement in JS. */
.mga-pending-art--tall .mga-pending-art-img,
.popup-art--tall .popup-art-img {
  width: 100%;
  max-width: 240px;
  height: var(--mga-pending-band, 100px);   /* per-game, measured (see MINI_GAME_DISPLAY) */
  max-height: none;
  object-fit: cover;
  object-position: center top;
  border-radius: 10px;
}
/* NO ART → the game's emoji, as bare text. No box, no background, no border:
   the black square in the v193 sheet was exactly that. */
.mga-pending-art--glyph,
.popup-art--glyph {
  font-size: 2.8rem;
  line-height: 1;
  background: none;
  border: 0;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.5));
}
.mga-pending-left {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--gold);
  letter-spacing: 0.04em;
}
.mga-pending-left[hidden] { display: none; }
.mga-pending-play, .mga-pending-later,
.popup-action { min-width: 180px; flex: 0 0 auto; }
/* The popup's own close control, pinned to the frame's top-right corner. */
.mga-pending-close,
.jt-close {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 34px; height: 34px;
  z-index: 2;
}

/* --- Jackpot Strike tab frames: inactive must not bleed (v161) ----
   The two frames are stacked at the SAME origin and the inactive one paints its
   WHOLE art at 0.6 opacity behind the active one. Measured from the art: the My
   Rows top cap (jackpot.frame_top, 880x110) is only 56.8% opaque — its LEFT HALF
   is fully transparent (alpha 0) so the Pick tab can show there — and the Pick
   frame's art (880x880) is opaque right through its interior. So wherever the
   active frame is transparent, the inactive frame's BODY shows through, not just
   its folder tab. .js-frame-stack's overflow:hidden clips the height and does
   nothing about this.

   Fix: clip each inactive frame to its own tab band, so only the folder tab
   paints and the body never does. The two bands differ because the two arts are
   scaled differently — My Rows is a 3-slice whose cap keeps its 880:110 aspect
   (12.5% of the width), while the Pick frame stretches one 880x880 image into a
   much shorter box, so its 110px tab lands at 110/880 of the RENDERED height.
   Both are tunables. The notch LABELS are separate elements at z-index 5 and are
   untouched, so both tabs stay visible and clickable in either state. */
.js-frame:not(.js-frame--active) {
  --js-pick-tab-band: 12.5%;   /* ← Pick frame's tab, as a share of its height */
  --js-rows-tab-band: 55px;    /* ← My Rows cap height (440px width x 110/880) */
}
.js-pick-frame:not(.js-frame--active) {
  clip-path: inset(0 0 calc(100% - var(--js-pick-tab-band)) 0);
}
.js-myrows-frame:not(.js-frame--active) {
  clip-path: inset(0 0 calc(100% - var(--js-rows-tab-band)) 0);
}

/* =============================================================
   POKER KING (v162) — 5-card draw mini game.
   PLACEHOLDER CHROME. Every piece here is a CSS stand-in with the
   CMS seam already wired in _pkApplyArt: .pk-cabinet takes a
   background-image, .pk-rows takes the felt, .pk-paytable takes its
   frame, and each card takes its own face. When art lands the
   placeholders are simply covered — no restructuring, the way the
   Lucky Reel went from v145 to v146.
   ============================================================= */

/* The machine fills the bare modal and is INSET so the game behind shows,
   exactly like the Lucky Reel cabinet (v152). Sizes as ONE unit against the
   available box, aspect preserved, so nothing ever scrolls. */
.mga-poker-king {
  height: 100%; width: 100%;
  min-height: 0;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  container-type: size;
}
.pk-machine {
  /* ── TUNABLES ── */
  --pk-fit:        0.756;  /* ← cabinet size as a fraction of the fitted box.
                                 v165: 0.756 renders the cabinet at 90% of the
                                 size it had in v164 — see the note on
                                 --pk-aspect in the art block for why this is
                                 not simply 0.90. */
  --pk-aspect:     0.62;   /* ← width / height of the PLACEHOLDER cabinet.
                                 The art overrides this with the real ratio. */
  --pk-card-gap:   1.6cqw; /* ← gap between cards */
  --pk-card-aspect: 153 / 188; /* ← card proportion, from the card_back art */
  --pk-enter-ms:   380ms;  /* ← entrance duration */
  /* ── REVEAL TIMING (v167) — used by BOTH the opening deal and the draw's
     turn-over, so the two feel like one game. JS reads these back so the
     stagger and the animation can never drift apart. ── */
  --pk-flip-ms:      460ms; /* ← ONE full turn, back-half + face-half + settle.
                                  v169: 420 -> 460 to fit a settle without
                                  hurrying the turn itself. */
  --pk-flip-stagger: 260ms; /* ← gap between consecutive cards */
  --pk-result-hold: 1200ms; /* ← pause on the final hand before COLLECT lights */
  --pk-save-ms:      380ms; /* ← a saved card's lift-and-set-down (v169: 320 -> 380;
                                  a lift needs time to read as one) */

  /* ── FEEL (v169) — every curve and amplitude of the turn and the lift ──
     TWO curves per turn: the back ACCELERATES into the edge-on moment, the face
     DECELERATES out of it. One curve for both halves is what read mechanical. */
  --pk-flip-ease-in:   cubic-bezier(0.55, 0.00, 0.90, 0.45); /* ← back half: gathers speed */
  --pk-flip-ease-out:  cubic-bezier(0.10, 0.55, 0.35, 1.00); /* ← face half: eases to rest */
  --pk-flip-edge:      46%;   /* ← where in the turn the card is edge-on */
  --pk-flip-mid-scale: 1.06;  /* ← lifts toward the player as it turns (mass) */
  --pk-flip-lift:      -5%;   /* ← …and rises slightly, % of card height */
  --pk-flip-settle:    1.022; /* ← UNIFORM overshoot as it lands, then rests */
  --pk-flip-shade:     0.30;  /* ← light falls off at edge-on — the 2D cue that
                                    it is turning, not squashing */

  --pk-save-ease:       cubic-bezier(0.45, 0.05, 0.20, 1.00); /* ← path: picks up, sets down */
  --pk-save-lift-scale: 1.07;  /* ← how far off the table it lifts */
  --pk-save-settle:     0.985; /* ← a touch of give as it is put down */
  --pk-shadow-rest:     0.22;  /* ← contact shadow when on the table */
  --pk-shadow-lift:     0.55;  /* ← …and at the top of the lift */

  /* ── WIN CELEBRATION (v170) — scaled by hand tier ──────────────────────
     Every duration sits INSIDE --pk-result-hold (1200ms) so the effect is over
     before COLLECT lights. The low tier fires on roughly every other hand, so
     it is the one kept smallest. */
  --pk-cel-pad:          12px;/* ← how far the glow box sits OUTSIDE the row.
                                    The cards cover the row exactly, so with a
                                    pad of 0 the glow is hidden behind them and
                                    only its outer shadow shows — this is what
                                    makes the halo read as a rim of light. */
  --pk-cel-ms-low:     650ms; /* ← one pair / two pair: brief */
  --pk-cel-ms-mid:     950ms; /* ← trips / straight / flush / full house */
  --pk-cel-ms-high:   1150ms; /* ← quads / straight flush */
  --pk-cel-peak-low:    0.45; /* ← glow opacity at its peak, low tier */
  --pk-cel-peak-mid:    0.55; /* ← …mid */
  --pk-cel-peak-high:   0.80; /* ← …high */
  --pk-cel-spread-low:   22px;/* ← how far the glow reaches past the row */
  --pk-cel-spread-mid:   30px;
  --pk-cel-spread-high:  46px;
  --pk-cel-scale-low:   1.010;/* ← breath at the peak — never a bounce */
  --pk-cel-scale-mid:   1.022;
  --pk-cel-scale-high:  1.035;
  --pk-cel-pulses-mid:      2;/* ← documentation: mid breathes twice, high three times */
  --pk-cel-colour-low:   255 205 110;  /* ← warm gold */
  --pk-cel-colour-mid:   255 216 128;
  --pk-cel-colour-high:  255 238 176;  /* ← nearly white-hot */
  --pk-cel-sparks:          8;/* ← HIGH tier only; 0 turns them off */
  --pk-cel-spark-ms:    900ms;/* ← spark travel */

  /* ── WINNING-CARD MARK (v171) — the cards the SERVER says made the hand ──
     Quiet on purpose: it explains the hand, it does not celebrate it. The tier
     glow (above) does the celebrating, and these two must not compete. */
  --pk-mark-ms:        280ms; /* ← one mark's fade-in */
  --pk-mark-stagger:   110ms; /* ← gap between marks, left to right. This is what
                                   makes five marks on a flush read as deliberate
                                   rather than as the row lighting up at once. */
  --pk-mark-ring:        3px; /* ← thickness of the inner rule */
  --pk-mark-glow:       14px; /* ← soft inner glow inside that rule */
  --pk-mark-peak:       1.00; /* ← the mark's settled opacity */
  --pk-mark-colour: 232 150 32; /* ← amber. Cream was tried first and vanished
                                     against the near-white card face — the mark
                                     has to READ, that is its whole job. Amber is
                                     also still distinct from the "saved" ring:
                                     that one is bright gold and OUTSIDE the
                                     card, this one is amber and INSIDE it, so a
                                     card that is both still reads as both. */
  --pk-enter-from: 0.88;   /* ← scale it arrives from */

  position: relative;
  height: min(calc(100cqh * var(--pk-fit)), calc(100cqw * var(--pk-fit) / var(--pk-aspect)));
  aspect-ratio: var(--pk-aspect);
  max-width: 100cqw;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.6cqh;
  padding: 3cqh 3.5cqw;
  box-sizing: border-box;
  /* Placeholder cabinet look — replaced by .pk-cabinet's art when uploaded. */
  background: radial-gradient(120% 90% at 50% 0%, #1d5e46 0%, #0d3a2b 55%, #07231a 100%);
  border: 2px solid var(--gold);
  border-radius: 18px;
  box-shadow: 0 0 0 4px rgba(0,0,0,0.35), 0 14px 40px rgba(0,0,0,0.55);
  /* forwards, NEVER both: `both` applies frame 0 before the animation runs, so
     a never-ticking animation would leave the cabinet invisible and undersized
     (the v152 trap). */
  animation: pkMachineIn var(--pk-enter-ms) cubic-bezier(0.34, 1.4, 0.64, 1) forwards;
}
/* The art layer. Empty until a cabinet is uploaded; then it covers the
   placeholder and the placeholder's own border/background are dropped. */
.pk-cabinet {
  position: absolute; inset: 0;
  background-position: center;
  background-repeat: no-repeat;
  background-size: 100% 100%;
  border-radius: 18px;
  pointer-events: none;
}
.pk-machine--art { background: none; border: none; box-shadow: none; }

@keyframes pkMachineIn {
  0%   { opacity: 0; transform: scale(var(--pk-enter-from)); }
  100% { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .pk-machine { animation: none; opacity: 1; transform: none; }
  .pk-card { transition: none !important; }
}

.pk-title {
  font-family: var(--font-display);
  font-size: clamp(0.85rem, 5cqw, 1.6rem);
  letter-spacing: 0.14em;
  color: var(--gold-bright);
  text-shadow: 0 0 12px var(--gold-glow);
  position: relative; z-index: 1;
}

/* The felt. Two rows: HELD on top, the hand below. */
.pk-rows {
  position: relative; z-index: 1;
  width: 100%;
  display: flex; flex-direction: column;
  gap: 2cqh;
  padding: 2cqh 1.5cqw;
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(0,0,0,0.28), rgba(0,0,0,0.12));
  background-size: 100% 100%;
  box-shadow: inset 0 0 22px rgba(0,0,0,0.5);
}
.pk-row {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--pk-card-gap);
  align-items: stretch;
}

/* ── THE CARD ──────────────────────────────────────────────────
   Drawn from rank + suit, never an image. Sizes purely from its grid cell and
   an aspect ratio, so it stays crisp at any scale (glyphs are text, not
   bitmaps). .pk-card--art hides the CSS face so an uploaded image is the whole
   card — that is the entire swap. */
.pk-card {
  position: relative;
  /* ⚠️ MATCHES THE CARD BACK. gameIcons.pokerking.card_back is 153 x 188, so
     the CSS-drawn FACES use the same ratio — at the old 5/7 a card changed
     shape as it flipped from back to face. Both sides now measure identically. */
  aspect-ratio: var(--pk-card-aspect);
  width: 100%;
  border-radius: 8%;
  border: 1px solid rgba(0,0,0,0.35);
  background: linear-gradient(180deg, #fdfaf2 0%, #efe7d4 100%);
  background-size: 100% 100%;
  box-shadow: 0 2px 6px rgba(0,0,0,0.45);
  padding: 0;
  cursor: pointer;
  overflow: hidden;
  /* Only opacity/filter transition — nothing that changes the box. */
  transition: filter 0.15s ease, opacity 0.15s ease;
}
.pk-card--art .pk-card-face { display: none; }
/* ⚠️ .pk-card--back sets `background` as a SHORTHAND, which resets
   background-size to auto — so an inline background-image from the CMS was
   being drawn at its natural 153x188 and cropped by the card box. Art mode
   restates the sizing (and drops the placeholder's lattice border) so the
   uploaded card back fills the card exactly. */
.pk-card--art {
  background-size: 100% 100% !important;
  background-repeat: no-repeat;
  background-position: center;
  border: none;
}
.pk-card--art::after { display: none; }
.pk-card-face { position: absolute; inset: 0; display: block; }
.pk-card--red   .pk-card-face { color: #c8102e; }
.pk-card--black .pk-card-face { color: #16181d; }

.pk-card-index {
  position: absolute; top: 4%; left: 7%;
  display: flex; flex-direction: column; align-items: center; line-height: 1;
  font-family: var(--font-display);
  font-size: clamp(0.5rem, 3.1cqw, 1rem);
}
.pk-card-suit { font-size: 0.85em; }
.pk-card-pip {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: clamp(1rem, 7.6cqw, 2.6rem);
  line-height: 1;
}

/* Face-down — a distinct look, since this is the opening state. */
.pk-card--back {
  background:
    repeating-linear-gradient(45deg,  rgba(255,255,255,0.10) 0 3px, transparent 3px 6px),
    repeating-linear-gradient(-45deg, rgba(255,255,255,0.10) 0 3px, transparent 3px 6px),
    linear-gradient(180deg, #8d1226 0%, #5c0a17 100%);
  border-color: rgba(255, 215, 0, 0.55);
  cursor: default;
}
.pk-card--back::after {
  content: '';
  position: absolute; inset: 8%;
  border: 1px solid rgba(255, 215, 0, 0.45);
  border-radius: 6%;
}

/* Flip-up reveal: opacity + a scaleX that never changes the LAYOUT box. */
.pk-card:not(.pk-card--up):not(.pk-card--back) { opacity: 0; }
/* --up is the RESTING state (visible). The flip itself is a separate one-shot
   class added only by _pkFlipUp, so re-rendering the rows on every hold tap
   does not replay the reveal on all five cards. */
.pk-card--up   { opacity: 1; }
/* ── THE TURN (v169) ─────────────────────────────────────────────────────
   WHAT v168 DID, and why it read cheap:
     keyframes pkFlipUp  scaleX(0.05) -> scaleX(1), 420ms, ONE ease-out curve.
     · It was HALF a flip. The back div was swapped for the face INSTANTLY, and
       only the face's widening was animated — so the back vanished and the face
       stretched out of a sliver. Switched on, not turned over.
     · ease-out from a 5% sliver is at MAX speed on frame one: it popped, with
       no gathering phase.
     · It stopped DEAD at scaleX(1). No settle.
     · Nothing about it moved except width — no lift, no light, no mass.
     · And (the one not on anybody's list) every re-render cut the previous
       card's turn off 72% of the way through — see _pkResumeFlip.

   NOW, still 2D, transform + opacity only:
     0% -> edge   the BACK narrows, accelerating; the card rises and grows a
                  touch; light falls off across it
     edge         swap — the back layer switches off at the exact edge-on moment
     edge -> 84%  the FACE widens, decelerating, light returns
     84% -> 100%  a uniform settle: slightly over, then rest
   The overshoot is UNIFORM (scale, not scaleX), so the only shape change is the
   turn's own horizontal compression — never a 153:188 card that stretches.
   fill-mode backwards: after the turn no transform lingers on a resting card. */
.pk-card--flip {
  animation: pkTurn var(--pk-flip-ms) backwards;
  animation-delay: var(--pk-flip-delay, 0ms);
}
@keyframes pkTurn {
  0% {
    transform: translateY(0) scaleX(1) scale(1);
    animation-timing-function: var(--pk-flip-ease-in);
  }
  46% {
    transform: translateY(var(--pk-flip-lift)) scaleX(0.02) scale(var(--pk-flip-mid-scale));
    animation-timing-function: var(--pk-flip-ease-out);
  }
  84% {
    transform: translateY(0) scaleX(1) scale(var(--pk-flip-settle));
    animation-timing-function: ease-in-out;
  }
  100% {
    transform: translateY(0) scaleX(1) scale(1);
  }
}
/* The BACK the card is turning away from — a layer over the face for the first
   half, gone at the edge-on moment. Default opacity 0, so a card that is not
   turning never shows it. */
/* Scoped to FACES: a face-down .pk-card--back already uses ::after for the
   placeholder's inset gold rule, and must keep it. */
.pk-card:not(.pk-card--back)::after {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background: var(--pk-card-back-bg,
    repeating-linear-gradient(45deg,  rgba(255,255,255,0.10) 0 3px, transparent 3px 6px),
    repeating-linear-gradient(-45deg, rgba(255,255,255,0.10) 0 3px, transparent 3px 6px),
    linear-gradient(180deg, #8d1226 0%, #5c0a17 100%));
  opacity: 0;
  pointer-events: none;
}
.pk-card--flip::after {
  animation: pkTurnBack var(--pk-flip-ms) linear;
  animation-delay: var(--pk-flip-delay, 0ms);
}
@keyframes pkTurnBack {
  0%, 45.9% { opacity: 1; }
  46%, 100% { opacity: 0; }
}
/* Light falloff across the turn — darkest edge-on. */
.pk-card:not(.pk-card--back)::before {
  content: '';
  position: absolute; inset: 0;
  border-radius: inherit;
  background: #000;
  opacity: 0;
  pointer-events: none;
  z-index: 1;
}
.pk-card--flip::before {
  animation: pkTurnShade var(--pk-flip-ms) ease-in-out;
  animation-delay: var(--pk-flip-delay, 0ms);
}
@keyframes pkTurnShade {
  0%   { opacity: 0; }
  46%  { opacity: var(--pk-flip-shade); }
  84%  { opacity: 0; }
  100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .pk-card--flip,
  .pk-card--flip::after,
  .pk-card--flip::before { animation: none; }
  .pk-card:not(.pk-card--up):not(.pk-card--back) { opacity: 1; }
}

.pk-card--held { box-shadow: 0 0 0 2px var(--gold-bright), 0 2px 10px rgba(0,0,0,0.5); }
.pk-card--spent { filter: grayscale(1) brightness(0.55); cursor: default; }

/* Empty frame — the opening top row, and the gaps as cards move up. */
.pk-slot--empty {
  aspect-ratio: var(--pk-card-aspect);
  width: 100%;
  border-radius: 8%;
  border: 1px dashed rgba(255, 215, 0, 0.35);
  background: rgba(0,0,0,0.18);
}

.pk-hint {
  /* ⚠️ NON-INTERACTIVE, so it must not hit-test. The hint spans the whole felt
     width and its box overlaps the bottom ~3px of the lower card row, so
     without this a tap on a card's bottom edge landed on the paragraph and the
     card was never held. Found by hit-testing in v166; it predates the tap-area
     work and is nothing to do with it. */
  pointer-events: none;
  position: relative; z-index: 1;
  font-size: clamp(0.55rem, 2.6cqw, 0.85rem);
  color: var(--text-secondary);
  text-align: center;
  min-height: 1.2em;
}

/* ── BUTTONS ───────────────────────────────────────────────────
   ⚠️ v151: nothing here may move, resize or reflow on press or when disabled.
   The press is filter-only, disabled is opacity+filter only, and NOTHING
   positions with a transform — so no rule can shift these by removing one.
   Shuffle is hidden with `visibility`, not `display`, so the row keeps its
   geometry in every phase. */
.pk-actions {
  position: relative; z-index: 1;
  display: flex; gap: 2cqw; align-items: center; justify-content: center;
  width: 100%;
}
.pk-btn {
  flex: 0 0 auto;
  min-width: 30%;
  min-height: 44px;
  padding: 1.4cqh 4cqw;
  font-family: var(--font-display);
  font-size: clamp(0.6rem, 3cqw, 1rem);
  letter-spacing: 0.06em;
  color: #1a0800;
  background: var(--grad-gold);
  border: 1px solid var(--gold);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-gold);
  cursor: pointer;
  transition: filter 0.12s ease;
}
.pk-btn:active   { filter: brightness(0.88); }
.pk-btn:disabled { opacity: 0.55; filter: saturate(0.15) brightness(0.92); cursor: default; }
.pk-btn--shuffle { background: var(--grad-teal); border-color: var(--teal); color: var(--bg-deep); }
/* Three controls: Shuffle | Deal | Auto Pick. Deal is the wide one in the
   centre; the two helpers flank it and are toggled with visibility, never
   display, so the row geometry is identical in every phase (v151). */
.pk-btn--auto    { background: var(--grad-teal); border-color: var(--teal); color: var(--bg-deep); }
.pk-btn--shuffle,
.pk-btn--auto    { min-width: 0; flex: 1 1 0; padding-left: 2cqw; padding-right: 2cqw; }
.pk-btn--deal    { flex: 0 0 auto; min-width: 34%; }

/* ── PAYTABLE ──────────────────────────────────────────────────
   Values come from the server (see _pkRenderPaytable). Nothing is scrollable —
   the rows shrink to fit like the Lucky Reel's prize list. */
.pk-paytable {
  position: relative; z-index: 1;
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-rows: auto repeat(8, minmax(0, 1fr));
  gap: 0.2cqh;
  padding: 1cqh 2.5cqw;
  border: 1px solid rgba(255, 215, 0, 0.35);
  border-radius: 10px;
  background: rgba(0,0,0,0.28);
  background-size: 100% 100%;
  overflow: hidden;
}
.pk-paytable-title {
  font-family: var(--font-display);
  font-size: clamp(0.5rem, 2.4cqw, 0.8rem);
  letter-spacing: 0.14em;
  color: var(--gold-bright);
  text-align: center;
}
.pk-pay-row {
  /* line-height is 1, so a descender spills ~2px past its row and grows the
     panel scrollHeight. Contained here rather than by loosening the rows, which
     would push the eighth past the frame. */
  overflow: hidden;
  display: flex; align-items: center; justify-content: space-between;
  gap: 2cqw;
  font-size: clamp(0.45rem, 2.2cqw, 0.75rem);
  line-height: 1;
}
.pk-pay-name { color: var(--text-secondary); }
.pk-pay-val  { font-family: var(--font-mono); color: var(--gold-bright); }

/* ── RESULT POPUP ──────────────────────────────────────────────
   Same shape as the Lucky Reel summary (v155/v156): a full-bleed TAP SHIELD
   over the machine that does NOT dim, with the shared nine-slice frame and the
   pale halo on an inner panel. */
.pk-machine .pk-result {
  position: absolute; inset: 0; z-index: 9;
  display: none;
  flex-direction: column; align-items: center; justify-content: center;
  padding: 6%;
  background: none;
  border: none;
  overflow: hidden;
}
.pk-result-panel {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .5em;
  width: 100%;
  max-width: 300px;
  box-sizing: border-box;
  text-align: center;
}
.pk-result-panel.uipop-frame { padding: 4px 10px; }
.pk-result-line {
  font-family: var(--font-mono);
  font-size: clamp(0.9rem, 6cqw, 1.9rem);
  line-height: 1.15;
  text-wrap: balance;
  color: var(--gold-bright);
  text-shadow: 0 0 12px var(--gold-glow);
  margin: 0.15em 0 0.35em;
}
/* Out of play: the cabinet's controls are REMOVED from the render, not merely
   disabled, so nothing can paint over the popup whatever the z-order (v150). */
.pk-machine--ended .pk-actions { display: none; }

/* =============================================================
   POKER KING — CMS ART MODE (v164)

   ⚠️ THE CABINET IS OPAQUE. It has no transparent cut-outs: the felt, the
   JACKPOT plaque and the POKER HANDS panel are all painted solid in the
   902 x 1628 source. Nothing shows THROUGH an aperture — the cabinet is a
   backdrop and every live element is placed ON TOP of it by coordinate.

   EVERY COORDINATE BELOW WAS MEASURED OFF THE ART, not guessed. Method: decode
   the source and scan for its regions by colour —
     · felt (open red field)   x  70..832   y  724..1262   (762 x 538)
     · JACKPOT plaque interior x 177..723   y  484..658    (the "JACKPOT"
       caption is baked in at y~493, so live text sits BELOW it)
     · POKER HANDS interior    x  78..824   y 1348..1552   (the "POKER HANDS"
       caption is baked in at y 1300..1342)
   Expressed below as PERCENTAGES of the 902 x 1628 cabinet, so they hold at any
   rendered size.

   Everything is scoped to .pk-machine--art, which is added only when the
   cabinet asset is present. Without it the v162 placeholder layout is untouched
   and the game still plays.
   ============================================================= */
.pk-machine--art {
  /* ╔══ MEASURED AGAINST ═══════════════════════════════════════════════════╗
     ║  gameIcons_pokerking_cabinet-1789451182612.webp                       ║
     ║  941 x 2000 px · uploaded 2026-09-15 05:46 UTC · measured v176        ║
     ║  Button art (v177 — their proportions are hard-coded below):          ║
     ║    btn_deal-1789035867378.png        350 x 119                        ║
     ║    btn_shuffle-1788945851067.png     189 x 117                        ║
     ║    btn_auto_pick-1788945854912.png   189 x 116                        ║
     ║  v178: all four files re-checked, sizes unchanged. Nothing new is     ║
     ║  measured from the art this build — the text shares and the 44px     ║
     ║  tap floor are fractions of the CABINET (--pk-cab-w / button height), ║
     ║  not of any painted feature, so they survive a re-upload as long as  ║
     ║  the proportions above still hold.                                   ║
     ║                                                                       ║
     ║  If the live cabinet is not 941 x 2000, EVERY value in this block is  ║
     ║  stale — including aspect-ratio and --pk-aspect below. Nothing will   ║
     ║  look broken: each % still lands NEAR its feature, which is exactly   ║
     ║  how the previous file (907 x 1959, measured v168) went unnoticed for ║
     ║  a week and left the POKER HANDS list off-centre. Check with:         ║
     ║      const i = new Image(); i.src = LC_THEME.gameIcons.pokerking      ║
     ║        .cabinet; await i.decode(); [i.naturalWidth, i.naturalHeight] ║
     ╚═══════════════════════════════════════════════════════════════════════╝

     ┌── TUNABLES — all % of the CABINET box. Nudge freely. ──┐
     HOW THEY WERE MEASURED (v176). Row by row on the decoded image, by
     MIRRORED FEATURES rather than a brightness cut: the art is lit differently
     left and right (the POKER HANDS rim's inner shadow is ~40% brighter on the
     right), so one threshold stops on different features on each side. A pair
     is trusted only when its two x positions sum to the art's axis — 939–941
     here, axis x ≈ 470 — and every region below passed on nearly every row.
     "Painted" = green ≥ 40: the felt never exceeds 13, even at its glow.

     Live-art regions, source px (ranges inclusive):
       banner lower rule    y  744..746     x 123..814 ↔ mirrored (sum 937–940)
       felt, upper half     y  747..1053
       gold divider         y 1054..1056    the SAME rule as the banner's
       felt, lower half     y 1057..1501
       felt edges (tapers)  y  750..805  x 65..875 · y 806..1008 x 60–62..880
                            y 1014..1308 x 56–58..882 · y 1314..1428 x 55..885
                            corners round above 750 and below 1434
       JACKPOT plaque rim   x 190 | 750 (straight, y 549..612; curves in below
                            621) · caption y 518..545 · value area y 546..653
       POKER HANDS rim      x 82 | 857 · caption 1534..1561 · rule 1570..1571
                            · body 1572..1918 · bottom rim from 1919
       POKER HANDS studs    x 87..115 | 823..852, y 1884..1918

     Old → new for each value is in NOTES (v176). */
  /* CARD-ROW BOX. The narrowest felt either row overlaps is x 62..879, taken
     symmetric about 470 → open width [62, 878) = 816. The rows sit ONE CARD GAP
     in from that on each side, so the outer cards are as far from the felt's
     edge as from their neighbours: W + 2 x 1.5% W = 816 → W = 792.2.
     (Zero margin would make every card 2.6% larger and put the fifth card's
     edge 2px off the rail's shadow line.) */
  --pk-felt-left:       7.85%;  /* ← card-row box, left     (73.9/941)   */
  --pk-felt-width:     84.19%;  /* ← card-row box, width   (792.2/941)   */
  /* Documentation only — nothing reads these two. The felt, both halves. */
  --pk-felt-top:       37.35%;  /* ← under the banner rule   (747/2000)  */
  --pk-felt-height:    37.75%;  /* ← down to the POKER HANDS rim (755/2000) */

  /* A card is 183.0 source px tall at that row width (0.188 W x 188/153).
     TOP row: CENTRED in the upper half [747, 1054) → 809.0..992.0.
     BOTTOM row: MIRRORED across the divider — the same 62.0px below its lower
     edge (1057) as the top row ends above its upper edge (1054). Both rules are
     v168's; the offsets v175 saw were the stale file, not design. */
  --pk-row-top-top:    40.45%;  /* ← TOP row, top            (809.0/2000) */
  --pk-row-top-bottom: 55.95%;  /* ← BOTTOM row, top        (1119.0/2000) */
  --pk-card-gap-art:    1.50%;  /* ← gap between cards, % of the row width */

  --pk-hint-top:       65.36%;  /* ← hint, CENTRED in the gap between the
                                     bottom row (ends 1302.0) and the button
                                     strip (starts 1361.0); the line is 48.5
                                     source px tall          (1307.2/2000) */

  /* Nothing is PAINTED for the strip, so it is placed by v168's spacing: its
     bottom sits 5.53 CSS px (17.6 source px) above the felt's bottom edge
     (1502). Its HEIGHT is kept at the rendered 38.66px it had, so the buttons
     look exactly as they did and the v166 tap areas keep their size.
     The button WIDTHS follow from this height and each art's proportion
     (v177, below). */
  --pk-btns-top:       68.05%;  /* ← button strip, top         (1361.0/2000) */
  --pk-btns-height:     6.17%;  /* ← button strip, height       (123.4/2000) */
  /* v177: each button's WIDTH comes from its own art's proportion at the
     strip's height — never from a percentage. v168 set widths as % "of the
     cabinet", but a flex-basis % resolves against the flex CONTAINER (the
     strip), so all three were drawn ~15% narrower than their art: DEAL at
     2.49 against 2.94, SHUFFLE / AUTO at 1.35 against 1.62 / 1.63.
     Measured from the decoded PNGs — see the box at the top of this block. */
  --pk-btn-deal-ratio:    350 / 119;  /* ← btn_deal art, W / H            */
  --pk-btn-shuffle-ratio: 189 / 117;  /* ← btn_shuffle art                */
  --pk-btn-auto-ratio:    189 / 116;  /* ← btn_auto_pick art              */
  --pk-btn-gap:         1.86%;  /* ← gap between buttons, % of the CABINET.
                                     v177: the strip became the full cabinet
                                     width, so this is honestly % of the cabinet
                                     now. It was written 2.20% but resolved
                                     against the narrower strip, which rendered
                                     5.48px; 1.86% keeps exactly that. Left at
                                     2.20% (6.49px) the outer buttons' painted
                                     ends crossed the felt's rounded corners. */

  /* The VALUE box is the plaque's value area itself — rim to rim, caption to
     bottom rim — and the value is flex-centred in it, so it sits on y 600. */
  --pk-jackpot-left:   20.30%;  /* ← inside the left rim      (191/941)   */
  --pk-jackpot-width:  59.40%;  /* ← rim to rim               (559/941)   */
  --pk-jackpot-top:    27.30%;  /* ← under the baked caption  (546/2000)  */
  --pk-jackpot-height:  5.40%;  /* ← down to the bottom rim   (108/2000)  */

  /* ── POKER HANDS box — first re-measured in v175, confirmed in v176 ──
     The v168 box (9.04% / 84.12%, from the 907 x 1959 file) landed at source
     x 85 → 877 on this art: 2px inside the gold rim on the left, and through
     the rim INTO the blue neon on the right. That was the v174 imbalance.

     Measured row by row, by the rim's STRUCTURE rather than a brightness cut —
     the two sides are shaded differently (the right rim's inner shadow is ~40%
     brighter), so a luminance threshold stops at different features on each
     side. Mirrored features, source px:
         blue neon          60–64    ↔  875–879
         gold rim           69–82    ↔  857–870     inner pixel: 82 on 305/340
                                                    rows, 857 on 276/340 (858 on
                                                    32; the rest are the corner
                                                    studs stopping the walk)
         rim shadow         83–85    ↔  854–856
         corner studs       87–115   ↔  823–852     (y 1884–1918)
     every pair sums to 939 → the panel's axis is x = 469.5, so the interior
     between the rims, [83, 857), is centred on 470.0 by construction.
     Vertically: gold top rim 1502–1516 · baked caption 1534–1561 · a thin
     divider rule 1570–1571 · flat body from 1572 · bottom rim from 1919.
     The box is that interior, exactly: [83, 857) x [1572, 1919). */
  --pk-hands-left:      8.82%;  /* ← inside the left rim           (83/941)  */
  --pk-hands-width:    82.25%;  /* ← rim to rim               (774/941)  */
  --pk-hands-top:      78.60%;  /* ← under the caption's rule   (1572/2000) */
  --pk-hands-height:   17.35%;  /* ← down to the bottom rim      (347/2000) */
  --pk-hands-inset:     8.00%;  /* ← v174 (was 5.00%, v172 1.50%): how far the
                                     rows sit in from the panel's LEFT and RIGHT
                                     edges. ⚠️ % of the MACHINE width, not the
                                     list width — this is a padding on an
                                     absolutely-positioned box, so it resolves
                                     against its containing block (the v172
                                     comment said "list width" and was wrong;
                                     the VALUE was measured, so nothing moved).
                                     8.00% = 23.59px a side at the shipped
                                     cabinet size. Since v175 the box IS the
                                     rim-to-rim interior, so this is also the
                                     clearance from the gold rim itself —
                                     equal on both sides. */
  --pk-hands-inset-y:   3.00%;  /* ← v174: breathing room at the TOP and BOTTOM,
                                     so the first and last rows sit inside the
                                     bevel instead of flush against it. Also %
                                     of the MACHINE width — CSS resolves
                                     padding-top/bottom percentages against the
                                     containing block's WIDTH, not its height.

                                     ⚠️ THIS IS ALSO THE ROW-SPACING KNOB, and
                                     it cannot be otherwise. The panel's height
                                     is fixed by the art and the eight rows are
                                     1fr, so pitch = (height − 2×inset-y) / 8:
                                     every pixel of breathing room comes out of
                                     the row pitch, which is exactly what makes
                                     the list read tighter. 3.00% = 8.84px top
                                     and bottom.
                                     At 390 x 844 (v176, true aspect): panel
                                     108.72px, pitch 11.38px at 3.00% (v174:
                                     10.95, v175: 11.59 — each move came from
                                     the box, never from this value). 3.58%
                                     restores 10.95px exactly.
                                     ⚠️ FLOOR: 4.05% — past it the pitch drops
                                     under 10.6px at this size.
                                     SHORT SCREENS (v177): the row type now
                                     follows the cabinet (see the rule below),
                                     so 390 x 640 → 7.2px type, pitch 8.78px,
                                     1.36px between rows. Below ~540px of
                                     screen height the 7.2px legibility floor
                                     stops the type shrinking and rows touch
                                     again (375 x 530: −0.15px). */
  /* └── end TUNABLES ──┘ */

  /* ⚠️ --pk-aspect MUST be overridden here, not just aspect-ratio. v164 set
     aspect-ratio to the art's real 902/1628 but left the HEIGHT formula on
     .pk-machine dividing by the placeholder's --pk-aspect: 0.62 — so in art
     mode --pk-fit was NOT the fraction of the fitted box its comment claimed.
     The two ratios disagree by 0.5541/0.62 = 0.894, and when the WIDTH branch
     of the min() won (any reasonably tall viewport) the cabinet came out ~10.6%
     smaller than the knob said; when the HEIGHT branch won (short viewports) it
     came out exactly right. A tunable that means two different things depending
     on which branch wins is not a tunable, so the ratio is stated once here and
     --pk-fit is now honest in both branches. */
  /* v176: the LIVE art's proportion. It was 907 / 1959 (0.46299) — the
     previous file's — which drew this 941 x 2000 art 1.62% taller than it was
     painted. background-size is 100% 100%, so nothing looked broken: every %
     still landed on its feature, the artwork was simply stretched. */
  --pk-aspect: 0.4705;          /* 941 / 2000, as a number for the height calc */
  /* v177: the cabinet's rendered WIDTH as a length, from the SAME inputs and
     the same min() as .pk-machine's height rule (width = height x aspect).
     Unregistered, so cq units resolve where it is USED — against
     .mga-poker-king, the same container the cabinet sizes from. ⚠️ If the
     height rule on .pk-machine changes, change this with it. */
  --pk-cab-w: min(100cqw * var(--pk-fit), 100cqh * var(--pk-fit) * var(--pk-aspect));
  /* v178: the ONE legibility floor for text in the cabinet. Every text size
     below is a fixed share of --pk-cab-w and only this floor can stop it
     shrinking. See NOTES v178 for how the value was chosen and where it
     starts to bite. */
  --pk-text-min: 5.5px;
  aspect-ratio: 941 / 2000;     /* the cabinet's own proportion (live art) */
  display: block;               /* coordinates, not flex flow */
  padding: 0;
  gap: 0;
  background: none;
  border: none;
  box-shadow: none;
}
/* The cabinet's own drop shadow follows the art's silhouette, not a rectangle
   (same reasoning as the Lucky Reel's, v152). */
.pk-machine--art .pk-cabinet {
  filter: drop-shadow(0 10px 26px rgba(0, 0, 0, 0.55));
  border-radius: 0;
}
/* The art carries the title, the felt and the panel frames — the placeholders
   for all three are retired rather than drawn underneath. */
.pk-machine--art .pk-title { display: none; }
/* v168: the two rows sit in the two HALVES of the felt, either side of the
   painted gold divider, so each is positioned on its own. .pk-rows spans the
   whole cabinet height purely so a row's % top resolves against the CABINET
   — it paints nothing and takes no taps. Row HEIGHT stays content-driven: five
   cards across the felt at 153:188 fix it exactly (the v164 over-constraint
   lesson). */
.pk-machine--art .pk-rows {
  position: absolute;
  left: var(--pk-felt-left);
  width: var(--pk-felt-width);
  top: 0;
  height: 100%;
  padding: 0;
  gap: 0;
  background: none;
  box-shadow: none;
  border-radius: 0;
  pointer-events: none;
}
.pk-machine--art .pk-row {
  position: absolute;
  left: 0; right: 0;
  gap: var(--pk-card-gap-art);
  pointer-events: auto;
}
.pk-machine--art .pk-row--held { top: var(--pk-row-top-top); }
.pk-machine--art .pk-row--hand { top: var(--pk-row-top-bottom); }

/* Cards size from the ROW height so five always fit the felt's width. */
/* Cards size from their COLUMN, height from the aspect — so five always fit
   the felt exactly and nothing can overflow it. */
.pk-machine--art .pk-card,
.pk-machine--art .pk-slot--empty { width: 100%; height: auto; }

.pk-machine--art .pk-hint {
  position: absolute;
  left: var(--pk-felt-left);
  width: var(--pk-felt-width);
  top: var(--pk-hint-top);
  margin: 0;
  color: rgba(255, 226, 168, 0.92);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}

.pk-machine--art .pk-actions {
  position: absolute;
  /* v177: the strip spans the WHOLE cabinet and centres its buttons, so its
     own width no longer has to be summed from their widths, and the gap % is
     honestly a % of the cabinet. It paints nothing, and what lies either side
     of the buttons at this height (felt, rails) takes no taps. */
  left: 0;
  width: 100%;
  top: var(--pk-btns-top);
  height: var(--pk-btns-height);
  gap: var(--pk-btn-gap);
  align-items: stretch;      /* the art buttons have no text box of their own */
}
/* TRUE PROPORTION (v177). Height is the strip's (height: 100% on
   .pk-btn--art); aspect-ratio then fixes the width, and flex: 0 0 auto takes
   that width as-is — no growing, no shrinking, no percentage to resolve
   against the wrong box. background-size stays 100% 100%, which is now an
   exact fit rather than a stretch. */
.pk-machine--art .pk-btn--deal    { flex: 0 0 auto; aspect-ratio: var(--pk-btn-deal-ratio); }
.pk-machine--art .pk-btn--shuffle { flex: 0 0 auto; aspect-ratio: var(--pk-btn-shuffle-ratio); }
.pk-machine--art .pk-btn--auto    { flex: 0 0 auto; aspect-ratio: var(--pk-btn-auto-ratio); }

.pk-machine--art .pk-jackpot {
  position: absolute;
  left: var(--pk-jackpot-left);
  width: var(--pk-jackpot-width);
  top: var(--pk-jackpot-top);
  height: var(--pk-jackpot-height);
}
.pk-machine--art .pk-paytable {
  position: absolute;
  left: var(--pk-hands-left);
  width: var(--pk-hands-width);
  top: var(--pk-hands-top);
  height: var(--pk-hands-height);
  padding: var(--pk-hands-inset-y) var(--pk-hands-inset);
  border: none;
  background: none;
  grid-template-rows: repeat(8, minmax(0, 1fr));   /* the caption is in the art */
  /* Still NO row gap (v174). The eight 1fr rows share whatever height the
     vertical inset leaves, so the pitch is already the spacing control — a gap
     on top of it would only re-open the space --pk-hands-inset-y just closed,
     and it would land unevenly because a gap does not shrink the rows. */
  gap: 0;
}
.pk-machine--art .pk-paytable-title { display: none; }

/* ⚠️ THE ROWS STOP CLIPPING IN ART MODE (v174) — and only because the panel
   now has vertical padding. `.pk-pay-row { overflow: hidden }` exists to stop a
   descender (line-height is 1, so p/y/J spill past their row) from growing the
   panel's scrollHeight and making it scroll. At the v174 pitch of 10.95px the
   descenders on "Jackpot ticket" and "Free play" fall 0.15px past the row box,
   so that clip would cut them. It is safe to lift HERE because
   --pk-hands-inset-y leaves 8.84px of padding under the last row: a ~2px spill
   lands inside the padding box, so scrollHeight does not grow — verified 0
   overflow in both axes. The spill is also far smaller than the 1.95px of clear
   space above the next row's ink, so nothing collides. Base (non-art) mode
   keeps the clip: it has no such padding. */
.pk-machine--art .pk-pay-row { overflow: visible; }

/* ── ROW TYPE FROM THE CABINET, NOT THE SCREEN (v177) ─────────────────────
   The base rule sizes the rows at 2.2cqw — and the size container is
   .mga-poker-king, the whole SCREEN. The panel, like everything else in the
   art block, scales with the CABINET. When the height branch shrinks the
   cabinet (short screens) the panel shrank and the text did not: at 390 x 640
   the pitch was 8.78px against 8.58px type and two pairs of rows overlapped.

   The rows now size from --pk-cab-w, the cabinet's own width. 2.2cqw was
   2.2 / 75.6 = 2.910% of the cabinet whenever the WIDTH branch wins, so on
   every screen where the cabinet is width-limited (390 x 844 included) the
   size is byte-for-byte what it was; only where the height branch wins does
   it now shrink with the cabinet. (A size container on the panel was tried
   first and rejected: cqw measures a container's CONTENT box, so the type
   would have silently changed with --pk-hands-inset.)

   v178: the 0.45rem (7.2px) floor and 0.75rem ceiling are gone. At 7.2px the
   rows touched again below ~540px of screen height; rows touching is worse
   than small text in a reference panel, so the floor is now the shared
   --pk-text-min. Because pitch and type are both fixed shares of the cabinet,
   the gap between rows keeps its 390 x 844 proportion (2.22 / 294.84) at
   every size until that floor engages.

   The name–prize gap moves with it (2cqw = 2.646% of the cabinet). It is a
   MINIMUM only; space-between does the real spacing. */
.pk-machine--art .pk-pay-row {
  font-size: max(var(--pk-text-min), calc(var(--pk-cab-w) * 0.029101));
  gap: calc(var(--pk-cab-w) * 0.026455);
}

/* ── EVERY OTHER TEXT IN THE CABINET, FROM THE CABINET (v178) ─────────────
   These were the stragglers: sized in cqw of .mga-poker-king — the SCREEN —
   with rem floors and ceilings. On a height-limited screen the cabinet shrank
   and they did not (the suit pip reached 82% of its card's width at
   390 x 640), and on desktop the rem ceilings capped them while the cabinet
   stayed small.

   Each multiplier is the old cqw value / 75.6 (--pk-fit x 100), so on any
   screen where the cabinet is WIDTH-sized the result is exactly what it was.
   The rem floors and ceilings are dropped: the only floor is --pk-text-min,
   and the only ceiling is the cabinet's own size. The base (placeholder, no
   art) rules are untouched. */
.pk-machine--art .pk-hint         { font-size: max(var(--pk-text-min), calc(var(--pk-cab-w) * 0.034392)); } /* was 2.6cqw */
.pk-machine--art .pk-jackpot      { font-size: max(var(--pk-text-min), calc(var(--pk-cab-w) * 0.058201)); } /* was 4.4cqw */
.pk-machine--art .pk-card-index   { font-size: max(var(--pk-text-min), calc(var(--pk-cab-w) * 0.041005)); } /* was 3.1cqw (rank; the suit under it is 0.85em) */
.pk-machine--art .pk-card-pip     { font-size: max(var(--pk-text-min), calc(var(--pk-cab-w) * 0.100529)); } /* was 7.6cqw */
.pk-machine--art .pk-result-line  { font-size: max(var(--pk-text-min), calc(var(--pk-cab-w) * 0.079365)); } /* was 6cqw */

/* Live text over the JACKPOT plaque — value only, the caption is baked in. */
.pk-jackpot {
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono);
  font-size: clamp(0.7rem, 4.4cqw, 1.5rem);
  color: var(--gold-bright);
  text-shadow: 0 0 10px var(--gold-glow);
  pointer-events: none;
  z-index: 1;
}

/* ── BUTTON ART ────────────────────────────────────────────────
   One image per button, with the LABEL BAKED IN. The live label stays in the
   DOM but is clipped to a screen-reader-only box so it cannot double with the
   art; the CMS copy therefore still drives the accessible name. Pressed and
   disabled are DERIVED here, never separate images. */
.pk-btn { position: relative; }
.pk-btn-label { display: inline; }
.pk-btn--art {
  background: center / 100% 100% no-repeat;
  border: none;
  box-shadow: none;
  border-radius: 0;
  padding: 0;
  min-width: 0;
  min-height: 0;
  /* The label is clipped to a screen-reader box, so the button has no content
     to give it height — it takes the strip's height instead. */
  height: 100%;
}
/* ⚠️ v167 — btn_deal was re-uploaded BLANK, so the primary button's label is
   VISIBLE text again and pk_btn_deal / pk_btn_draw / pk_btn_collect drive what
   the player reads, not just what a screen reader hears. Shuffle and Auto Pick
   still have their words baked into their art, so those two keep the v164
   screen-reader-only clipping. */
.pk-btn--art:not(.pk-btn--deal) .pk-btn-label {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
/* The blank pill is a FIXED-SIZE image, not nine-sliced — it does not stretch to
   fit a longer word. The label is therefore capped to the pill's flat centre
   face and never allowed to wrap: one line, or nothing. */
.pk-btn--deal.pk-btn--art .pk-btn-label {
  display: block;
  max-width: 76%;
  margin: 0 auto;
  white-space: nowrap;
  overflow: hidden;
  font-family: var(--font-display);
  /* v178: was 3.4cqw. This rule can apply WITHOUT the cabinet art (button art
     and cabinet art are independent), where --pk-cab-w is undefined — the
     fallbacks reproduce the old width-branch size there. */
  font-size: max(var(--pk-text-min, 5.5px), calc(var(--pk-cab-w, 75.6cqw) * 0.044974));
  letter-spacing: 0.06em;
  color: #3a1d00;
  text-transform: uppercase;
  text-shadow: 0 1px 0 rgba(255, 226, 150, 0.55);
  line-height: 1;
}
/* ── TAP TARGET (v166) ─────────────────────────────────────────
   The art buttons are 38.9px tall — the strip's height comes from the art's own
   119/1628, so making the VISIBLE button taller would overhang the painted
   strip. Instead an invisible ::before extends the TOUCH area past the drawn
   button.

   WHY THE VISIBLE BOX DOES NOT MOVE: the pseudo-element is absolutely
   positioned, so it is out of flow — it contributes nothing to the button's own
   size and nothing to the row's layout. It is a child of the button, so a tap
   inside it hits the button. getBoundingClientRect() on the button is
   unchanged, which is also what keeps the v151 rule true.

   VERTICAL ONLY. The three buttons sit 6.5px apart (5.5px before v177) in a narrow strip, and a tap
   landing on the wrong one here would hold the wrong cards or deal a hand the
   player did not mean to — so the hit area is never widened, only heightened,
   and the three can therefore never overlap each other.

   The extension is a PERCENTAGE of the button's own height, so it scales with
   the cabinet exactly as the gaps above and below do — a fixed px value would
   start eating into the cards on a smaller cabinet.

   v178 — A 44px FLOOR, CAPPED. 12% alone gives a hit area of 1.24 x the
   button, which is under 44px wherever the visible button is under 35.5px —
   every height-limited screen shorter than ~761px (390 x 640: 37.0px). So the
   extension is now the LARGER of 12% and whatever reaches 44px in total, and
   never more than 40% of the button:
       · 12%  — the v166 value; still what applies on taller screens, so
                390 x 844 is unchanged at 47.93px.
       · (44.5px − 100%) / 2 — enough, each side, to make 44px. Aimed half a
                pixel high on purpose: layout snaps to 1/64px, and aiming at
                exactly 44 measured 43.99 at 390 x 640, 375 x 548 and
                320 x 568.
       · 40%  — the cap. The bottom card row ends 47.8% of a button height
                above the strip (59 of 123.4 source px), so 40% always leaves
                ≥ 7.8% of a button between the extended area and the cards.
                It engages below a 24.7px button — a ~531px-tall screen —
                where the hit area stops at 1.8 x the button rather than
                reach into the cards.
   Percentages in top/bottom resolve against the button's own height, so
   100% here IS the button. */
.pk-btn--art { --pk-tap-extend: clamp(12%, (44.5px - 100%) / 2, 40%); }   /* ← extra touch height, each side */
.pk-btn--art::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: calc(-1 * var(--pk-tap-extend));
  bottom: calc(-1 * var(--pk-tap-extend));
  /* no paint of its own — the artwork is unchanged */
}

/* PRESSED — slight darken plus a small inward offset of the ARTWORK. The offset
   moves background-position, so the button's own box is untouched: it cannot
   move, resize or reflow, and nothing here positions with a transform (v151).

   ⚠️ KNOWN AND ACCEPTED (v178 decision) — DO NOT "FIX". Since v177 the buttons
   are drawn at their art's true width, which leaves the outer two only
   1.6–2.7 source px clear of the felt's rounded bottom corners at rest. This
   fixed 2px offset moves each pill's widest rows down into the curve, so while
   a finger is down SHUFFLE and AUTO PICK paint 1–7 source px over the corner
   (worst on the smallest cabinets). That was reviewed and accepted: it lasts
   only as long as the press, and the finger is covering exactly that spot.
   Do not shrink the buttons, move the strip or scale this offset to avoid
   it. */
.pk-btn--art:active {
  filter: brightness(0.88);
  background-position: center calc(50% + 2px);
}
/* DISABLED — reduced opacity AND desaturation. Deliberate and permanent, the
   same derivation as the popup buttons (v154): opacity alone still reads as a
   live control on a busy background because it keeps its full colour. */
.pk-btn--art:disabled {
  opacity: 0.55;
  filter: saturate(0.15) brightness(0.92);
  background-position: center;
}

/* --- Poker King ten-position states (v168) ---------------------------
   IDLE: once the draw is committed, an unsaved bottom card has been replaced
   and is out of play — greyed, not hidden, so the column still reads.
   TAP: anything the player can act on right now shows a pointer.
   FLYING: the floating clone of a card being saved or unsaved. It moves by
   transform on an element OUTSIDE the grid, so nothing laid out ever moves. */
.pk-card--idle   { filter: grayscale(1) brightness(0.5); cursor: default; }
.pk-card--tap    { cursor: pointer; }
.pk-card--flying { pointer-events: none; }

/* ── THE LIFT (v169) ─────────────────────────────────────────────────────
   WHAT v168 DID: a single translate from slot to slot, 320ms, one strongly
   ease-OUT curve (0.3, 0.9, 0.35, 1), under a STATIC box-shadow. It leapt off at
   full speed, slid in a straight line at constant height, and stopped dead —
   a position change, not a card being picked up.

   NOW three layers, one job each, transform + opacity only:
     .pk-flight          the path — accelerates away, decelerates in
     .pk-flight::before  the contact shadow — spreads and fades out as the card
                         rises, gathers back under it as it is set down
     .pk-card            the lift — up a few percent, then a little give on
                         landing before it rests */
.pk-flight {
  position: absolute;
  z-index: 8;
  pointer-events: none;
  animation: pkFly var(--pk-save-ms) var(--pk-save-ease) forwards;
}
@keyframes pkFly {
  from { transform: translate(0, 0); }
  to   { transform: translate(var(--pk-fly-dx), var(--pk-fly-dy)); }
}
.pk-flight::before {
  content: '';
  position: absolute;
  left: 6%; right: 6%; bottom: -7%;
  height: 22%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0));
  animation: pkFlyShadow var(--pk-save-ms) ease-in-out forwards;
}
@keyframes pkFlyShadow {
  0%   { opacity: var(--pk-shadow-rest); transform: translateY(0)   scale(0.92); }
  40%  { opacity: var(--pk-shadow-lift); transform: translateY(20%) scale(1.20); }
  100% { opacity: var(--pk-shadow-rest); transform: translateY(0)   scale(0.92); }
}
.pk-flight > .pk-card {
  width: 100%;
  height: 100%;
  animation: pkFlyCard var(--pk-save-ms) ease-in-out forwards;
}
@keyframes pkFlyCard {
  0%   { transform: scale(1); }
  40%  { transform: scale(var(--pk-save-lift-scale)); }
  82%  { transform: scale(var(--pk-save-settle)); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .pk-flight,
  .pk-flight::before,
  .pk-flight > .pk-card { animation: none !important; }
}

/* --- Poker King win celebration (v170) --------------------------------
   ⚠️ INSERTED BEHIND THE CARDS. _pkCelebrate puts this layer before .pk-rows in
   the DOM, so everything here paints UNDER the hand. The whole point of the
   1200ms hold is that the player can read what they made, so the celebration is
   physically incapable of covering a card. It is inert to taps, adds nothing to
   layout (position:absolute, inset:0 on a positioned ancestor), and animates
   transform and opacity ONLY. */
.pk-celebrate {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;          /* a spark can never reach outside the cabinet */
}
.pk-celebrate * { pointer-events: none; }

/* The glow sits exactly on the top row's layout box and blooms outward from
   behind it. border-radius + box-shadow are STATIC; only opacity and scale move. */
.pk-celebrate-glow {
  position: absolute;
  border-radius: 14px;
  /* A soft core as well as the outer halo: the gaps between the five cards let
     it through, so the hand sits IN light rather than merely beside it. */
  background: radial-gradient(ellipse at center,
              rgba(255, 255, 255, 0.30) 0%, rgba(255, 255, 255, 0) 72%);
  will-change: opacity, transform;
  animation-fill-mode: forwards;
  animation-timing-function: cubic-bezier(0.25, 0.6, 0.35, 1);
}
.pk-celebrate--low  .pk-celebrate-glow {
  box-shadow: 0 0 var(--pk-cel-spread-low) rgba(var(--pk-cel-colour-low) / 0.95),
              0 0 calc(var(--pk-cel-spread-low) * 2.2) rgba(var(--pk-cel-colour-low) / 0.55);
  animation-name: pkCelLow;  animation-duration: var(--pk-cel-ms-low);
}
.pk-celebrate--mid  .pk-celebrate-glow {
  box-shadow: 0 0 var(--pk-cel-spread-mid) rgba(var(--pk-cel-colour-mid) / 0.95),
              0 0 calc(var(--pk-cel-spread-mid) * 2.2) rgba(var(--pk-cel-colour-mid) / 0.6);
  animation-name: pkCelMid;  animation-duration: var(--pk-cel-ms-mid);
}
.pk-celebrate--high .pk-celebrate-glow {
  box-shadow: 0 0 var(--pk-cel-spread-high) rgba(var(--pk-cel-colour-high) / 1),
              0 0 calc(var(--pk-cel-spread-high) * 2.4) rgba(var(--pk-cel-colour-high) / 0.7);
  animation-name: pkCelHigh; animation-duration: var(--pk-cel-ms-high);
}
/* LOW — one soft rise and fall. Nothing else. */
@keyframes pkCelLow {
  0%   { opacity: 0;                      transform: scale(0.985); }
  35%  { opacity: var(--pk-cel-peak-low); transform: scale(var(--pk-cel-scale-low)); }
  100% { opacity: 0;                      transform: scale(1); }
}
/* MID — two breaths, stronger. */
@keyframes pkCelMid {
  0%   { opacity: 0;                              transform: scale(0.985); }
  22%  { opacity: var(--pk-cel-peak-mid);         transform: scale(var(--pk-cel-scale-mid)); }
  48%  { opacity: calc(var(--pk-cel-peak-mid) * 0.55); transform: scale(1); }
  70%  { opacity: var(--pk-cel-peak-mid);         transform: scale(var(--pk-cel-scale-mid)); }
  100% { opacity: 0;                              transform: scale(1); }
}
/* HIGH — three breaths and the widest bloom. */
@keyframes pkCelHigh {
  0%   { opacity: 0;                               transform: scale(0.98); }
  16%  { opacity: var(--pk-cel-peak-high);         transform: scale(var(--pk-cel-scale-high)); }
  36%  { opacity: calc(var(--pk-cel-peak-high) * 0.5); transform: scale(1); }
  54%  { opacity: var(--pk-cel-peak-high);         transform: scale(var(--pk-cel-scale-high)); }
  74%  { opacity: calc(var(--pk-cel-peak-high) * 0.5); transform: scale(1); }
  88%  { opacity: calc(var(--pk-cel-peak-high) * 0.8); transform: scale(var(--pk-cel-scale-high)); }
  100% { opacity: 0;                               transform: scale(1); }
}

/* Sparks — HIGH tier only. Small, behind the cards, so they read in the gaps
   and around the edges of the hand rather than over it. Staggered off their
   index so they do not all leave together. */
.pk-celebrate-spark {
  position: absolute;
  width: 5px; height: 5px;
  margin: -2.5px 0 0 -2.5px;
  border-radius: 50%;
  background: rgba(var(--pk-cel-colour-high) / 1);
  box-shadow: 0 0 8px rgba(var(--pk-cel-colour-high) / 0.9);
  opacity: 0;
  animation: pkCelSpark var(--pk-cel-spark-ms) cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
  animation-delay: calc(var(--pk-spark-i) * 42ms);
}
@keyframes pkCelSpark {
  0%   { opacity: 0;    transform: translate(0, 0) scale(0.5); }
  18%  { opacity: 0.95; transform: translate(calc(var(--pk-spark-dx) * 0.28), calc(var(--pk-spark-dy) * 0.28)) scale(1); }
  100% { opacity: 0;    transform: translate(var(--pk-spark-dx), var(--pk-spark-dy)) scale(0.6); }
}

/* Reduced motion: no burst, no particles, no pulsing. _pkCelebrate builds
   nothing at all in that mode (there is no hold to decorate), and this is the
   second lock in case the hold is ever restored for it. */
@media (prefers-reduced-motion: reduce) {
  .pk-celebrate-glow,
  .pk-celebrate-spark { animation: none !important; }
  .pk-celebrate-spark { display: none !important; }
  .pk-celebrate-glow  { opacity: var(--pk-cel-peak-low); transform: none; }
}

/* --- Poker King winning-card mark (v171) ------------------------------
   ⚠️ INSIDE THE CARD. The mark is a child of the card with inset: 0, so it
   cannot touch the card's layout box, cannot escape .pk-card's overflow, and
   cannot widen a column. The rule and glow are drawn with an INSET box-shadow
   for the same reason — an outer shadow would be clipped anyway.
   The card stays fully legible: this is a rule around the edge and a soft bloom
   inside it, never a wash over the face. */
.pk-card-mark {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  z-index: 2;                    /* over the face, under nothing that matters */
  box-shadow:
    inset 0 0 0 var(--pk-mark-ring) rgba(var(--pk-mark-colour) / 1),
    inset 0 0 var(--pk-mark-glow) rgba(var(--pk-mark-colour) / 0.55);
  opacity: 0;
  animation: pkMarkIn var(--pk-mark-ms) cubic-bezier(0.2, 0.7, 0.35, 1) forwards;
  animation-delay: var(--pk-mark-delay, 0ms);
}
/* Opacity and transform only. The scale is on the MARK, never the card. */
@keyframes pkMarkIn {
  0%   { opacity: 0;                    transform: scale(1.06); }
  100% { opacity: var(--pk-mark-peak);  transform: scale(1); }
}

/* Reduced motion: v170 builds no celebration at all in that mode, so this mark
   IS the whole indication — which makes it more important there, not less. It
   is shown STATICALLY: no fade, no stagger, no scale, present the instant the
   hand resolves. */
@media (prefers-reduced-motion: reduce) {
  .pk-card-mark {
    animation: none !important;
    opacity: var(--pk-mark-peak);
    transform: none;
  }
}
