  /* ── IAP buy modal (Relic Vault) ────────────────────────────────────
     Six SKU tiles in a symmetric 2×3 grid. Visual hierarchy:
       Row 1 — T1 (500),     T2 (3,000)
       Row 2 — T3 (7,000),   T4 (16,000) ★ MOST POPULAR
       Row 3 — T5 (50,000),  T6 (150,000) ★ BEST VALUE
     T4 and T6 carry distinct treatments (gold ribbon / inline pill +
     warm aura) so the "value bracket" reads on first scan without
     forcing one tile to span columns.

     Layout vocabulary:
       .iap-card        outer frame
       .iap-head        title + balance + close (one row)
       #iap-tiles       2×3 grid container (3×2 in landscape)
       .iap-tile        a single SKU button (always tappable; sign-in
                        check happens on tap, see _showSignInRequired)
         .iap-tile-art    artwork box w/ soft glow halo
         .iap-tile-amount  big relic count
         .iap-tile-price   bottom-pinned price chip
       .iap-tile-popular T4 — adds a "MOST POPULAR" ribbon at top
       .iap-tile-best    T6 — adds a "{n}% MORE" ribbon at top
       .iap-tile-ribbon  the ribbon itself, shared between T4 + T6
  */
  #iap-modal {
    position: fixed; inset: 0;
    display: none; align-items: center; justify-content: center;
    z-index: 280;  /* above relic-shop (250) */
    padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom));
  }
  #iap-modal.visible { display: flex; }
  .iap-backdrop {
    position: absolute; inset: 0;
    /* rc159 — opaque modal backdrop (was rgba(8,6,24,0.86) over scene). */
    background: var(--panel-bg-2, #0e1426);
  }
  .iap-card {
    position: relative;
    width: min(460px, 96vw);
    max-height: 92vh;
    overflow-y: auto;
    /* rc159 — opaque IAP card. */
    background: #221c40;
    border: 1px solid var(--lilac);
    border-radius: 20px;
    box-shadow:
      0 30px 90px rgba(0, 0, 0, 0.65),
      0 0 0 1px var(--lilac) inset,
      0 0 80px rgba(196, 124, 255, 0.25);
    padding: 18px 18px 14px;
  }

  /* Head row: title (flex-grow), balance pill, close X. */
  .iap-head {
    display: flex; align-items: center; gap: 10px;
    margin-bottom: 14px;
  }
  .iap-head h2 {
    flex: 1;
    font-size: 19px; font-weight: 800; letter-spacing: 0.6px;
    margin: 0; color: var(--text);
  }
  .iap-head-balance {
    display: inline-flex; align-items: baseline; gap: 5px;
    padding: 5px 12px;
    /* rc159 — opaque relic balance pill. */
    background: #243a55;
    border: 1px solid #7ee2ff;
    border-radius: var(--radius-pill);
    color: #d8f4ff;
    font-size: 13px; font-weight: 800;
    letter-spacing: 0.3px;
    line-height: 1;
    white-space: nowrap;
  }
  /* Inline "RELICS" unit label — small, dim, sits to the right of the
     count so the pill reads as "50 RELICS" rather than just "50". */
  .iap-head-balance-label {
    font-size: 9px; font-weight: 700;
    letter-spacing: 1.2px;
    color: #9bb2d8;
  }
  .iap-close {
    background: none; border: none; color: var(--text-dim);
    font-size: 26px; line-height: 1; cursor: pointer;
    padding: 0 4px; font-family: inherit;
    transition: color 0.15s, transform 0.15s;
  }
  .iap-close:hover { color: var(--text); transform: scale(1.1); }

  /* Sign-in spinner shared by .settings-cta. The earlier IAP gate also
     used these rules, but the sign-in step now happens in the
     #iap-signin-required-modal popup (whose loading state has its own
     selectors below in that block). */
  .settings-cta.is-loading {
    position: relative;
    color: transparent !important;
    pointer-events: none;
  }
  .settings-cta.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;
  }
  .settings-cta.is-loading::after {
    border-color: rgba(255, 255, 255, 0.20);
    border-top-color: rgba(255, 255, 255, 0.85);
  }

  /* Product-load diagnostic. Spans both columns of the tile grid so the
     warning sits as a full-width banner, not as a cell that pushes the
     SKU layout out of symmetry. */
  .iap-diag {
    grid-column: 1 / -1;
    padding: 8px 12px;
    margin: 0 0 4px;
    /* rc159 — opaque warning banner. */
    background: #3a2a14;
    border: 1px solid #ffb454;
    border-radius: 8px;
    color: #ffd699;
    font-size: 11.5px; line-height: 1.4;
  }

  /* ── Grid + base tile ── */
  #iap-tiles {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }
  /* Landscape: 6 tiles arrange as 3×2 instead of 2×3. Modal widens so
     each tile keeps comfortable breathing room. With the relic-count
     anchored to card bottom (`margin-top: auto` on .iap-tile-amount),
     row-mates with different art sizes still align horizontally —
     so T1/T2/T3 line up in row 1 and T4/T5/T6 line up in row 2 even
     though the bottom row carries 100px (T4) and 135px (T5/T6) art. */
  body.landscape #iap-tiles {
    grid-template-columns: repeat(3, 1fr);
    gap: 12px;
  }
  body.landscape .iap-card {
    width: min(680px, 96vw);
    max-height: 96vh;
  }
  .iap-tile {
    position: relative;
    display: flex; flex-direction: column;
    align-items: center; text-align: center;
    gap: 6px;
    /* padding-top is uniformly 16px across ALL tiles so the artwork +
       relic count line up horizontally between row-mates, even when
       only one tile in the row carries a top ribbon (T4 has MOST
       POPULAR; T6 has 200% MORE; their row-mates T3/T5 do not). */
    padding: 16px 10px 10px;
    background:
      linear-gradient(180deg, rgba(126, 226, 255, 0.08) 0%, transparent 35%),
      linear-gradient(180deg, rgba(28, 24, 56, 0.85), rgba(18, 16, 40, 0.85));
    border: 1px solid rgba(126, 226, 255, 0.22);
    border-radius: 14px;
    color: var(--text);
    font: inherit;
    cursor: pointer;
    overflow: visible;  /* allow ribbon + best pill to bleed */
    min-height: 200px;
    transition: transform 0.15s ease, filter 0.15s ease, box-shadow 0.18s ease, border-color 0.15s;
    /* Entrance: fade-up. style="animation-delay:Nms" stamped per-tile in JS. */
    animation: iap-tile-in 380ms ease-out both;
  }
  .iap-tile:hover:not(:disabled) {
    transform: translateY(-2px);
    border-color: rgba(126, 226, 255, 0.50);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.40), 0 0 22px rgba(126, 226, 255, 0.18);
  }
  .iap-tile:active:not(:disabled) {
    transform: translateY(0) scale(0.97);
    filter: brightness(1.08);
  }
  .iap-tile:disabled { opacity: 0.55; cursor: progress; }
  /* Keyboard focus ring — visible only for keyboard navigation, not
     mouse/touch presses. Matches the modal's cyan accent so it reads
     as part of the design. Defaults to `outline` so it doesn't fight
     with the per-tier border colors. */
  .iap-tile:focus-visible,
  .iap-close:focus-visible {
    outline: 2px solid rgba(126, 226, 255, 0.85);
    outline-offset: 3px;
  }

  /* Artwork box — soft radial glow behind the bespoke per-SKU image so
     the gem/chest reads against the dark card background. */
  .iap-tile-art {
    position: relative;
    display: flex; align-items: center; justify-content: center;
    width: 80px; height: 80px;
    flex-shrink: 0;
    margin-top: 2px;
  }
  .iap-tile-glow {
    position: absolute;
    inset: -6px;
    background: radial-gradient(circle, rgba(126, 226, 255, 0.32) 0%, transparent 65%);
    filter: blur(2px);
    pointer-events: none;
    z-index: 0;
  }
  .iap-tile-img {
    position: relative;
    z-index: 1;
    width: 100%; height: 100%;
    display: block;
    object-fit: contain;
    filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.55));
    user-select: none;
    -webkit-user-drag: none;
    pointer-events: none;
  }

  /* Headline relic count + "RELICS" unit label, stacked.
     `margin-top: auto` on the wrapper anchors this block + the price
     chip to the BOTTOM of the card. Art floats at the top; vertical
     slack expands above. So row-mates with different art sizes still
     align horizontally on their relic counts. */
  .iap-tile-amount {
    display: flex; flex-direction: column; align-items: center;
    gap: 1px;
    margin-top: auto;
    line-height: 1;
  }
  /* Big number — gradient white-to-blue with soft cyan glow. */
  .iap-tile-amount-num {
    font-size: 22px; font-weight: 900;
    letter-spacing: 0.3px;
    background: linear-gradient(180deg, #ffffff 0%, #d8ecff 100%);
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
            color: transparent;
    text-shadow: 0 0 14px rgba(126, 226, 255, 0.32);
  }
  /* Small "RELICS" unit label sits a hair below the count. Quiet so
     the count stays the dominant element. */
  .iap-tile-amount-label {
    font-size: 9px; font-weight: 700;
    letter-spacing: 1.4px;
    color: rgba(220, 230, 255, 0.55);
    text-transform: uppercase;
  }

  /* Price chip — full-width BUY action. Bottom-pinning is handled by
     the `margin-top: auto` on .iap-tile-amount, which floats the entire
     text block to the card bottom. */
  .iap-tile-price {
    width: 100%;
    padding: 9px 12px;
    /* rc159 — opaque price chip. */
    background: #2a6f95;
    border: 1px solid #7ee2ff;
    border-radius: var(--radius-pill);
    color: #ecfaff;
    font-size: 13px; font-weight: 800;
    letter-spacing: 0.3px;
    white-space: nowrap;
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.20),
      0 4px 12px rgba(92, 212, 255, 0.35);
    text-shadow: 0 0 8px rgba(126, 226, 255, 0.45);
  }

  /* ── Tier-specific accents ── */
  .iap-tile-t3 {
    border-color: #7ee2ff;
    background: #1c1c40;
  }
  .iap-tile-t5 {
    border-color: var(--lilac);
    background: #221638;
  }
  .iap-tile-t5 .iap-tile-price {
    /* rc159 — opaque T5 price chip. */
    background: #5e3aa8;
    border-color: var(--lilac);
    color: #f4ebff;
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.22),
      0 4px 14px rgba(196, 124, 255, 0.45);
    text-shadow: 0 0 8px rgba(184, 155, 255, 0.55);
  }

  /* T4 — MOST POPULAR. Gold-ringed card with a subtle outlined ribbon at
     the top edge (same position as T6's BEST VALUE ribbon, but quieter
     fill so the visual hierarchy still puts T6 above it). padding-top
     leaves room for the ribbon overlap. */
  .iap-tile-popular {
    border-color: #ffd766;
    /* rc159 — opaque popular tile. */
    background: #2a2238;
    box-shadow:
      0 0 0 1px #ffd766 inset,
      0 6px 22px rgba(0, 0, 0, 0.45),
      0 0 24px rgba(255, 180, 84, 0.35);
  }

  /* Top-edge ribbon, shared between T4 (MOST POPULAR) and T6 (200%
     MORE). Both use the same subtle muted-gold pill — small footprint,
     low saturation, soft shadow. The DIFFERENT label is what
     distinguishes them: T4 says "popular", T6 says "value math".
     Bleeds 6px above the card border so it reads as a stamped award
     rather than a header row. */
  .iap-tile-ribbon {
    position: absolute;
    top: -7px; left: 50%;
    transform: translateX(-50%);
    padding: 2px 9px;
    background: linear-gradient(180deg, rgba(255, 224, 138, 0.75) 0%, rgba(255, 180, 84, 0.75) 60%, rgba(217, 149, 40, 0.75) 100%);
    border: 1px solid rgba(140, 86, 18, 0.42);
    border-radius: var(--radius-pill);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.30),
      0 2px 6px rgba(0, 0, 0, 0.35);
    z-index: 2;
    white-space: nowrap;
  }
  .iap-tile-ribbon span {
    font-size: 9px; font-weight: 800;
    letter-spacing: 1.0px;
    text-transform: uppercase;
    color: #2a1a08;
  }

  /* T6 — top-tier "vault" pack. Warm gold aura + gold price chip + the
     prominent BEST VALUE ribbon at the top together carry the premium
     signal. padding-top makes room for the ribbon overlap. */
  .iap-tile-best {
    border-color: #ffd766;
    /* rc159 — opaque best-value tile. */
    background: #2e2040;
    box-shadow:
      0 0 0 1px #ffd766 inset,
      0 6px 22px rgba(0, 0, 0, 0.55),
      0 0 32px rgba(255, 180, 84, 0.45);
  }
  .iap-tile-best .iap-tile-amount-num {
    background: linear-gradient(180deg, #fff7d6 0%, #ffd45a 100%);
    -webkit-background-clip: text;
            background-clip: text;
    -webkit-text-fill-color: transparent;
            color: transparent;
    text-shadow: 0 0 14px rgba(255, 180, 84, 0.42);
  }
  .iap-tile-best .iap-tile-amount-label {
    color: #ffd766;
  }
  .iap-tile-best .iap-tile-price {
    background: linear-gradient(180deg, #ffe088 0%, #ffb454 60%, #e8941a 100%);
    border-color: rgba(120, 76, 22, 0.55);
    color: #2a1a08;
    text-shadow: 0 1px 0 rgba(255, 240, 200, 0.55);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.45),
      0 4px 12px rgba(255, 180, 84, 0.40);
  }

  /* T4 (POPULAR): 80 → 100px. T5/T6: 80 → 135px. T3 stays at default
     80px so it visibly steps below T4. Cross-row alignment is handled
     by the layout (amount/meta/price anchor to card bottom — see the
     `margin-top: auto` on .iap-tile-amount above), so T3's smaller
     art doesn't shift its relic count out of line with T4's. */
  .iap-tile-t4 .iap-tile-art {
    width: 100px; height: 100px;
  }
  .iap-tile-t5 .iap-tile-art,
  .iap-tile-t6 .iap-tile-art {
    width: 135px; height: 135px;
    margin-top: 4px;
  }

  /* Per-tier glow halos behind the artwork.
     T4 + T6 carry an animated golden glow — the two tiers we're actively
     trying to convert. T5 keeps a static lilac glow so it reads as
     premium-but-not-spotlit. The animation pulses the radial gradient
     opacity + scale slowly (~2.4s) so the eye is drawn without the card
     feeling jittery. */
  .iap-tile-t4 .iap-tile-glow {
    inset: -7px;
    background: radial-gradient(circle, rgba(255, 215, 102, 0.34) 0%, transparent 65%);
    animation: iap-gold-glow 2.4s ease-in-out infinite;
  }
  .iap-tile-t5 .iap-tile-glow {
    inset: -10px;
    background: radial-gradient(circle, rgba(184, 155, 255, 0.30) 0%, transparent 65%);
  }
  .iap-tile-t6 .iap-tile-glow {
    inset: -10px;
    background: radial-gradient(circle, rgba(255, 215, 102, 0.38) 0%, transparent 65%);
    animation: iap-gold-glow 2.4s ease-in-out infinite;
    /* Slight phase offset so T4 and T6 don't pulse in perfect sync —
       reads as life rather than a mechanical strobe across the modal. */
    animation-delay: -1.2s;
  }
  @keyframes iap-gold-glow {
    0%, 100% { opacity: 0.85; transform: scale(1.00); filter: blur(2px); }
    50%      { opacity: 1.00; transform: scale(1.10); filter: blur(4px); }
  }
  @media (prefers-reduced-motion: reduce) {
    .iap-tile-t4 .iap-tile-glow,
    .iap-tile-t6 .iap-tile-glow { animation: none; }
  }

  @keyframes iap-tile-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  @media (prefers-reduced-motion: reduce) {
    .iap-tile { animation: none; }
  }
