/* ── Sign-in-required popup ───────────────────────────────────────────────
   Opens on top of #iap-modal (z-280) when an unsigned-in player taps a
   SKU. The buy modal stays mounted underneath but is dimmed by this
   modal's backdrop. After a successful sign-in we auto-resume the
   in-flight purchase from the original tile click. */
#iap-signin-required-modal {
  position: fixed; inset: 0;
  display: none; align-items: center; justify-content: center;
  z-index: 320;  /* above iap-modal (280) and verifying overlay (300) */
  padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom));
}
#iap-signin-required-modal.visible { display: flex; }
.iap-signin-req-backdrop {
  position: absolute; inset: 0;
  background: rgba(8, 6, 24, 0.72);
}
.iap-signin-req-card {
  position: relative;
  width: min(380px, 92vw);
  background:
    radial-gradient(circle at 50% -20%, rgba(126, 226, 255, 0.18) 0%, transparent 55%),
    linear-gradient(170deg, rgba(34, 28, 64, 0.98), rgba(18, 16, 38, 0.98));
  border: 1px solid rgba(126, 226, 255, 0.32);
  border-radius: 18px;
  box-shadow:
    0 30px 90px rgba(0, 0, 0, 0.65),
    0 0 0 1px rgba(255, 255, 255, 0.04) inset;
  padding: 24px 22px 18px;
  text-align: center;
}
.iap-signin-req-title {
  margin: 0 0 8px;
  color: #ecfaff;
  font-size: 19px; font-weight: 800;
  letter-spacing: 0.4px;
  line-height: 1.2;
}
.iap-signin-req-body {
  margin: 0 0 20px;
  color: rgba(220, 230, 255, 0.78);
  font-size: 13px; line-height: 1.5;
}
.iap-signin-req-providers {
  display: flex; flex-direction: column; gap: 10px;
  margin-bottom: 16px;
}
/* Provider button — generic shell. Each provider's class can layer
   brand-specific paint on top (Google: white pill; Apple: black pill;
   Steam: dark-blue; Epic: black). */
.iap-signin-req-provider {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%;
  padding: 11px 16px;
  font: inherit; font-size: 14px; font-weight: 700;
  border-radius: 8px;
  cursor: pointer;
  transition: transform 0.12s, filter 0.14s, background 0.14s;
}
.iap-signin-req-provider:hover:not(:disabled) {
  transform: translateY(-1px);
  filter: brightness(1.04);
}
.iap-signin-req-provider:active:not(:disabled) { transform: translateY(0) scale(0.98); }
.iap-signin-req-provider:disabled { opacity: 0.6; cursor: progress; }
.iap-signin-req-provider:focus-visible {
  outline: 2px solid rgba(126, 226, 255, 0.85);
  outline-offset: 3px;
}
/* Google provider: white pill w/ Google's recommended treatment. */
.iap-signin-req-google {
  background: #fff;
  color: #1f1f1f;
  border: 1px solid #dadce0;
}
.iap-signin-req-google:hover:not(:disabled) { background: #f7f7f7; }
/* Loading state — re-uses the shared spin-360 keyframes. */
.iap-signin-req-provider.is-loading {
  position: relative;
  color: transparent !important;
  pointer-events: none;
}
.iap-signin-req-provider.is-loading svg { visibility: hidden; }
.iap-signin-req-provider.is-loading::after {
  content: '';
  position: absolute;
  top: 50%; left: 50%;
  margin: -9px 0 0 -9px;
  width: 18px; height: 18px;
  border: 2px solid rgba(0, 0, 0, 0.18);
  border-top-color: rgba(0, 0, 0, 0.65);
  border-radius: 50%;
  animation: spin-360 0.7s linear infinite;
}
.iap-signin-req-no-providers {
  padding: 18px 8px;
  background: rgba(255, 180, 84, 0.08);
  border: 1px dashed rgba(255, 180, 84, 0.32);
  border-radius: 10px;
  color: #ffd699;
  font-size: 12.5px;
}
/* Cancel — quiet ghost button, separated from the provider stack so
   it doesn't read as another sign-in option. */
.iap-signin-req-cancel {
  background: none;
  border: none;
  color: rgba(220, 230, 255, 0.55);
  font: inherit; font-size: 13px; font-weight: 600;
  padding: 6px 12px;
  cursor: pointer;
  transition: color 0.14s;
}
.iap-signin-req-cancel:hover { color: #d8f4ff; }
.iap-signin-req-cancel:focus-visible {
  outline: 2px solid rgba(126, 226, 255, 0.85);
  outline-offset: 3px;
  border-radius: 6px;
}

/* ── Energy-empty popup ──────────────────────────────────────────────────
   Shown when the player taps PLAY with 0 energy. Same visual language
   as the iap-signin-required modal. */
#energy-empty-modal {
  position: fixed; inset: 0;
  display: none; align-items: center; justify-content: center;
  z-index: 1250;
  padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom));
}
#energy-empty-modal.visible { display: flex; }
.energy-empty-backdrop {
  position: absolute; inset: 0;
  background: rgba(8, 6, 24, 0.72);
}
.energy-empty-card {
  position: relative;
  width: min(380px, 92vw);
  background:
    radial-gradient(circle at 50% -20%, rgba(255, 215, 102, 0.18) 0%, transparent 55%),
    linear-gradient(170deg, rgba(34, 28, 64, 0.98), rgba(18, 16, 38, 0.98));
  border: 1px solid rgba(255, 215, 102, 0.32);
  border-radius: 18px;
  box-shadow:
    0 30px 90px rgba(0, 0, 0, 0.65),
    0 0 0 1px rgba(255, 255, 255, 0.04) inset;
  padding: 24px 22px 18px;
  text-align: center;
}
.energy-empty-title {
  margin: 0 0 8px;
  color: #fff8e0;
  font-size: 19px; font-weight: 800;
  letter-spacing: 0.4px;
  line-height: 1.2;
}
.energy-empty-body {
  margin: 0 0 20px;
  color: rgba(220, 230, 255, 0.78);
  font-size: 13px; line-height: 1.5;
}
.energy-empty-actions {
  display: flex; flex-direction: column; gap: 10px;
  margin-bottom: 16px;
}
.energy-empty-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%;
  padding: 12px 16px;
  font: inherit; font-size: 14px; font-weight: 700;
  border-radius: 10px;
  cursor: pointer;
  transition: transform 0.12s, filter 0.14s;
  border: none;
}
.energy-empty-btn:hover:not(:disabled) {
  transform: translateY(-1px);
  filter: brightness(1.08);
}
.energy-empty-btn:active:not(:disabled) { transform: translateY(0) scale(0.98); }
.energy-empty-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.energy-empty-btn-icon { font-size: 18px; }
.energy-empty-btn-label { text-align: left; line-height: 1.3; }
.energy-empty-btn-label small {
  font-size: 11px; font-weight: 600;
  opacity: 0.7;
}
.energy-refill {
  background: linear-gradient(135deg, #ffd766 0%, #ffaa33 100%);
  color: #1a1200;
}
.energy-shop {
  background: rgba(126, 226, 255, 0.16);
  color: #d8f1ff;
  border: 1px solid rgba(126, 226, 255, 0.4);
}
.energy-empty-cancel {
  background: none;
  border: none;
  color: rgba(220, 230, 255, 0.55);
  font: inherit; font-size: 13px; font-weight: 600;
  padding: 6px 12px;
  cursor: pointer;
  transition: color 0.14s;
}
.energy-empty-cancel:hover { color: #d8f4ff; }

/* ── Floating toast for IAP feedback ──────────────────────────────────────
   Sits above the IAP modal (z-280) and any other overlay (z-9999) so a
   verify-failed message is never hidden behind the modal backdrop. Used
   when the relic shop isn't on screen (the in-shop toast is preferred
   when available — see _showToast in src/iap.js). */
.iap-floating-toast {
  position: fixed;
  bottom: max(24px, env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%) translateY(8px);
  padding: 12px 18px;
  background: rgba(20, 20, 28, 0.96);
  color: #f0e8d0;
  font-size: 14px; font-weight: 600;
  border: 1px solid rgba(126, 226, 255, 0.32);
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  pointer-events: none;
  opacity: 0;
  transition: opacity 200ms ease, transform 200ms ease;
  z-index: var(--z-critical);
  max-width: min(420px, calc(100vw - 32px));
  text-align: center;
}
.iap-floating-toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.iap-floating-toast.error {
  border-color: rgba(255, 142, 142, 0.55);
  background: rgba(48, 18, 24, 0.96);
  color: #ffd6d6;
}
.relic-shop-toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* Spec 2b-Google — Account section + conflict modal */
.settings-account-row {
  display: flex; flex-direction: column; gap: 12px;
  /* The parent #settings-modal .settings-row sets align-items: center, which
     would horizontally center the account card / blurb / sign-out button on
     this column-flex row. Explicit flex-start aligns them to the start edge
     instead — left in LTR, right in RTL (flexbox auto-mirrors with dir=rtl).
     Specificity 0,2,0 vs the parent 0,1,1 wouldn't be enough; the
     #settings-modal-prefixed rule below ensures we win. */
}
#settings-modal .settings-account-row { align-items: flex-start; }
/* The row also matches `#settings-modal .settings-row { display: flex }`,
   whose ID specificity (0,1,1) beats a plain attribute selector (0,2,0).
   Without the #settings-modal prefix here the [hidden] rule loses the
   cascade and BOTH rows render at the same time. */
#settings-modal .settings-account-row[hidden] { display: none; }
.settings-account-blurb,
.settings-account-readout {
  color: var(--text-dim); font-size: 13px; margin: 0;
}
.settings-link {
  background: transparent; border: none;
  color: rgba(180, 200, 240, 0.62);
  font-size: 12px; cursor: pointer;
  padding: 0; text-decoration: underline;
  align-self: flex-start;
}
.settings-link:hover { color: rgba(246,248,254,0.92); }

/* Signed-in card: avatar + name/email stack on the left, sign-out button
   on the right. Stacks vertically on narrow screens. */
.settings-account-card {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  background: rgba(126,226,255,0.06);
  border: 1px solid rgba(126,226,255,0.18);
  border-radius: 12px;
}
.settings-account-avatar {
  flex: 0 0 auto;
  width: 40px; height: 40px;
  border-radius: 50%;
  overflow: hidden;
  background: linear-gradient(135deg, rgba(126,226,255,0.32), rgba(160,140,255,0.28));
  display: flex; align-items: center; justify-content: center;
  border: 1px solid rgba(126,226,255,0.32);
}
.settings-account-avatar img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.settings-account-avatar-initial {
  font-size: 16px; font-weight: 700;
  color: rgba(246, 248, 254, 0.92);
  text-shadow: 0 1px 2px rgba(0,0,0,0.4);
}
.settings-account-meta {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; gap: 2px;
}
.settings-account-name {
  color: rgba(246,248,254,0.96);
  font-size: 14px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.settings-account-email {
  color: var(--text-dim);
  font-size: 12.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.settings-account-email:empty { display: none; }

/* Standalone Sign-out row — sits at the bottom of the settings modal as
   the destructive end-of-list action. Hidden when the user isn't signed
   in. The .settings-account-row[hidden] override doesn't reach this row
   (different class), so use [hidden]'s default display:none. */
.settings-signout-row {
  margin-top: 24px;
  padding-top: 16px;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.settings-signout-btn {
  display: block;
  width: 100%;
  padding: 12px 16px;
  border-radius: 10px;
  background: rgba(239, 68, 68, 0.10);
  border: 1px solid rgba(239, 68, 68, 0.40);
  color: #ff8a8a;
  font: inherit; font-size: 13.5px; font-weight: 700;
  letter-spacing: 0.4px;
  cursor: pointer;
  transition: background 0.14s, border-color 0.14s, color 0.14s, transform 0.12s;
}
.settings-signout-btn:hover {
  background: rgba(239, 68, 68, 0.18);
  border-color: rgba(239, 68, 68, 0.7);
  color: #ffadad;
  transform: translateY(-1px);
}
.settings-signout-btn:active { transform: translateY(0); }

.settings-toast {
  position: absolute; bottom: 16px; left: 50%;
  transform: translateX(-50%) translateY(8px);
  padding: 8px 14px;
  background: rgba(20, 20, 28, 0.92);
  color: #f0e8d0;
  font-size: 13px;
  border-radius: 6px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 240ms ease, transform 240ms ease;
  z-index: 100;
}
.settings-toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ─── Touch devices — make modal panels full-screen ─────────────────────────
   The default panels were centered cards (`width: min(960px, 92vw); max-height:
   88vh`) — fine on desktop where there's chrome around the page, but on phones
   they leave dead space and a stacked rounded border that competes with the
   game-world canvas behind them. Match the pattern stage-select + wiki already
   use: full-bleed on touch, no rounded corners, no border. Inner header keeps
   the safe-area-inset-top padding it already had so Android 15 / iOS notches
   stay clear. Desktop is unaffected. */
body.touch:not(.tablet) #settings-modal .settings-card,
body.touch:not(.tablet) #leaderboard-modal .lb-card,
body.touch:not(.tablet) #achievements-panel,
body.touch:not(.tablet) .iap-card {
  /* rc163 — #relic-shop-panel dropped from this list; the relic shop
     is no longer a modal, its contents live in the Play Hub SHOP tab
     which is already full-bleed via #stage-select-panel. */
  /* dvh = dynamic viewport height — adapts to the actual visible
     area as iOS Safari's URL bar hides/shows. */
  width: 100vw; height: 100vh; height: 100dvh;
  max-width: none;
  max-height: 100vh; max-height: 100dvh;
  margin: 0;
  border-radius: 0; border: none;
}
/* Drop the IAP modal's outer 24px padding too — it shrinks the card by 48px
   on each axis, defeating the full-bleed. The .iap-card itself already
   handles its own internal padding. */
body.touch:not(.tablet) #iap-modal { padding: 0; }

/* ─── Full-bleed touch panels: 100% opaque background ──────────────────────
   On touch, these panels cover the whole viewport — anything below is
   invisible regardless. Bumping the gradient stops from alpha 0.96/0.97
   to 1.0 lets the compositor short-circuit painting of every layer
   below the panel. Same trick that fixed the IAP success popup; same
   reason it works (compositor sees fully-opaque surface and skips the
   underlying stack entirely). Visual is identical — the 3-4% alpha gap
   was already invisible against the deep navy stops. */
body.touch:not(.tablet) #leaderboard-modal .lb-card,
body.touch:not(.tablet) #achievements-panel,
body.touch:not(.tablet) .iap-card {
  /* rc163 — #relic-shop-panel dropped; relic shop is now a Play Hub tab. */
  background: linear-gradient(160deg, rgb(30, 36, 60), rgb(22, 18, 40));
}
body.touch:not(.tablet) #settings-modal .settings-card {
  background: linear-gradient(180deg, rgb(28, 38, 68), rgb(18, 22, 40));
}
body.touch:not(.tablet) .wiki-panel {
  background: linear-gradient(180deg, rgb(28, 22, 42), rgb(20, 16, 30));
}
/* Content wrapper holds count + title + blurb + button on the success
   popup. Used by both portrait (flex column inside the card) and
   landscape (right column of the grid). Default flow is a vertical
   stack so portrait keeps working; the landscape rule below positions
   it inside the grid via grid-column/grid-row. */
body.touch:not(.tablet) .iap-success-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  width: 100%;
  max-width: 420px;
}

/* ─── Touch devices — unified header padding ──────────────────────────────
   Each modal had its own portrait-specific override (.lb-head:14px,
   #achievements-header:14px, .settings-head:34px, #relic-shop-header:18px,
   .iap-card:22px). Result: title sat between 14 and 40px from the top
   depending on which modal you opened. Most also dropped env(safe-area-
   inset-top), so dynamic-island / punch-hole cutouts overlapped titles
   on phones that report safe-area to webviews. Unified rule below: 22dp
   base + safe-area-inset-top so titles always clear the cutout AND sit
   at the same height across modals. Horizontal padding also folds in
   safe-area-inset-left/right for landscape dynamic-island intrusions.
   The rule sits AFTER all the portrait media queries (line numbers
   1561 / 3341 / 4952 / 5196) so it wins on cascade. */
body.touch:not(.tablet) #settings-modal .settings-head,
body.touch:not(.tablet) #leaderboard-modal .lb-head,
body.touch:not(.tablet) #achievements-header {
  padding-top: calc(env(safe-area-inset-top, 0px) + 22px) !important;
  padding-right: calc(env(safe-area-inset-right, 0px) + 18px);
  padding-bottom: 14px;
  padding-left: calc(env(safe-area-inset-left, 0px) + 18px);
}
body.touch:not(.tablet) #shop-tab-header { display: none; }
/* IAP doesn't have a separate header element — title sits inside the
   .iap-card's own padding. Mirror the same insets so it lines up. */
body.touch:not(.tablet) .iap-card {
  padding-top: calc(env(safe-area-inset-top, 0px) + 22px);
  padding-right: calc(env(safe-area-inset-right, 0px) + 18px);
  padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 14px);
  padding-left: calc(env(safe-area-inset-left, 0px) + 18px);
}
/* Wiki uses `.wiki-modal padding: 24px` to inset the panel; on touch we
   want full-bleed (the panel itself takes 100% width/height) AND the
   header should clear the dynamic island. Matches the same pattern. */
body.touch:not(.tablet) .wiki-modal {
  padding: 0;
}
body.touch:not(.tablet) .wiki-header {
  padding-top: calc(env(safe-area-inset-top, 0px) + 22px) !important;
  padding-right: calc(env(safe-area-inset-right, 0px) + 18px);
  padding-bottom: 14px;
  padding-left: calc(env(safe-area-inset-left, 0px) + 18px);
}
/* Stage select uses #stage-select-header — same treatment so the whole
   set of full-screen modals matches. The existing body.touch:not(.tablet) rule was
   `padding: calc(14px + env(safe-area-inset-top, 0px)) 18px 10px` —
   override to the unified 22dp base. */
body.touch:not(.tablet) #stage-select-header {
  padding-top: calc(env(safe-area-inset-top, 0px) + 22px) !important;
  padding-right: calc(env(safe-area-inset-right, 0px) + 18px);
  padding-bottom: 14px;
  padding-left: calc(env(safe-area-inset-left, 0px) + 18px);
}

/* ─── Pause landing-page animations when any overlay is on top ──────────────
   When the landing screen sits behind a translucent / backdrop-blurred
   overlay (relic shop, leaderboard, achievements, settings, wiki, stage
   select, IAP buy modal, IAP success popup), the 96 photons + PLAY halo
   pulse + RELIC halo pulse + title shimmer keep animating. Each frame
   forces the compositor to re-sample the layers below the overlay and
   recompute the blur — which is the dominant source of the "janky over
   the title screen" feel the user reported.

   Pausing the animations stops the per-frame layer churn. The blur
   below the overlay computes once, then caches. Overlays above stay
   smooth.

   :has() lets us flip these on a single body-level selector instead of
   wiring class toggles into every JS modal-open path. Supported in all
   Chromium 105+ (covers our Capacitor Android WebView and modern
   browsers). Fails closed on older browsers — animations keep running,
   no breakage. */
body:has(#leaderboard-modal.visible) .landing-photon,
body:has(#achievements-modal.visible) .landing-photon,
body:has(#settings-modal.visible) .landing-photon,
body:has(.wiki-modal:not(.hidden)) .landing-photon,
body:has(#stage-select.visible) .landing-photon,
body:has(#iap-modal.visible) .landing-photon,
body:has(.iap-success-modal.visible) .landing-photon,
body:has(#leaderboard-modal.visible) .landing-play-wrap::before,
body:has(#achievements-modal.visible) .landing-play-wrap::before,
body:has(#settings-modal.visible) .landing-play-wrap::before,
body:has(.wiki-modal:not(.hidden)) .landing-play-wrap::before,
body:has(#stage-select.visible) .landing-play-wrap::before,
body:has(#iap-modal.visible) .landing-play-wrap::before,
body:has(.iap-success-modal.visible) .landing-play-wrap::before,
body:has(#leaderboard-modal.visible) #landing-utility-row::before,
body:has(#achievements-modal.visible) #landing-utility-row::before,
body:has(#settings-modal.visible) #landing-utility-row::before,
body:has(.wiki-modal:not(.hidden)) #landing-utility-row::before,
body:has(#stage-select.visible) #landing-utility-row::before,
body:has(#iap-modal.visible) #landing-utility-row::before,
body:has(.iap-success-modal.visible) #landing-utility-row::before,
body:has(#leaderboard-modal.visible) .landing-title-wrap::after,
body:has(#achievements-modal.visible) .landing-title-wrap::after,
body:has(#settings-modal.visible) .landing-title-wrap::after,
body:has(.wiki-modal:not(.hidden)) .landing-title-wrap::after,
body:has(#stage-select.visible) .landing-title-wrap::after,
body:has(#iap-modal.visible) .landing-title-wrap::after,
body:has(.iap-success-modal.visible) .landing-title-wrap::after {
  animation-play-state: paused;
}

/* ─── Landscape phones — short-height modal & in-game overlays ──────────────
   Every panel that hugs `max-height: 88vh` was sized for portrait; on a
   ~390px-tall landscape viewport the 34dp Android-15 status-bar inset +
   16dp bottom inset + tall headers + generous card padding eat the budget,
   and the second row of cards / second row of stage filters / right-pane
   detail clips below the fold. Trim insets (the bar is hidden in landscape
   so 14dp top is enough), shrink headers, tighten card padding, switch
   horizontal-scroll where wrapping was the symptom. Existing portrait +
   max-width media queries above stay authoritative in their own ranges. */
@media (orientation: landscape) and (max-height: 720px) {
  /* ── Powerup picker (LEVEL UP) ─────────────────────────────────────── */
  #powerup-screen h2 { font-size: 22px; margin-bottom: 4px; letter-spacing: 0.5px; }
  #powerup-screen p { font-size: 12px; margin-bottom: 12px; }
  #powerup-choices { gap: 12px; }
  .powerup-card { width: 180px; padding: 14px 14px; }
  .powerup-card .icon { width: 42px; height: 42px; margin-bottom: 6px; }
  .powerup-card .name { font-size: 13px; margin-bottom: 4px; }
  .powerup-card .desc { font-size: 11.5px; line-height: 1.4; }
  .powerup-card .tier { font-size: 10px; margin-top: 6px; }

  /* ── Stage select ─────────────────────────────────────────────────── */
  /* Default touch layout was vertical-row cards 70-80px tall — only ~3
     visible in landscape. Compress to ~52px each so 6+ stages scroll
     into view without thumb gymnastics. */
  body.touch:not(.tablet) #stage-select-header {
    padding: calc(10px + env(safe-area-inset-top, 0px))
             calc(18px + env(safe-area-inset-right, 0px))
             8px
             calc(18px + env(safe-area-inset-left, 0px));
  }
  body.touch:not(.tablet) #stage-select-title { font-size: 15px; letter-spacing: 1px; }
  body.touch:not(.tablet) #stage-select-subtitle { font-size: 10.5px; margin-top: 1px; }
  body.touch:not(.tablet) #stage-select-grid {
    gap: 6px;
    padding: 6px
             calc(14px + env(safe-area-inset-right, 0px))
             calc(10px + env(safe-area-inset-bottom, 0px))
             calc(14px + env(safe-area-inset-left, 0px));
  }
  body.touch:not(.tablet) .stage-hero-card .stage-hero-media { width: 80px; }
  body.touch:not(.tablet) .stage-hero-card .stage-hero-body {
    padding: 6px 80px 6px 12px;
  }
  body.touch:not(.tablet) .stage-hero-card .stage-hero-name { font-size: 13px; }
  body.touch:not(.tablet) .stage-hero-card .stage-hero-diff { font-size: 9.5px; }
  body.touch:not(.tablet) .stage-hero-card .stage-hero-cta {
    padding: 5px 10px; font-size: 9.5px;
    right: 10px;
  }

  /* ── Settings ─────────────────────────────────────────────────────── */
  /* The 7vh top margin + 22px section gap eats most of the landscape
     viewport before content starts scrolling. Pull the card to the top
     edge and tighten section spacing so 4-5 sections fit without scroll. */
  #settings-modal .settings-card {
    margin: 0 auto;
    width: calc(100vw - 24px);
    max-width: 720px;
    max-height: 100vh;
    border-radius: 12px;
  }
  #settings-modal .settings-head {
    padding: calc(10px + env(safe-area-inset-top, 0px))
             calc(18px + env(safe-area-inset-right, 0px))
             10px
             calc(18px + env(safe-area-inset-left, 0px));
  }
  #settings-modal .settings-head h2 { font-size: 16px; letter-spacing: 1px; }
  #settings-modal .settings-body {
    padding: 10px
             calc(18px + env(safe-area-inset-right, 0px))
             14px
             calc(18px + env(safe-area-inset-left, 0px));
    gap: 12px;
  }
  #settings-modal .settings-section h3 {
    margin: 0 0 4px; font-size: 10.5px; letter-spacing: 1.3px;
  }
  #settings-modal .settings-row > label {
    min-width: 80px; font-size: 12px;
  }

  /* ── Leaderboard ──────────────────────────────────────────────────── */
  #leaderboard-modal .lb-card {
    height: 100vh;
    width: calc(100vw - 24px);
    padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
  }
  .lb-head {
    padding: calc(10px + env(safe-area-inset-top, 0px))
             calc(18px + env(safe-area-inset-right, 0px))
             8px
             calc(18px + env(safe-area-inset-left, 0px));
  }
  .lb-head h2 { font-size: 16px; letter-spacing: 1px; }
  .lb-period-tabs {
    padding: 6px
             calc(18px + env(safe-area-inset-right, 0px))
             4px
             calc(18px + env(safe-area-inset-left, 0px));
    gap: 6px;
  }
  .lb-period { padding: 5px 8px; font-size: 9.5px; letter-spacing: 0.8px; }

  /* ── Achievements ─────────────────────────────────────────────────── */
  #achievements-panel {
    max-height: 100vh;
    padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
  }
  #achievements-header {
    padding: calc(10px + env(safe-area-inset-top, 0px))
             calc(18px + env(safe-area-inset-right, 0px))
             8px
             calc(18px + env(safe-area-inset-left, 0px));
    gap: 12px;
  }
  #achievements-title { font-size: 16px; letter-spacing: 1px; }
  #achievements-subtitle { font-size: 10.5px; }
  #achievements-progress { padding: 3px 9px; font-size: 11px; }
  #achievements-close { font-size: 22px; }
  #achievements-body {
    padding: 8px
             calc(18px + env(safe-area-inset-right, 0px))
             12px
             calc(18px + env(safe-area-inset-left, 0px));
  }
  #achievements-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; }
  .achievement-card { padding: 10px 10px 8px; gap: 4px; }
  .achievement-card .ach-card-icon { width: 40px; height: 40px; margin-bottom: 2px; }
  .achievement-card .ach-card-name { font-size: 12.5px; letter-spacing: 0.2px; }
  .achievement-card .ach-card-desc { font-size: 11px; min-height: 0; line-height: 1.35; }
  .achievement-card .ach-card-foot { font-size: 9.5px; gap: 4px; }

  /* ── Wiki ────────────────────────────────────────────────────────── */
  .wiki-modal {
    padding: 10px
             calc(10px + env(safe-area-inset-right, 0px))
             10px
             calc(10px + env(safe-area-inset-left, 0px));
  }
  .wiki-panel { height: 100%; }
  .wiki-header {
    padding: calc(8px + env(safe-area-inset-top, 0px))
             18px 8px;
  }
  .wiki-title { font-size: 16px; letter-spacing: 1.6px; }
  .wiki-close { width: 28px; height: 28px; font-size: 16px; }
  /* Tighter columns: sidebar at 150px so "LIMIT BREAKS" stays one line;
     list 170px; detail 1fr soaks the remainder. */
  .wiki-cols { grid-template-columns: 150px 170px 1fr; }
  .wiki-sidebar { padding: 6px 5px; }
  /* Reduce letter-spacing too so multi-word category labels don't push
     past the 150px sidebar — "LIMIT BREAKS" was wrapping at 1.4px. */
  .wiki-cat-btn {
    padding: 6px 7px; font-size: 10.5px; letter-spacing: 0.8px;
    white-space: nowrap;
  }
  .wiki-list { padding: 6px 5px; }
  .wiki-list-item { padding: 6px 10px; font-size: 11.5px; }
  .wiki-detail { padding: 10px 14px 14px; }

  /* ── Shop tab — book theme mobile overrides ─────────────────────── */
  #shop-tab-header { display: none; }
  #relic-shop-section-head {
    padding: 16px
             calc(16px + env(safe-area-inset-right, 0px))
             4px
             calc(16px + env(safe-area-inset-left, 0px));
  }
  #relic-shop-section-title { font-size: 20px; }
  #relic-shop-section-subtitle { font-size: 12px; }
  #relic-shop-grid {
    grid-template-columns: repeat(auto-fill, minmax(115px, 1fr));
    gap: 12px 8px;
    padding: 8px
             calc(12px + env(safe-area-inset-right, 0px))
             8px
             calc(12px + env(safe-area-inset-left, 0px));
  }
  #limit-break-section {
    padding: 4px
             calc(18px + env(safe-area-inset-right, 0px))
             10px
             calc(18px + env(safe-area-inset-left, 0px));
    margin-top: 6px;
  }
  #limit-break-grid {
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
    gap: 8px;
  }
  .relic-card { padding: 8px 4px 4px; gap: 1px; }
  .relic-card-icon { width: 56px; height: 56px; }
  .relic-card-name { font-size: 15px; }
  .relic-card-desc { font-size: 10.5px; max-width: 120px; }
  .relic-card-tier { font-size: 11px; }
  .relic-card-buy { width: 56px; height: 56px; font-size: 12px; }

  /* ── IAP success popup — landscape rework ─────────────────────────────
     Portrait stacks vertically with a 370px hero image; in landscape
     that 370px IS the entire viewport height, pushing the title and
     button off-screen. Switch to a 2-column grid: image left (spans
     both rows), eyebrow row 1 + content row 2 on the right.

     Both grid rows are auto so the whole stack hugs its content height,
     and `align-content: center` then centers that stack vertically
     inside the card — visually balanced regardless of how much
     vertical room the system bars consume. */
  body.touch:not(.tablet) .iap-success-card {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-rows: auto auto;
    align-content: center; justify-content: center;
    align-items: center;
    column-gap: 4vw; row-gap: 6px;
    padding: calc(env(safe-area-inset-top, 0px) + 14px)
             calc(env(safe-area-inset-right, 0px) + 24px)
             calc(env(safe-area-inset-bottom, 0px) + 14px)
             calc(env(safe-area-inset-left, 0px) + 24px);
  }
  /* Image: spans both rows on the left. Sized in lvh (large viewport
     height) so when the system bars come/go the artwork stays the
     same size — dvh would resize and shift the whole grid, reading as
     "movement" to the user. The card itself is locked to lvh so the
     artwork-relative layout stays perfectly stable. */
  body.touch:not(.tablet) .iap-success-art-wrap {
    grid-column: 1; grid-row: 1 / 3;
    width: 75lvh; height: 75lvh;
    max-width: 50vw; max-height: 50vw;
    margin: 0;
    align-self: center;
  }
  body.touch:not(.tablet) .iap-success-art {
    width: 80%; height: 80%;
    max-width: none; max-height: none;
  }
  body.touch:not(.tablet) .iap-success-flare,
  body.touch:not(.tablet) .iap-success-rays {
    width: 130%; height: 130%;
    max-width: none; max-height: none;
  }
  /* Right column row 1: eyebrow. Compact on landscape — no flank
     hairlines (they'd waste horizontal budget on the narrow column). */
  body.touch:not(.tablet) .iap-success-eyebrow {
    grid-column: 2; grid-row: 1;
    font-size: 26px; letter-spacing: 2.5px;
    margin: 0;
    text-align: center;
    align-self: end;
  }
  body.touch:not(.tablet) .iap-success-eyebrow::before,
  body.touch:not(.tablet) .iap-success-eyebrow::after { display: none; }
  /* Right column row 2: content stack (count → title → blurb → button). */
  body.touch:not(.tablet) .iap-success-content {
    grid-column: 2; grid-row: 2;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    text-align: center;
    align-self: start;
  }
  body.touch:not(.tablet) .iap-success-amount { font-size: 30px; margin-bottom: 4px; }
  body.touch:not(.tablet) .iap-success-title { font-size: 16px; margin-bottom: 4px; }
  body.touch:not(.tablet) .iap-success-blurb {
    font-size: 12.5px; margin-bottom: 14px;
    max-width: 320px;
  }
  body.touch:not(.tablet) .iap-success-dismiss {
    padding: 13px 24px; font-size: 13.5px;
    max-width: 260px; width: 100%;
  }
}

/* ─── Capacitor Android: bottom system-nav fade ────────────────────────────
   When the OEM (Honor MagicOS, etc.) keeps the 3-button nav bar visible
   despite our SystemBars.hide() request, MainActivity now paints the
   nav-bar background TRANSPARENT and the WebView extends under it. This
   ::after pseudo-element draws a gradient that fades in from transparent
   at the top to a translucent dark at the bottom — so the system nav
   icons sit on a soft fade rather than the previous opaque strip.
   --safe-area-inset-bottom is set by the Capacitor 8 SystemBars plugin
   to match the actual nav-bar height; falls back to ~32px if absent.
   z-index sits between canvas (~0) and the in-game HUD (~9) so the HUD
   stays crisp on top. */
html.flavor-android body::after {
  content: '';
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  /* Bleed the gradient ABOVE the nav-bar inset by ~50px so the dark
     blend doesn't start abruptly at the inset's upper edge. */
  height: calc(var(--safe-area-inset-bottom, 32px) + 50px);
  background: linear-gradient(
    to bottom,
    rgba(0, 0, 0, 0) 0%,
    rgba(0, 0, 0, 0.18) 35%,
    rgba(0, 0, 0, 0.55) 100%
  );
  pointer-events: none;
  z-index: 5;
}
/* ─── Capacitor Android: TOP system-status fade ─────────────────────────────
   Mirror of the bottom fade above. MainActivity now forces light
   (white) status-bar icons + transparent status-bar bg, so the WebView
   paints under the bar. To guarantee the clock/battery/notif icons
   stay legible against the colourful landing scene we draw a
   transparent-to-translucent-dark gradient over the top edge. The
   gradient bottom fades fully to transparent so the page content
   below is undisturbed; the top edge sits behind the icons at ~50%
   black so even the brightest pixel below reads as dim under them. */
html.flavor-android body::before {
  content: '';
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  /* Bleed BELOW the status-bar inset by ~28px so the dark blend doesn't
     stop abruptly at the inset edge. --safe-area-inset-top is set by
     Capacitor 8 SystemBars; ~32px fallback for non-Capacitor contexts. */
  height: calc(var(--safe-area-inset-top, 32px) + 28px);
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0) 0%,
    rgba(0, 0, 0, 0.22) 45%,
    rgba(0, 0, 0, 0.55) 100%
  );
  pointer-events: none;
  z-index: 5;
}


/* ─── REDUCED-MOTION SAFETY NET ─────────────────────────────────────────────
   Catches the long tail of decorative @keyframes animations + transitions
   that don't have explicit per-element prefers-reduced-motion gates. The
   audit found ~149 animation references but only ~19 explicit reduced-
   motion media queries — most decorative motion (button pulses, toast
   slide-ins, panel entrance animations, photon warps, shimmer effects)
   was leaking through to motion-sensitive users.

   Strategy: cap duration to 0.01ms rather than `animation: none`. This
   preserves end-state values (XP bar fills to its final width, level chip
   reaches its final color, modal opacity hits 1) while removing the
   actual motion. The `iteration-count: 1` gate stops infinite animations
   like the photon warp from re-running.

   `scroll-behavior: auto` catches CSS scroll-snap / smooth-scroll on
   modal lists.

   Per-element rules earlier in this file (e.g. landing-image-bg,
   etc.) still take precedence via specificity or later position —
   those already chose `none` semantics intentionally.
*/
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration:        0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration:       0.01ms !important;
    scroll-behavior:           auto !important;
  }
}

/* ══════════════════════════════════════════════════════════════════
   INVENTORY TAB — single-scroll list (2026-05-18 redesign)
   Replaces the slot-paged binder + trading-card flip view. Always-
   visible info, inline buttons, sections grouped by slot. Reads
   metaBank, never writes directly.
   ══════════════════════════════════════════════════════════════════ */

.play-hub-panel[data-tab="inventory"] .inv-binder {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 14px 14px 24px;
  /* Parent .play-hub-panel has `overflow: hidden` (line 2597) so the binder
     must own its own scroll. Without `min-height: 0` flex-children refuse
     to shrink past their content height — required so the inner scroll
     actually clips at the panel boundary instead of overflowing it. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}
.inv-loading {
  color: #8e93ad;
  text-align: center;
  padding: 40px 0;
}

/* First-visit hint */
.inv-hint {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  /* rc159 — opaque hint banner. */
  background: #3d2c0e;
  border: 1px solid #ffd766;
  border-radius: 10px;
  color: #f4e9c9;
  font-size: 12px;
  letter-spacing: 0.02em;
}
.inv-hint-icon { font-size: 14px; }


/* ───────────────────────────────────────────────────────────────────────────
   SHOP "UPGRADES" BANNER (goal item 7)
   Mirror the STAGES chapter-ribbon design: the "UPGRADES" heading sits INSIDE
   the same chapter-ribbon.png banner, centred on the page, in every aspect
   ratio. responsive.css loads after wiki.css, so these #stage-select-scoped
   (2-id) rules win source-order over wiki.css's per-breakpoint shop header
   rules without needing !important.
   ─────────────────────────────────────────────────────────────────────────── */
#stage-select #relic-shop-section-head {
  width: 100%;
  max-width: none;
  margin: 0 auto;
  /* Clear the absolute play-hub HUD so the UPGRADES ribbon never slides under
     it. The HUD is ~58px on desktop / ~46px on touch and already carries the
     safe-area inset; matching that inset here keeps a steady ~12px gap below
     the HUD on every device (notched phones shift both down together). */
  padding: calc(70px + env(safe-area-inset-top, 0px)) 16px 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-sizing: border-box;
}
body.touch:not(.tablet) #stage-select #relic-shop-section-head {
  padding-top: calc(58px + env(safe-area-inset-top, 0px));
}
#stage-select #relic-shop-section-title {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: min(460px, 78%);
  height: clamp(44px, 8vh, 72px);
  /* bottom padding lifts the label onto the ribbon's writing band, same trick
     the stages chapter pill uses */
  padding: 0 clamp(28px, 8%, 56px) 12px;
  background: url('../assets/stage-select/figma/chapter-ribbon.png') center / 100% 100% no-repeat;
  font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-weight: 700;
  font-size: clamp(14px, 2.2vh, 21px);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #542e1f;
  text-shadow: 0 1px 0 rgba(255, 232, 182, 0.45);
  text-align: center;
  white-space: nowrap;
}
#stage-select #relic-shop-section-subtitle {
  display: block;
  text-align: center;
  font-size: clamp(10px, 1.5vh, 13px);
  color: #6f5c47;
  margin-top: 6px;
}
