:root { color-scheme: dark; }
html, body {
  margin: 0; padding: 0; height: 100%;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  color: #fff;
  overflow: hidden;
  user-select: none;
}
body {
  background: #07021c; /* base color, in case bgs are loading */
}

/* Same layout pattern as BalloonPopRush:
   - #app is a narrow portrait column (max-width 480px), centered.
   - The bg image fills #app via in-column .bg-layer <img>s.
   - A separate blurred copy fills the rest of the viewport (visible on
     wide screens / desktop) via fixed .bg-blur-layer <img>s.
   - Both pairs crossfade together when cycling through bg_1..bg_4. */
.bg-blur-layer {
  position: fixed; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  filter: blur(28px);
  transform: scale(1.12); /* hide soft edges from the blur */
  opacity: 0;
  transition: opacity 1.2s ease-in-out;
  pointer-events: none;
  z-index: -2;
}
.bg-blur-layer.active { opacity: 1; }

.bg-layer {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.2s ease-in-out;
  pointer-events: none;
  z-index: 0;
}
.bg-layer.active { opacity: 1; }

#app {
  height: 100vh; max-width: 480px; margin: 0 auto;
  position: relative;
  display: flex; flex-direction: column;
  align-items: center;
  padding: 0 12px 8px;
  box-sizing: border-box;
  overflow: hidden;
  /* Subtle frame so the column edge is visible against the blurred backdrop */
  box-shadow: 0 0 60px rgba(0, 0, 0, 0.55), inset 0 0 0 1px rgba(255, 209, 102, 0.18);
}
/* Every direct child of #app sits above the in-column .bg-layer */
#app > *:not(.bg-layer) { position: relative; z-index: 1; }

/* Title image — z-index 12 so it sits on top of the stats frame (z-index 11).
   !important needed because a more-specific rule elsewhere can otherwise win. */
#title {
  margin-top: -25px;
  width: 70%;
  max-width: 380px;
  height: auto;
  filter: drop-shadow(0 4px 14px rgba(0, 0, 0, 0.7));
  pointer-events: none;
  position: relative;
  z-index: 12 !important;
}

/* Stats bar — ONE balanceFrame backdrop with LAST WIN / BALANCE / BET
   stacked inside. Title overlaps it from above. */
#stats {
  display: flex;
  gap: 0px;
  margin-top: -70px;
  position: relative;
  z-index: 11;
  align-self: baseline;
  width: 100%;
  aspect-ratio: 3.4;
  background: url('images/balanceFrame.webp') center no-repeat;
  background-size: cover;
  filter: drop-shadow(0 3px 8px rgba(0, 0, 0, 0.55));
}
.stat-cell {
  flex: 1;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 7% 2%;
  box-sizing: border-box;
  text-align: center;
}
/* Shift LAST WIN content rightward (toward center of frame) */
.stat-cell:first-child   { padding-left: 14%; padding-right: 0; }
/* Shift BET content leftward (toward center of frame) */
.stat-cell:last-child    { padding-left: 0; padding-right: 14%; }
/* 3D gold-on-dark label — stacked text-shadows build the extruded look:
   1) thin dark outline (4 directional 1px shadows form a stroke)
   2) two stacked solid dark shadows below for "depth"
   3) soft drop shadow for a grounding cast
   4) gold glow for sparkle */
.stat-cell .label {
  font-size: 16px; letter-spacing: 1.4px; font-weight: 900;
  color: #ffd166;
  text-shadow:
    -1px -1px 0 #3a1d00,
     1px -1px 0 #3a1d00,
    -1px  1px 0 #3a1d00,
     1px  1px 0 #3a1d00,
     0    2px 0 #2a1400,
     0    3px 0 #1a0a00,
     0    4px 6px rgba(0, 0, 0, 0.55),
     0    0   10px rgba(255, 200, 80, 0.45);
  line-height: 1;
  font-family: 'Lilita One', 'Arial Black', sans-serif;
}
/* 3D white-coin value — bright fill, dark stacked depth, gold rim glow */
.stat-cell .val {
  font-size: 19px; font-weight: 900;
  color: #fffcef;
  text-shadow:
    -1px -1px 0 #3a1d00,
     1px -1px 0 #3a1d00,
    -1px  1px 0 #3a1d00,
     1px  1px 0 #3a1d00,
     0    2px 0 #2a1400,
     0    3px 0 #1a0a00,
     0    4px 8px rgba(0, 0, 0, 0.6),
     0    0   12px rgba(255, 220, 120, 0.55);
  line-height: 1.1;
  margin-top: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  font-family: 'Lilita One', 'Arial Black', sans-serif;
}

/* Reel area — no flex-grow so meter/bet/action stack tightly under the
   slot frame instead of being pushed to the bottom of the viewport. */
#stage {
  width: 100%;
  margin-top: 6px;
  position: relative;
  display: flex; flex-direction: column; align-items: center;
  justify-content: flex-start;
}
#reelBox {
  position: relative;
  width: 100%;
  aspect-ratio: 1.95; /* matches goldFrame1x3 image proportions */
  max-height: 240px;
}
/* Reels live UNDER the frame; the frame is positioned absolutely on top.
   Insets are tuned to fit inside the gold frame's three rounded windows —
   the top number has to clear the decorative purple-gem crown. */
#reels {
  position: absolute;
  top: 27%; bottom: 16%;
  left: 6%; right: 6%; /* tighter so the reel white covers the frame windows fully (no sliver gaps) */
  display: flex; gap: 1%;
}
.reel {
  flex: 1;
  background: radial-gradient(circle at center, rgba(255,255,255,0.92) 0%, rgba(220,220,235,0.85) 70%, rgba(180,180,200,0.85) 100%);
  border-radius: 14px;
  overflow: hidden;
  position: relative;
  box-shadow: inset 0 4px 12px rgba(0,0,0,0.25);
}
.reel-strip {
  position: absolute; left: 0; right: 0; top: 0;
  display: flex; flex-direction: column; align-items: center;
  transition: transform 0.1s linear;
  will-change: transform;
}
.reel-strip img {
  width: 78%;
  aspect-ratio: 1;
  margin: 10% 0;
  object-fit: contain;
  filter: drop-shadow(0 4px 6px rgba(0,0,0,0.45));
  pointer-events: none;
}
.reel.spinning .reel-strip {
  animation: spin-cycle 0.45s linear infinite;
  filter: blur(2px);
}
@keyframes spin-cycle {
  /* Seamless DOWN cycle. The reel-strip is filled in JS with the SAME 5
     decoy images appended TWICE (10 total). Animating from -50% to 0
     moves the strip down by exactly one half — its height — landing the
     second copy in the visual position the first copy occupied at -50%.
     Because the two copies are identical, the snap-back at 100%→0% is
     visually invisible (no missing-frame "blink" at the reel bottom). */
  0%   { transform: translateY(-50%); }
  100% { transform: translateY(0); }
}
.reel.win {
  box-shadow: 0 0 16px rgba(255, 230, 102, 0.95), inset 0 0 16px rgba(255, 230, 102, 0.5);
  animation: win-pulse 0.55s ease-in-out infinite alternate;
}
@keyframes win-pulse {
  from { filter: brightness(1); }
  to   { filter: brightness(1.35); }
}
/* Gold frame overlay */
#reelFrame {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  object-fit: contain;
  filter: drop-shadow(0 4px 14px rgba(0,0,0,0.6));
}

/* Bonus meter — slight negative margin pulls it up under the slot frame */
#meterRow {
  display: flex; align-items: center;
  gap: 10px;
  width: 100%;
  margin-top: -32px;
  position: relative;
  z-index: 5;
  padding: 12px 12px;
  box-sizing: border-box;
  background: url('images/bonusBarBackground.webp') center / 100% 100% no-repeat;
}
#meterRow .chest {
  width: 56px; height: 56px;
  object-fit: contain;
  position: relative;
  z-index: 1;
  flex-shrink: 0;
}
/* Open chest on the LEFT — represents earned/collected progress: warm gold glow */
#meterRow .chest.open {
  animation: chest-glow-gold 1.8s ease-in-out infinite alternate;
}
/* Closed chest on the RIGHT — represents the next goal: cooler magenta glow */
#meterRow .chest.closed {
  animation: chest-glow-magenta 1.8s ease-in-out infinite alternate;
}
@keyframes chest-glow-gold {
  from { filter: drop-shadow(0 0  6px rgba(255, 215,  64, 0.7)) drop-shadow(0 3px 6px rgba(0,0,0,0.55)); }
  to   { filter: drop-shadow(0 0 20px rgba(255, 215,  64, 1.0)) drop-shadow(0 3px 6px rgba(0,0,0,0.55)); }
}
@keyframes chest-glow-magenta {
  from { filter: drop-shadow(0 0  6px rgba(255, 100, 220, 0.6)) drop-shadow(0 3px 6px rgba(0,0,0,0.55)); }
  to   { filter: drop-shadow(0 0 20px rgba(255, 100, 220, 1.0)) drop-shadow(0 3px 6px rgba(0,0,0,0.55)); }
}
/* Gem flood meter — overlay covering the BOTTOM HALF of #app. Gems are
   pre-positioned at random spots throughout the container; client.js
   reveals them progressively from bottom→top based on meter progress.
   pointer-events: none so taps on the bet/spin buttons still go through.
   Selector is `#app > #gemFlood` so it beats the global
   `#app > *:not(.bg-layer)` rule which would otherwise force this back
   into the document flow with z-index: 1. */
#app > #gemFlood {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  /* Tall enough that the pile's apex reaches the bottom of the slot frame
     at a full meter. Adjust if your slot-frame layout changes. */
  height: 65%;
  pointer-events: none;
  /* z-index: 0 sits ABOVE the .bg-layer (also z:0, but earlier in DOM
     order) and BELOW every other UI element (default z:1, plus explicit
     z 5+ for the slot stage/frame, bet/spin controls, stats, title). */
  z-index: 0;
  overflow: hidden;
}
.flood-gem {
  position: absolute;
  width: 30px; height: 30px;
  object-fit: contain;
  opacity: 0;
  transition: opacity 0.6s ease-out, filter 0.4s;
  filter: drop-shadow(0 0 4px gold) drop-shadow(0 2px 3px rgba(0, 0, 0, 0.55));
  /* Inline style sets bottom% + left% + rotate/scale per-gem. */
}
.flood-gem.visible { opacity: 1; }
/* When meter ready: the whole flood gently pulses. nth-child stagger so the
   shimmer ripples instead of pulsing in unison. */
#gemFlood.ready .flood-gem.visible {
  animation: flood-sparkle 1.6s ease-in-out infinite alternate;
}
#gemFlood.ready .flood-gem.visible:nth-child(3n)   { animation-delay: 0.25s; }
#gemFlood.ready .flood-gem.visible:nth-child(3n+1) { animation-delay: 0.5s; }
@keyframes flood-sparkle {
  from { filter: drop-shadow(0 0 4px gold) drop-shadow(0 2px 3px rgba(0,0,0,0.55)); }
  to   { filter: drop-shadow(0 0 12px gold) drop-shadow(0 0 22px rgba(255,209,102,0.85)) brightness(1.2); }
}

/* No more meter row — the gem flood IS the meter. */
#meterRow { display: none !important; }

/* Bet row — pulled up to overlap the bottom of the meter frame */
#betRow {
  display: flex; align-items: center; justify-content: center;
  gap: 8px;
  width: 100%;
  margin-top: -25px;
  position: relative;
  z-index: 6;
}
.iconBtn {
  background: none; border: 0; padding: 0;
  cursor: pointer;
  transition: transform 0.1s;
}
.iconBtn:active { transform: scale(0.92); }
.iconBtn img { width: 44px; height: 44px; object-fit: contain; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.6)); pointer-events: none; }
.iconBtn:disabled { opacity: 0.5; cursor: not-allowed; }

/* Bet selector — same frame style as the top stat cards, just bigger */
#betCenter {
  position: relative;
  flex: 0 0 auto;
  height: 135px;
  aspect-ratio: 1.5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  text-align: center;
}
/* +/- buttons live INSIDE betCenter, absolutely positioned at the frame's
   left and right edges (frame fills betCenter exactly since their aspect
   ratios match). */
#betCenter .iconBtn {
  position: absolute;
  top: 50%;
  z-index: 1;
  transform: translateY(-50%);
}
#betCenter .iconBtn.bet-minus { left: -30px; }
#betCenter .iconBtn.bet-plus  { right: -30px; }
/* Label + value stack vertically in the center, sitting on top of the frame. */
#betCenter .bet-info {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
}
#betCenter .frame {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  pointer-events: none;
  z-index: 0;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.5));
}
#betCenter .label {
  position: relative; z-index: 1;
  font-size: 14px; letter-spacing: 1.4px; font-weight: 900;
  color: #ffd166;
  text-shadow:
    -1px -1px 0 #3a1d00,
     1px -1px 0 #3a1d00,
    -1px  1px 0 #3a1d00,
     1px  1px 0 #3a1d00,
     0    2px 0 #2a1400,
     0    3px 0 #1a0a00,
     0    0   10px rgba(255, 200, 80, 0.45);
  line-height: 1;
  font-family: 'Lilita One', 'Arial Black', sans-serif;
}
#betCenter .val {
  position: relative; z-index: 1;
  font-size: 22px; font-weight: 900;
  color: #fffcef;
  text-shadow:
    -1px -1px 0 #3a1d00,
     1px -1px 0 #3a1d00,
    -1px  1px 0 #3a1d00,
     1px  1px 0 #3a1d00,
     0    2px 0 #2a1400,
     0    3px 0 #1a0a00,
     0    4px 8px rgba(0, 0, 0, 0.6),
     0    0   12px rgba(255, 220, 120, 0.55);
  line-height: 1.1; margin-top: 2px;
  max-width: 100%;
  font-family: 'Lilita One', 'Arial Black', sans-serif;
}
#betCenter input {
  background: transparent; border: 0; outline: 0;
  width: 100%; text-align: center;
  color: inherit; font: inherit;
  -moz-appearance: textfield;
}
#betCenter input::-webkit-outer-spin-button,
#betCenter input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Action row — big negative margin so SPIN button visually overlaps the
   bet row. SPIN gets highest z-index so it always sits on top. */
#actionRow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  width: 100%;
  margin-top: -55px;
  padding: 0 4px;
  position: relative;
  z-index: 7;
}
#spin { position: relative; z-index: 10; }
#maxBtn, #settingsBtn {
  background: none; border: 0; padding: 0;
  cursor: pointer;
  transition: transform 0.1s;
}
#maxBtn img, #settingsBtn img {
  width: 70px; height: 70px;
  object-fit: contain;
  filter: drop-shadow(0 3px 6px rgba(0,0,0,0.6));
  pointer-events: none;
}
#maxBtn:active, #settingsBtn:active { transform: scale(0.93); }
#spin {
  background: none; border: 0; padding: 0;
  cursor: pointer;
  position: relative;
  transition: transform 0.1s;
}
#spin img {
  width: 130px; height: 130px;
  object-fit: contain;
  filter: drop-shadow(0 4px 10px rgba(0,0,0,0.6));
  pointer-events: none;
}
#spin:active { transform: scale(0.95); }
#spin:disabled { opacity: 0.5; cursor: not-allowed; }
#spin.free-spin-ready img {
  animation: free-spin-glow 0.8s ease-in-out infinite alternate;
}
@keyframes free-spin-glow {
  from { filter: drop-shadow(0 4px 10px rgba(0,0,0,0.6)) drop-shadow(0 0 8px gold); }
  to   { filter: drop-shadow(0 4px 10px rgba(0,0,0,0.6)) drop-shadow(0 0 22px gold); }
}


/* Dim+blur cover behind the win banner so the gold frame pops off the
   stage instead of blending into it. Activated via :has(#winBanner.show)
   so we don't need a JS toggle. */
#winBackdrop {
  position: fixed; inset: 0;
  background: radial-gradient(ellipse at center, rgba(15, 5, 60, 0.55) 0%, rgba(0, 0, 0, 0.85) 75%);
  backdrop-filter: blur(5px);
  opacity: 0;
  pointer-events: none;
  z-index: 90;          /* under winBanner (z:100), over everything else */
  transition: opacity 0.3s ease-out;
}
#app:has(#winBanner.show) #winBackdrop { opacity: 1; }

/* === Win banner (jewelled gold frame + WIN crown + value) === */
#winBanner {
  position: absolute;
  left: 50%; top: 35%;
  transform: translate(-50%, -50%) scale(0);
  width: 92%;
  max-width: 380px;
  pointer-events: none;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  filter: drop-shadow(0 8px 18px rgba(0, 0, 0, 0.55));
}
#winBanner.show { transform: translate(-50%, -50%) scale(1); }
#winBanner .win-title {
  width: 55%;
  max-width: 200px;
  height: auto;
  /* Overlap the WIN crown down into the top of the frame */
  margin-bottom: -32px;
  position: relative;
  z-index: 2;
}
#winBanner .win-frame {
  position: relative;
  width: 100%;
  height: 130px;
  /* The value bg is THE background of the frame, stretched to fill. */
  background: url('images/winPartsVValueBG.webp') center / 100% 100% no-repeat;
  /* Alive: subtle breathe + pulsing gold aura — gives the whole frame a
     constant heartbeat instead of feeling static. */
  animation: win-frame-alive 1.7s ease-in-out infinite alternate;
}
@keyframes win-frame-alive {
  from {
    transform: scale(1);
    filter:
      drop-shadow(0 6px 14px rgba(0, 0, 0, 0.55))
      drop-shadow(0 0 12px rgba(255, 209, 102, 0.4));
  }
  to {
    transform: scale(1.03);
    filter:
      drop-shadow(0 6px 14px rgba(0, 0, 0, 0.55))
      drop-shadow(0 0 28px rgba(255, 209, 102, 0.85))
      drop-shadow(0 0 48px rgba(255, 140, 220, 0.45));
  }
}
/* Four small corner ornaments. TL uses the source image as-is; the others
   are CSS-mirrored so we can ship a single corner asset. */
#winBanner .win-corner {
  position: absolute;
  width: 35px;     /* tune: corner ornament size */
  height: auto;
  pointer-events: none;
  /* Filter is animated below; do not redeclare it statically here or the
     animation override won't take effect. */
  animation: win-corner-glow 1.7s ease-in-out infinite alternate;
}
/* Corners don't get a transform animation (they already use scaleX/Y for
   mirroring — would conflict). Animate filter glow only. */
@keyframes win-corner-glow {
  from { filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5)); }
  to   { filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5)) drop-shadow(0 0 10px rgba(255, 255, 255, 0.7)) drop-shadow(0 0 18px rgba(180, 80, 255, 0.6)); }
}
#winBanner .win-corner.tl { top: 0;    left: 0;  }
#winBanner .win-corner.tr { top: 0;    right: 0; transform: scaleX(-1); }
#winBanner .win-corner.bl { bottom: 0; left: 0;  transform: scaleY(-1); }
#winBanner .win-corner.br { bottom: 0; right: 0; transform: scale(-1, -1); }

#winBanner .win-amount {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Display font — "Lilita One" is a chunky friendly slot-style face. */
  font-family: 'Lilita One', 'Arial Black', sans-serif;
  font-weight: 400;       /* Lilita One only ships at 400 — set explicitly */
  font-size: 64px;
  letter-spacing: 2px;
  z-index: 3;
  pointer-events: none;
  /* Rich gold gradient — multiple stops mimic the highlight→midtone→shadow
     transition you see on the WIN crown's burnished metal. */
  background: linear-gradient(180deg,
    #fffce0  0%,    /* top highlight */
    #fff0a0  12%,
    #ffe88c  22%,
    #ffd166  38%,
    #f5a000  55%,   /* core gold */
    #c87000  72%,
    #7a3000  88%,
    #3a1500 100%);  /* deep shadow at the bottom */
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
          color: transparent;
  /* Thick dark outline for the cartoon rim. */
  -webkit-text-stroke: 3px #1a0a00;
  /* Shorter, gold-toned extrusion: 3 stepped shadows in deepening bronze
     so the text looks embossed on gold rather than dropped from black.
     Soft cast shadow at the bottom anchors it to the frame.
     Value rides the .win-frame's scale animation via parent transform. */
  filter:
    drop-shadow(0 2px 0 #c98c0a)
    drop-shadow(0 4px 0 #8a5a05)
    drop-shadow(0 6px 0 #4a2f00)
    drop-shadow(0 8px 10px rgba(40, 20, 0, 0.7));
}

/* freeSpinBanner — only used for the BONUS UNLOCKED celebration now.
   max-width keeps it inside the stage column even at small viewports;
   white-space normal lets the JS-injected line breaks render. */
#freeSpinBanner {
  position: absolute; left: 50%; top: 30%;
  transform: translate(-50%, -50%) scale(0);
  max-width: 92%;
  text-align: center;
  pointer-events: none;
  z-index: 100;
  transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}
#freeSpinBanner.show { transform: translate(-50%, -50%) scale(1); }

#status {
  position: absolute; top: 70px; left: 50%;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.6); padding: 5px 12px; border-radius: 14px;
  font-size: 12px; opacity: 0; transition: opacity 0.2s;
  pointer-events: none; white-space: nowrap;
  z-index: 50;
}
#status.show { opacity: 1; }
#status.error { background: linear-gradient(180deg, #ef4444, #b91c1c); font-weight: 800; }

/* Bonus stage popup (using whiteFrame.webp as the container) */
#bonusOverlay {
  position: fixed; inset: 0;
  background: rgba(5, 5, 15, 0.85);
  backdrop-filter: blur(8px);
  z-index: 200;
  display: none;
  align-items: center; justify-content: center;
}
#bonusOverlay.show { display: flex; }
#bonusBox {
  position: relative;
  width: 90%; max-width: 440px;
  aspect-ratio: 1.18; /* matches whiteFrame.webp */
}
#bonusFrame {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
  filter: drop-shadow(0 8px 32px rgba(0,0,0,0.6));
}
#bonusInner {
  position: absolute;
  /* Empirical inset to fit the 3×3 inside the frame's purple cells */
  top: 18%; bottom: 8%; left: 12%; right: 12%;
  display: flex; flex-direction: column;
}
#bonusHud {
  display: flex; justify-content: space-around;
  font-size: 11px; font-weight: 800; letter-spacing: 1.2px;
  color: #fff; text-shadow: 0 1px 2px #000;
  margin-bottom: 6px;
}
#bonusHud .pl { color: #ff66dd; }
#bonusHud .tw { color: #ffd166; }
#bonusGrid {
  flex: 1;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px;
}
.bonus-cell {
  background: none;
  border: 0;
  border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  position: relative;
  transition: transform 0.15s;
}
.bonus-cell img.chest {
  width: 86%; height: 86%; object-fit: contain;
  filter: drop-shadow(0 3px 6px rgba(0,0,0,0.4));
  transition: filter 0.25s;
}
/* Idle bob animation REMOVED — running 9 parallel keyframe animations was
   a heavy hit on the bonus-stage feel. Chests are static at rest; the
   firework + open animation on click is now the focus. */
.bonus-cell:not(.revealed):hover img.chest {
  transform: scale(1.06);
  filter: drop-shadow(0 6px 14px rgba(255, 209, 102, 0.7)) drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5));
  transition: transform 0.15s, filter 0.15s;
}

.bonus-cell.revealed { cursor: default; }
/* On reveal: chest swaps to open variant (handled in JS) + does a bigger,
   faster open-shake. Stays visible after — reward overlay sits on top. */
.bonus-cell.revealed img.chest {
  animation: chest-open-burst 0.35s cubic-bezier(0.34, 1.78, 0.64, 1) forwards;
  filter: drop-shadow(0 0 22px gold) drop-shadow(0 3px 8px rgba(0, 0, 0, 0.6));
}
@keyframes chest-open-burst {
  0%   { transform: scale(0.6)  rotate(-15deg); }
  45%  { transform: scale(1.45) rotate(10deg);  }
  100% { transform: scale(1.20) rotate(0deg);   }
}
.bonus-cell .reward {
  position: absolute;
  top: 50%; left: 50%;
  font-size: 36px;
  font-family: 'Lilita One', 'Arial Black', sans-serif;
  color: #fff8d4;
  text-shadow:
    -2px -2px 0 #3a1d00,
     2px -2px 0 #3a1d00,
    -2px  2px 0 #3a1d00,
     2px  2px 0 #3a1d00,
     0 3px 0 #000,
     0 0 18px gold,
     0 0 32px #ff66dd;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.4);
  pointer-events: none;
  z-index: 2;
  /* Pop in earlier (0.2s delay vs 0.45s) and faster */
  transition: opacity 0.25s 0.2s, transform 0.35s 0.2s cubic-bezier(0.34, 1.78, 0.64, 1);
  letter-spacing: 1px;
}
.bonus-cell.revealed .reward {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1.25);
}

/* Big white-gold flash burst centered on the chest at reveal moment.
   Spawned by JS as a fixed-position element; auto-removes. */
.chest-reveal-flash {
  position: fixed;
  width: 200px; height: 200px;
  background: radial-gradient(circle,
    rgba(255, 255, 240, 0.95) 0%,
    rgba(255, 209, 102, 0.7) 28%,
    rgba(255, 209, 102, 0) 70%);
  pointer-events: none;
  z-index: 999;
  transform: translate(-50%, -50%) scale(0);
  animation: chest-flash 0.55s ease-out forwards;
}
@keyframes chest-flash {
  0%   { transform: translate(-50%, -50%) scale(0);   opacity: 0; }
  25%  { transform: translate(-50%, -50%) scale(1.6); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(2.4); opacity: 0; }
}

/* Gem-burst particles — DOM <img>s spawned at click position. */
.gem-burst {
  position: fixed;
  width: 36px; height: 36px;
  pointer-events: none;
  z-index: 9998;
  filter: drop-shadow(0 0 10px gold) drop-shadow(0 0 18px rgba(255,200,80,0.6));
  animation: gem-fly 1.0s ease-out forwards;
  will-change: transform, opacity;
}
@keyframes gem-fly {
  0%   { transform: translate(-50%, -50%) scale(0.3) rotate(0deg);   opacity: 0; }
  15%  { opacity: 1; }
  100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(0.6) rotate(540deg); opacity: 0; }
}

/* Big-chest finale — shown after the bonus pick(s) complete.
   Covers the bonus overlay with the chest + total win amount centerstage. */
#bonusFinale {
  position: fixed; inset: 0;
  z-index: 250;
  display: none;
  flex-direction: column;
  align-items: center; justify-content: center;
  background: radial-gradient(circle at center, rgba(40, 20, 80, 0.85) 0%, rgba(5, 5, 15, 0.95) 70%);
  backdrop-filter: blur(6px);
  /* User taps anywhere to dismiss — see #bonusFinale click handler in client.js. */
  cursor: pointer;
}
#bonusFinale.show { display: flex; animation: finale-fade 0.4s ease-out; }
/* "Tap to continue" hint — fades in after the chest+win animation settles
   (~1.6s) so it doesn't compete with the reveal. */
#bonusFinale .finale-hint {
  margin-top: 40px;
  font-family: 'Lilita One', 'Arial Black', sans-serif;
  font-size: 14px; letter-spacing: 3px;
  color: rgba(255, 248, 212, 0.7);
  text-shadow: 0 2px 4px rgba(0,0,0,0.7);
  opacity: 0;
  animation: finale-hint-in 1.5s 1.6s ease-out forwards,
             finale-hint-pulse 1.4s 3.1s ease-in-out infinite alternate;
}
@keyframes finale-hint-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 0.7; transform: translateY(0); }
}
@keyframes finale-hint-pulse {
  from { opacity: 0.45; }
  to   { opacity: 0.9; }
}
@keyframes finale-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
#bonusFinale .big-chest {
  width: 60%; max-width: 280px;
  height: auto;
  filter: drop-shadow(0 0 50px gold) drop-shadow(0 8px 20px rgba(0,0,0,0.7));
  animation: big-chest-in 1.0s cubic-bezier(0.34, 1.56, 0.64, 1) forwards,
             big-chest-bob 2.2s 1.0s ease-in-out infinite alternate;
}
/* Big chest emerges FROM the chest the player just picked. JS sets
   --origin-dx and --origin-dy on #bonusFinale (the parent) — fractions of
   the way move the chest from the click location to viewport center as
   it scales up. If origin vars are missing (e.g., the celebration was
   triggered without a known chest position), it falls back to plain
   center-scaling via the `0px` defaults. */
@keyframes big-chest-in {
  0% {
    transform:
      translate(var(--origin-dx, 0px), var(--origin-dy, 0px))
      scale(0.15) rotate(-20deg);
    opacity: 0;
  }
  20% {
    transform:
      translate(calc(var(--origin-dx, 0px) * 0.85), calc(var(--origin-dy, 0px) * 0.85))
      scale(0.55) rotate(-10deg);
    opacity: 1;
  }
  65% {
    transform:
      translate(calc(var(--origin-dx, 0px) * 0.15), calc(var(--origin-dy, 0px) * 0.15))
      scale(1.18) rotate(8deg);
    opacity: 1;
  }
  100% {
    transform: translate(0, 0) scale(1) rotate(0deg);
    opacity: 1;
  }
}
@keyframes big-chest-bob {
  from { transform: scale(1)    translateY(0); }
  to   { transform: scale(1.05) translateY(-8px); }
}
#bonusFinale .finale-win {
  font-family: 'Lilita One', 'Arial Black', sans-serif;
  font-size: 64px; font-weight: 900;
  color: #fff8d4;
  margin-top: 28px;
  letter-spacing: 3px;
  text-shadow:
    -2px -2px 0 #3a1d00,
     2px -2px 0 #3a1d00,
    -2px  2px 0 #3a1d00,
     2px  2px 0 #3a1d00,
     0 4px 0 #000,
     0 0 32px gold,
     0 0 60px #ff66dd;
  animation: finale-text 0.6s 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
}
@keyframes finale-text {
  from { transform: scale(0.4) translateY(30px); opacity: 0; }
  to   { transform: scale(1)   translateY(0);    opacity: 1; }
}
#bonusWin {
  text-align: center;
  margin-top: 4px;
  font-size: 22px; font-weight: 900;
  color: gold; min-height: 28px;
  text-shadow: 0 0 12px gold, 0 2px 0 #000;
}

/* === Settings overlay === */
#settingsOverlay {
  position: fixed; inset: 0;
  background: rgba(5, 5, 15, 0.85);
  backdrop-filter: blur(6px);
  z-index: 220;
  display: none;
  align-items: center; justify-content: center;
}
#settingsOverlay.show { display: flex; }
#settingsPanel {
  width: 90%; max-width: 380px;
  max-height: 85vh;
  overflow-y: auto;
  background: linear-gradient(180deg, #2a1660 0%, #160830 100%);
  border: 3px solid #c9a44d;
  border-radius: 18px;
  box-shadow: 0 0 40px rgba(255, 209, 102, 0.4), 0 12px 30px rgba(0, 0, 0, 0.7);
  padding: 18px;
  box-sizing: border-box;
  font-family: 'Lilita One', 'Arial Black', sans-serif;
}
.settings-header {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 18px;
  padding-bottom: 10px;
  border-bottom: 2px solid rgba(201, 164, 77, 0.4);
}
.settings-title {
  font-size: 22px; letter-spacing: 3px;
  color: #ffd166;
  text-shadow: 0 2px 0 #4a2f00, 0 0 10px rgba(255, 209, 102, 0.5);
}
#settingsClose {
  background: rgba(0, 0, 0, 0.4);
  border: 2px solid #c9a44d;
  color: #ffd166;
  width: 36px; height: 36px;
  border-radius: 50%;
  font-size: 24px; line-height: 1;
  font-family: inherit;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  padding: 0 0 4px 0;
  transition: transform 0.1s, background 0.15s;
}
#settingsClose:active { transform: scale(0.9); }
#settingsClose:hover { background: rgba(255, 209, 102, 0.2); }

.settings-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 4px;
  margin-bottom: 14px;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(201, 164, 77, 0.25);
  border-radius: 10px;
  padding-left: 14px; padding-right: 14px;
}
.settings-label {
  font-size: 16px; letter-spacing: 2px;
  color: #fff8d4;
  text-shadow: 0 1px 2px #000;
}
.settings-toggle {
  background: linear-gradient(180deg, #2da82d 0%, #1b6b1b 100%);
  border: 2px solid #6cd66c;
  color: #fff;
  font-family: inherit;
  font-size: 14px; letter-spacing: 2px;
  padding: 6px 16px;
  border-radius: 20px;
  cursor: pointer;
  min-width: 70px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
  transition: transform 0.1s;
}
.settings-toggle.off {
  background: linear-gradient(180deg, #a82d2d 0%, #6b1b1b 100%);
  border-color: #d66c6c;
}
.settings-toggle:active { transform: scale(0.95); }

.settings-section {
  margin-top: 4px;
}
.settings-section-title {
  font-size: 16px; letter-spacing: 3px;
  color: #ffd166;
  text-align: center;
  margin-bottom: 12px;
  text-shadow: 0 2px 0 #4a2f00;
}
.paytable {
  display: flex; flex-direction: column;
  gap: 8px;
}
.pay-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 14px;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid rgba(201, 164, 77, 0.3);
  border-radius: 10px;
}
.pay-symbols {
  display: flex; align-items: center; gap: 4px;
}
.pay-symbols img {
  width: 32px; height: 32px;
  object-fit: contain;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.6));
}
.pay-value {
  font-size: 18px; letter-spacing: 1.5px;
  color: #ffd166;
  text-shadow: 0 2px 0 #4a2f00, 0 0 6px rgba(255, 209, 102, 0.3);
}
.pay-value.bonus {
  color: #ff66dd;
  text-shadow: 0 2px 0 #5a005a, 0 0 8px rgba(255, 102, 221, 0.5);
  font-size: 14px;
  letter-spacing: 2px;
}

/* Env badge */
#env-badge {
  position: fixed; bottom: 6px; right: 6px; z-index: 9999;
  display: none; gap: 4px;
  font-family: ui-monospace, monospace; font-size: 10px; line-height: 1; pointer-events: none;
}
#env-badge.show { display: flex; }
#env-badge .chip { padding: 4px 6px; border-radius: 5px; border: 1px solid; backdrop-filter: blur(4px); }
#env-badge .chip.env { background: rgba(245, 158, 11, 0.85); color: #000; border-color: rgba(252, 211, 77, 1); font-weight: 700; }
#env-badge .chip.sha { background: rgba(0, 0, 0, 0.4); color: rgba(255, 255, 255, 0.5); border-color: rgba(255, 255, 255, 0.1); }

/* ========================================================================
   Meter-triggered "BONUS UNLOCKED!" celebration sequence.
   Fires once every 200 paid spins, when the forced-HHH spin lands.
   ======================================================================== */

/* Body class set for ~2.5s during the celebration — used as a focus hook
   in case we want to dim other UI later. */
body.bonus-trigger-active #app { box-shadow: 0 0 80px rgba(255, 215, 64, 0.45), inset 0 0 0 1px rgba(255, 209, 102, 0.5); }

/* Screen-wide white→gold flash that fades to transparent. */
.bonus-screen-flash {
  position: fixed; inset: 0;
  background: radial-gradient(circle at center, rgba(255, 255, 240, 0.9) 0%, rgba(255, 215, 64, 0.5) 40%, rgba(255, 215, 64, 0) 75%);
  pointer-events: none;
  z-index: 9998;
  animation: bonus-flash 0.9s ease-out forwards;
}
@keyframes bonus-flash {
  0%   { opacity: 0; transform: scale(0.6); }
  20%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.05); }
}

/* Confetti rain — DOM elements injected by client.js fall from above. */
#confettiLayer {
  position: fixed; inset: 0;
  pointer-events: none;
  z-index: 9997;
  overflow: hidden;
}
.confetti-piece {
  position: absolute;
  top: -20px;
  width: 10px; height: 16px;
  border-radius: 2px;
  animation: confetti-fall linear forwards;
  box-shadow: 0 0 6px rgba(255, 255, 255, 0.5);
}
@keyframes confetti-fall {
  0%   { transform: translateY(-40px) rotate(0deg);   opacity: 1; }
  85%  { opacity: 1; }
  100% { transform: translateY(110vh) rotate(720deg); opacity: 0; }
}

/* === BONUS UNLOCKED! celebration — 3D gold === */
#freeSpinBanner.bonus-unlocked {
  display: flex;
  flex-direction: column;
  align-items: center;
  animation: bonus-banner-pulse 0.85s ease-in-out infinite alternate;
}
/* Each text line is its own span so wrapping stays predictable and each
   line gets the gold gradient + 3D extrusion applied independently. */
#freeSpinBanner.bonus-unlocked .bu-line {
  font-family: 'Lilita One', 'Arial Black', sans-serif;
  font-size: 56px;
  line-height: 0.95;
  letter-spacing: 3px;
  /* Gold gradient mimicking the WIN amount style. */
  background: linear-gradient(180deg,
    #fffce0 0%, #fff0a0 12%, #ffe88c 22%, #ffd166 38%,
    #f5a000 55%, #c87000 72%, #7a3000 88%, #3a1500 100%);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
          color: transparent;
  -webkit-text-stroke: 3px #1a0a00;
  /* Gold-toned 3D extrusion (3 steps) + cast shadow. */
  filter:
    drop-shadow(0 2px 0 #c98c0a)
    drop-shadow(0 4px 0 #8a5a05)
    drop-shadow(0 6px 0 #4a2f00)
    drop-shadow(0 8px 14px rgba(40, 20, 0, 0.75));
}
#freeSpinBanner.bonus-unlocked .bu-line + .bu-line { margin-top: 4px; }
/* The avg bet sub-line is smaller, same gold styling but no big extrusion. */
#freeSpinBanner.bonus-unlocked small {
  font-family: 'Lilita One', 'Arial Black', sans-serif;
  font-size: 18px;
  letter-spacing: 2px;
  margin-top: 14px;
  color: #fff8d4;
  text-shadow:
    -1px -1px 0 #3a1d00, 1px -1px 0 #3a1d00,
    -1px  1px 0 #3a1d00, 1px  1px 0 #3a1d00,
    0 2px 0 #2a1400,
    0 3px 6px rgba(0, 0, 0, 0.8);
}
@keyframes bonus-banner-pulse {
  from { transform: translate(-50%, -50%) scale(1)    rotate(-1deg); }
  to   { transform: translate(-50%, -50%) scale(1.06) rotate(1deg); }
}

/* Gem flood "explodes" on bonus trigger — all visible gems over-saturate
   and brighten in sync with the BONUS UNLOCKED celebration. */
#gemFlood.exploding .flood-gem.visible {
  animation: flood-explode 0.4s ease-in-out infinite alternate !important;
}
@keyframes flood-explode {
  0%   { filter: drop-shadow(0 0 4px gold)  brightness(1)   saturate(1.2); }
  100% { filter: drop-shadow(0 0 16px gold) brightness(1.8) saturate(2); }
}
