// shop-pieces.jsx — page-specific primitives for the Shop module.
//
// Design Log §16.5 (Shop module proposal) + Phase 0 inventory (chat v1.5).
// Token-only CSS — every color/spacing comes from design-system/tokens.css
// or the SHOP_RARITY palette (which itself maps to tokens). All chrome
// primitives (AppBar / BottomNav / SectionHead / StatTile / FilterChips /
// HHEmptyState / TeamCrest / MyCardsDetailModal) come from window via the
// canonical baseline §12 — this file only defines the page-specific bits.
//
// Primitives defined here:
//   <PriceTag>            — currency display (coin glyph + value + optional
//                           strikethrough). Variants: 'inline' | 'large'.
//   <RarityBadge>         — small chip with rarity dot + label.
//   <PackCard>            — the main grid cell. State-aware ribbon overlay
//                           (available/owned/locked/comingSoon), rarity-
//                           tinted art, optional badge + savings strike.
//   <BundleCard>          — multi-pack card with stacked mini-marks +
//                           savings chip.
//   <GiftCodeRow>         — gift code input + redeem CTA, plus rows for
//                           already-redeemed/expired codes.
//   <PackOpenScene>       — full-screen pack-open animation overlay
//                           (4 stages: flash → idle → reveal → summary).
//                           Defined separately in Phase 4 of this sprint.
//
// All CSS lives at the bottom of this file (prefix `sh-`).

const { useState: shUseState, useEffect: shUseEffect, useRef: shUseRef,
        useMemo: shUseMemo, useCallback: shUseCallback } = React;

const _shT = (n, locale) => (window.toLocaleDigits
  ? window.toLocaleDigits(n, locale) : String(n));
const _shHHIcon = (props) => (window.HHIcon
  ? React.createElement(window.HHIcon, props) : null);

// ─── PriceTag ──────────────────────────────────────────────────────────────
// A currency display. By default renders the coin glyph + the value with a
// tabular-numbers font. When `wasPrice` is set, the old price is shown
// strikethrough above the current price (sale signal). `variant='large'`
// is for sheet headers; default `inline` is for pack cards / row meta.
function PriceTag({ locale = 'fa', value = 0, wasPrice = null,
                    isFree = false, variant = 'inline', tone = 'gold' }) {
  const ll = window.SHOP_I18N ? window.SHOP_I18N[locale] : {};
  if (isFree) {
    return (
      <span className={`sh-price sh-price--${variant} sh-price--free`}>
        <span className="sh-price-value">{ll.priceFree || ll.badgeFree || 'Free'}</span>
      </span>
    );
  }
  const onSale = wasPrice != null && wasPrice > value;
  const coinSize = variant === 'large' ? 16 : 12;
  const fmt = (n) => _shT(n.toLocaleString('en-US'), locale);

  if (onSale) {
    return (
      <span className={`sh-price sh-price--${variant} sh-price--${tone} sh-price--sale`}
            title={`${fmt(wasPrice)} → ${fmt(value)}`}>
        <span className="sh-price-was tabular-nums">{fmt(wasPrice)}</span>
        <span className="sh-price-now">
          <span className="sh-price-icon" aria-hidden="true">
            {_shHHIcon({ name: 'coin', size: coinSize })}
          </span>
          <span className="sh-price-value tabular-nums">{fmt(value)}</span>
        </span>
      </span>
    );
  }

  return (
    <span className={`sh-price sh-price--${variant} sh-price--${tone}`}>
      <span className="sh-price-icon" aria-hidden="true">
        {_shHHIcon({ name: 'coin', size: coinSize })}
      </span>
      <span className="sh-price-value tabular-nums">{fmt(value)}</span>
    </span>
  );
}

// ─── RarityBadge ───────────────────────────────────────────────────────────
// Small chip with rarity dot + localized rarity label. Color comes from the
// SHOP_RARITY palette (tokens only). When `dotOnly` is true the label is
// hidden — useful for compact card corners.
function RarityBadge({ locale = 'fa', rarity = 'common', dotOnly = false }) {
  const ll = window.SHOP_I18N ? window.SHOP_I18N[locale] : {};
  const palette = (window.SHOP_RARITY || {})[rarity] || {};
  const labelKey = `rarity${rarity.charAt(0).toUpperCase()}${rarity.slice(1)}`;
  const label = ll[labelKey] || rarity;
  return (
    <span className={`sh-rarity sh-rarity--${rarity} ${dotOnly ? 'is-dot' : ''}`}
          style={{ '--rarity-accent': palette.accent, '--rarity-glow': palette.glow }}>
      <span className="sh-rarity-dot" aria-hidden="true"></span>
      {!dotOnly && <span className="sh-rarity-label">{label}</span>}
    </span>
  );
}

// ─── PackBadge (internal) ─────────────────────────────────────────────────
// Top-corner badge that surfaces sale / new / hot / limited / free states.
// Maps a SHOP_PACKS.badge value to a localized label + visual tone.
function PackBadge({ locale = 'fa', kind }) {
  if (!kind) return null;
  const ll = window.SHOP_I18N ? window.SHOP_I18N[locale] : {};
  const map = {
    new:     { label: ll.badgeNew,     tone: 'info'    },
    hot:     { label: ll.badgeHot,     tone: 'danger'  },
    sale:    { label: ll.badgeSale,    tone: 'warning' },
    limited: { label: ll.badgeLimited, tone: 'accent'  },
    free:    { label: ll.badgeFree,    tone: 'success' },
  };
  const m = map[kind];
  if (!m) return null;
  return (
    <span className={`sh-badge sh-badge--${m.tone}`}>
      <span>{m.label}</span>
    </span>
  );
}

// ─── PackArt (internal) ────────────────────────────────────────────────────
// The visual "pack mark" on top of each PackCard. Recipe is intentionally
// CSS glyph fallback when no coverImage; with assets, photo + colored halo.
// The shape is a tilted rounded rectangle that suggests a card pack
// (spine on the inline-start, "face" with a centered glyph, a top shine).
// Rarity drives the surface gradient + the glyph color. For team-themed
// packs, `themeTeam.color` overrides the accent so the pack looks
// brand-tinted (e.g. red for Persepolis).
function PackArt({ rarity, accent, themeTeam, artStyle = 'tinted', coverImage }) {
  const palette = (window.SHOP_RARITY || {})[rarity] || {};
  const surfaceFrom = palette.surfaceFrom || 'rgba(255,255,255,0.12)';
  const surfaceTo   = palette.surfaceTo   || 'rgba(255,255,255,0.04)';
  const finalAccent = accent || palette.accent;
  const hasCover = !!coverImage;
  // CSS-only glyph when no cover asset (e.g. pk-spring)
  const glyph = !hasCover && artStyle === 'team' && themeTeam
    ? (window.TeamCrest
        ? React.createElement(window.TeamCrest, { team: { code: themeTeam.code, color: themeTeam.color, name: { fa: '', en: '', ar: '' } }, size: 44, layout: 'inline' })
        : null)
    : !hasCover
      ? _shHHIcon({ name: artStyle === 'gold' ? 'sparkle' : 'gift', size: 28 })
      : null;
  return (
    <div className={`sh-pack-art sh-pack-art--${artStyle}${hasCover ? ' has-cover' : ''}`}
         style={{ '--pack-accent': finalAccent,
                  '--pack-surface-from': surfaceFrom,
                  '--pack-surface-to':   surfaceTo }}>
      {hasCover && (
        <div className="sh-pack-art-photo" aria-hidden="true">
          <img src={coverImage} alt="" decoding="async" loading="lazy"/>
        </div>
      )}
      {hasCover && <div className="sh-pack-art-halo" aria-hidden="true"></div>}
      {!hasCover && <div className="sh-pack-art-spine" aria-hidden="true"></div>}
      {!hasCover && (
        <div className="sh-pack-art-face" aria-hidden="true">{glyph}</div>
      )}
      <div className="sh-pack-art-shine" aria-hidden="true"></div>
      <div className="sh-pack-art-glow"  aria-hidden="true"></div>
    </div>
  );
}

// ─── PackCard ──────────────────────────────────────────────────────────────
// The main grid cell. State-aware ribbon overlays handle:
//   • available  → default CTA "Buy" with price
//   • owned      → "Open" CTA (or "Owned" pill if not free + already bought)
//   • locked     → unlockHint chip + grayed art
//   • comingSoon → blurred art + "Soon" pill
//
// Behavior:
//   • Tap anywhere on the card (except the CTA) opens the detail sheet.
//   • Tap the CTA goes straight to the purchase / open-pack flow.
//
// Props:
//   pack    — SHOP_PACKS entry shape
//   locale  — fa/en/ar
//   onOpenDetails(pack)  — open the bottom sheet
//   onBuy(pack)          — direct purchase (skipping sheet)
//   user    — { coinBalance, level } — used to render the right CTA
//   compact — boolean   → shorter art height, single column tagline (mobile)
function PackCard({ pack, locale = 'fa', onOpenDetails, onBuy,
                   user, compact = false }) {
  if (!pack) return null;
  const ll = window.SHOP_I18N ? window.SHOP_I18N[locale] : {};
  const isOwned   = pack.state === 'owned';
  const isLocked  = pack.state === 'locked';
  const isComing  = pack.state === 'comingSoon';
  const canBuyFn  = window.canBuyPack;
  const buyState  = (canBuyFn && user) ? canBuyFn(pack, user) : { ok: !isLocked && !isComing };

  // CTA label
  const ctaLabel = (() => {
    if (isLocked && pack.unlockHint) return ll.btnLocked(pack.unlockHint[locale]);
    if (isComing)                   return ll.btnComingSoon;
    if (pack.isFree && isOwned)     return ll.btnOpenFree;
    if (isOwned && !pack.isFree)    return ll.btnOwned;
    if (pack.isFree)                return ll.btnOpenFree;
    return ll.btnDetails;
  })();

  const handleCardTap = (e) => {
    if (e.target.closest('.sh-pack-cta') || e.target.closest('.sh-pack-cta-stub')) return;
    if (isLocked || isComing) return;
    if (onOpenDetails) onOpenDetails(pack);
  };

  const handleCta = (e) => {
    e.stopPropagation();
    if (isLocked || isComing) return;
    if (isOwned && !pack.isFree) { if (onOpenDetails) onOpenDetails(pack); return; }
    if (onOpenDetails) onOpenDetails(pack);  // always open sheet — buy lives there
  };

  return (
    <article className={`sh-pack ${compact ? 'sh-pack--compact' : ''}
                         sh-pack--${pack.rarity}
                         ${isLocked ? 'is-locked' : ''}
                         ${isComing ? 'is-coming' : ''}
                         ${isOwned  ? 'is-owned'  : ''}`}
             onClick={handleCardTap}
             role="button"
             tabIndex={isLocked || isComing ? -1 : 0}
             aria-label={`${pack.name[locale]} — ${pack.tagline[locale]}`}>
      <div className="sh-pack-top">
        <PackArt rarity={pack.rarity}
                 accent={pack.accent}
                 themeTeam={pack.themeTeam}
                 artStyle={pack.artStyle}
                 coverImage={pack.coverImage}/>
        {pack.badge && (
          <div className="sh-pack-badge-wrap">
            <PackBadge locale={locale} kind={pack.badge}/>
          </div>
        )}
        <div className="sh-pack-rarity-wrap">
          <RarityBadge locale={locale} rarity={pack.rarity}/>
        </div>
        {isLocked && (
          <div className="sh-pack-state-overlay">
            {_shHHIcon({ name: 'lock', size: 22 })}
          </div>
        )}
        {isComing && (
          <div className="sh-pack-state-overlay sh-pack-state-overlay--coming">
            {_shHHIcon({ name: 'clock', size: 22 })}
          </div>
        )}
      </div>
      <div className="sh-pack-body">
        <h3 className="sh-pack-name">{pack.name[locale]}</h3>
        <p className="sh-pack-tagline">{pack.tagline[locale]}</p>
        <div className="sh-pack-meta">
          <span className="sh-pack-meta-item">
            {_shHHIcon({ name: 'cards', size: 11 })}
            <span>{ll.cardCount(_shT(pack.cardCount, locale))}</span>
          </span>
          {pack.guaranteedTier && (
            <span className="sh-pack-meta-item sh-pack-meta-item--guaranteed">
              {_shHHIcon({ name: 'check', size: 11 })}
              <span>{ll.guaranteedTier(
                pack.guaranteedTier === 'gold'     ? ll.tierGold     :
                pack.guaranteedTier === 'platinum' ? ll.tierPlatinum :
                pack.guaranteedTier
              )}</span>
            </span>
          )}
        </div>
        <div className="sh-pack-foot">
          {!isLocked && !isComing && (
            <PriceTag locale={locale}
                      value={pack.price}
                      wasPrice={pack.priceWas}
                      isFree={pack.isFree}
                      tone={pack.rarity === 'mythic' ? 'accent' : 'gold'}/>
          )}
          <button type="button"
                  className={`sh-pack-cta
                              ${isOwned && !pack.isFree ? 'sh-pack-cta--ghost' : ''}
                              ${isLocked || isComing ? 'sh-pack-cta--disabled' : ''}`}
                  disabled={isLocked || isComing}
                  onClick={handleCta}>
            <span>{ctaLabel}</span>
            {!isLocked && !isComing && _shHHIcon({ name: 'chevron', size: 12 })}
          </button>
        </div>
      </div>
    </article>
  );
}

// ─── BundleCard ────────────────────────────────────────────────────────────
// Renders a bundle (group of packs sold at a discounted total). Anatomy:
//   • Header chip: "BUNDLE · save N%"
//   • Body: name + tagline + stacked mini pack-marks (1 per packId)
//   • Foot: total price + buy CTA
function BundleCard({ bundle, packs, locale = 'fa', onOpenDetails }) {
  if (!bundle) return null;
  const ll = window.SHOP_I18N ? window.SHOP_I18N[locale] : {};
  const memberPacks = bundle.packIds.map((id) => packs.find((p) => p.id === id)).filter(Boolean);
  const originalTotal = memberPacks.reduce((s, p) => s + (p.price || 0), 0);

  return (
    <article className="sh-bundle"
             style={{ '--bundle-accent': bundle.accent || 'var(--accent-primary)' }}
             onClick={() => onOpenDetails && onOpenDetails(bundle)}
             role="button" tabIndex={0}
             aria-label={`${bundle.name[locale]} — ${bundle.tagline[locale]}`}>
      <header className="sh-bundle-head">
        <span className="sh-bundle-chip">
          {_shHHIcon({ name: 'gift', size: 11 })}
          <span>{ll.catBundle}</span>
        </span>
        <span className="sh-bundle-savings">
          {ll.bundleSavings(_shT(bundle.savingsPct, locale))}
        </span>
      </header>
      <div className="sh-bundle-body">
        <h3 className="sh-bundle-name">{bundle.name[locale]}</h3>
        <p className="sh-bundle-tagline">{bundle.tagline[locale]}</p>
        <div className="sh-bundle-stack" aria-hidden="true">
          {memberPacks.map((p, i) => (
            <span key={p.id} className="sh-bundle-mini"
                  style={{
                    '--mini-accent': (window.SHOP_RARITY?.[p.rarity]?.accent) || 'var(--accent-primary)',
                    transform: `translateX(${i * (locale === 'en' ? 14 : -14)}px) rotate(${i * 3 - (memberPacks.length - 1) * 1.5}deg)`,
                    zIndex: memberPacks.length - i,
                  }}>
              {_shHHIcon({ name: 'cards', size: 14 })}
            </span>
          ))}
        </div>
      </div>
      <footer className="sh-bundle-foot">
        <div className="sh-bundle-foot-meta">
          <span className="sh-bundle-pack-count">{ll.bundlePackCount(_shT(memberPacks.length, locale))}</span>
          {originalTotal > bundle.bundlePrice && (
            <span className="sh-bundle-was tabular-nums">{_shT(originalTotal.toLocaleString('en-US'), locale)}</span>
          )}
        </div>
        <PriceTag locale={locale} value={bundle.bundlePrice} tone="accent" variant="inline"/>
      </footer>
    </article>
  );
}

// ─── GiftCodeRow ───────────────────────────────────────────────────────────
// One row per existing gift code (showing its state) + an input control at
// the top for entering a new code. Visually: a clean glass row with a chip
// indicating state (available / redeemed / expired) and inline action.
function GiftCodeRow({ code, locale = 'fa', onRedeem }) {
  if (!code) return null;
  const ll = window.SHOP_I18N ? window.SHOP_I18N[locale] : {};
  const isAvailable = code.state === 'available';
  const isRedeemed  = code.state === 'redeemed';
  const isExpired   = code.state === 'expired';
  return (
    <div className={`sh-gift-row
                     ${isAvailable ? 'is-available' : ''}
                     ${isRedeemed  ? 'is-redeemed'  : ''}
                     ${isExpired   ? 'is-expired'   : ''}`}>
      <div className="sh-gift-row-icon" aria-hidden="true">
        {_shHHIcon({ name: isExpired ? 'lock' : 'gift', size: 16 })}
      </div>
      <div className="sh-gift-row-body">
        <code className="sh-gift-row-code">{code.code}</code>
        <div className="sh-gift-row-sub">
          {isAvailable && ll.giftCodeAvailable}
          {isRedeemed  && ll.giftCodeUsedAt(code.redeemedAt?.[locale] || '')}
          {isExpired   && ll.giftCodeExpired}
        </div>
      </div>
      {isAvailable && (
        <button type="button" className="sh-gift-row-cta"
                onClick={() => onRedeem && onRedeem(code)}>
          <span>{ll.giftCodeRedeem}</span>
          {_shHHIcon({ name: 'chevron', size: 12 })}
        </button>
      )}
      {isRedeemed && (
        <span className="sh-gift-row-pill sh-gift-row-pill--success">
          {_shHHIcon({ name: 'check', size: 11 })}
        </span>
      )}
    </div>
  );
}

// ─── GiftCodeInput ─────────────────────────────────────────────────────────
// Standalone input + redeem button (used at the top of the gift-code list).
function GiftCodeInput({ locale = 'fa', onRedeem }) {
  const ll = window.SHOP_I18N ? window.SHOP_I18N[locale] : {};
  const [val, setVal] = shUseState('');
  const [err, setErr] = shUseState(null);
  const submit = () => {
    if (!val.trim()) return;
    const res = window.redeemGiftCode ? window.redeemGiftCode(val, window.SHOP_USER) : null;
    if (res && res.ok) {
      setVal('');
      setErr(null);
      onRedeem && onRedeem(res);
    } else {
      setErr(res?.reason === 'redeemed' ? ll.giftCodeUsedAt('')
           : res?.reason === 'expired'  ? ll.giftCodeExpired
           :                              ll.giftCodeInvalid);
    }
  };
  return (
    <div className="sh-gift-input">
      <div className="sh-gift-input-row">
        <input type="text"
               className="sh-gift-input-field"
               placeholder={ll.giftCodePlaceholder}
               value={val}
               onChange={(e) => { setVal(e.target.value.toUpperCase()); setErr(null); }}
               onKeyDown={(e) => { if (e.key === 'Enter') submit(); }}
               dir="ltr"/>
        <button type="button" className="sh-gift-input-cta" onClick={submit}>
          <span>{ll.giftCodeRedeem}</span>
        </button>
      </div>
      {err && <div className="sh-gift-input-err">{err}</div>}
    </div>
  );
}

// ─── SponsorPack (v1.5.1) ──────────────────────────────────────────────────
// Pack-shaped sponsor card. Lives *inside* the pack grid as a single tile so
// it inherits the same dimensions as `<PackCard>` and never breaks the row
// rhythm. Brand color drives the gradient + ribbon; a small "حمایت‌شده"
// eyebrow makes it clear this isn't a regular pack. The CTA is its own
// button (not the surrounding card) so screen-readers and keyboard users
// get a distinct, labeled action target.
function SponsorPack({ locale = 'fa', sponsor, onClick }) {
  if (!sponsor) return null;
  const ll = window.SHOP_I18N ? window.SHOP_I18N[locale] : {};
  return (
    <div className="sh-sponsor-pack"
         style={{ '--brand': sponsor.brand, '--brand-2': sponsor.brand2 }}>
      <span className="sh-sponsor-pack-eye">{ll.sponsorEyebrow || 'Sponsored'}</span>
      <div className="sh-sponsor-pack-mark" aria-hidden="true">
        {sponsor.logoSrc
          ? <img className="sh-sponsor-pack-logo" src={sponsor.logoSrc} alt=""/>
          : <span>{sponsor.mark}</span>}
      </div>
      <div className="sh-sponsor-pack-body">
        <h3 className="sh-sponsor-pack-name">{sponsor.name[locale]}</h3>
        <p className="sh-sponsor-pack-tag">{sponsor.tagline[locale]}</p>
        {sponsor.bonus && (
          <span className="sh-sponsor-pack-bonus">
            {_shHHIcon({ name: 'coin', size: 11 })}
            <span>{sponsor.bonus[locale]}</span>
          </span>
        )}
      </div>
      <button type="button"
              className="sh-sponsor-pack-cta"
              onClick={(e) => { e.stopPropagation(); onClick && onClick(sponsor); }}>
        <span>{sponsor.cta?.[locale] || ll.sponsorCta}</span>
        <svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true">
          <path d="M3.5 2.5 L6 5 L3.5 7.5" stroke="currentColor"
                strokeWidth="1.5" fill="none"
                strokeLinecap="round" strokeLinejoin="round"/>
        </svg>
      </button>
      <div className="sh-sponsor-pack-shine" aria-hidden="true"></div>
    </div>
  );
}

// ─── SponsorBox (v1.5.1) ───────────────────────────────────────────────────
// Full-width horizontal sponsor banner. Used between sections on mobile and
// stacked in the desktop sidebar. Layout is logo + (name / tagline) + CTA on
// a single row, wrapping the CTA below at narrow widths. Same brand-token
// surface as SponsorPack but no ribbon / mark-mega; this one's a quieter
// shape so a column of two stays readable.
function SponsorBox({ locale = 'fa', sponsor, onClick }) {
  if (!sponsor) return null;
  const ll = window.SHOP_I18N ? window.SHOP_I18N[locale] : {};
  return (
    <div className="sh-sponsor-box"
         style={{ '--brand': sponsor.brand, '--brand-2': sponsor.brand2 }}>
      <div className="sh-sponsor-box-mark" aria-hidden="true">
        {sponsor.logoSrc
          ? <img className="sh-sponsor-box-logo" src={sponsor.logoSrc} alt=""/>
          : <span>{sponsor.mark}</span>}
      </div>
      <div className="sh-sponsor-box-body">
        <span className="sh-sponsor-box-eye">{ll.sponsorEyebrow || 'Sponsored'}</span>
        <div className="sh-sponsor-box-name">{sponsor.name[locale]}</div>
        <div className="sh-sponsor-box-tag">{sponsor.tagline[locale]}</div>
        {sponsor.bonus && (
          <span className="sh-sponsor-box-bonus">
            {_shHHIcon({ name: 'coin', size: 11 })}
            <span>{sponsor.bonus[locale]}</span>
          </span>
        )}
      </div>
      <button type="button"
              className="sh-sponsor-box-cta"
              onClick={(e) => { e.stopPropagation(); onClick && onClick(sponsor); }}>
        <span>{sponsor.cta?.[locale] || ll.sponsorCta}</span>
      </button>
      <div className="sh-sponsor-box-shine" aria-hidden="true"></div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────
// ──── Phase 4 ── PackOpenScene ───────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────
//
// Fullscreen pack-open animation overlay. Four discrete stages cover the
// entire experience:
//
//   1. flash   (~380 ms) — quick accent pulse (opacity + scale only)
//   2. idle    (loops)   — pack + pulsing glow ring (no blur filters)
//   3. reveal  (per tap) — first tap: pack drops away, card drops in; later
//                          taps: active card falls out, then next card drops
//                          in from above (sequential, not instant swap).
//   4. summary           — staggered card grid + tally + CTA
//
// Performance (v1.5.2): no backdrop-filter, no filter:blur, no preserve-3d
// flip, no duplicate PackArt layers. Only transform + opacity animate.

// Reduced-motion detector — combines the OS media query with the explicit
// `sh-reduce-motion` class (driven by the Tweaks panel) so the scene
// degrades gracefully in either case.
function useShopReducedMotion() {
  const [reduced, setReduced] = shUseState(() => {
    if (typeof window === 'undefined') return false;
    if (document.documentElement.classList.contains('sh-reduce-motion')) return true;
    try { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; }
    catch { return false; }
  });
  shUseEffect(() => {
    const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
    const update = () => setReduced(
      document.documentElement.classList.contains('sh-reduce-motion') || mq.matches
    );
    mq.addEventListener?.('change', update);
    const obs = new MutationObserver(update);
    obs.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
    return () => { mq.removeEventListener?.('change', update); obs.disconnect(); };
  }, []);
  return reduced;
}

// Render one reward-card record (the shape returned by
// `generateRewardCards`). Centralised here so the reveal stack + the
// summary grid stay visually identical.
function _renderRewardCard(card, locale, size = 'compact') {
  if (!card) return null;
  const r = (obj, prop) => (typeof obj?.[prop] === 'object' ? obj[prop][locale] : obj?.[prop]);
  if (card.kind === 'player' && window.PlayerCard) {
    return (
      <window.PlayerCard
        tier={card.props.tier} locale={locale} size={size}
        position={card.props.position} overall={card.props.overall}
        statKey={card.props.statKey} statValue={card.props.statValue}
        playerName={r(card.props, 'playerName')}
        teamName={r(card.props, 'teamName')}
        playerImage={card.props.playerImage}/>
    );
  }
  if (card.kind === 'fan' && window.FanCard) {
    return (
      <window.FanCard locale={locale} size={size}
        bonusValue={card.props.bonusValue}
        team={r(card.props, 'team')} image={card.props.image}/>
    );
  }
  if (card.kind === 'coach' && window.CoachCard) {
    return (
      <window.CoachCard locale={locale} size={size}
        multiplier={card.props.multiplier}
        coachName={r(card.props, 'coachName')}
        condition={r(card.props, 'condition')}
        image={card.props.image}/>
    );
  }
  if (card.kind === 'null' && window.NullCard) {
    return (
      <window.NullCard locale={locale} size={size}
        tier={card.props.tier} overall={card.props.overall}
        image={card.props.image}/>
    );
  }
  return null;
}

// Per-card visual tier used for the rarity-tinted reveal burst.
function _cardTierKey(card) {
  if (!card) return 'common';
  if (card.kind === 'player') return card.props?.tier || 'common';
  if (card.kind === 'fan')    return 'special-fan';
  if (card.kind === 'coach')  return 'special-coach';
  if (card.kind === 'null')   return card.props?.tier || 'common';
  return 'common';
}
const _TIER_ACCENT = {
  bronze:  'var(--tier-bronze)',
  silver:  'var(--tier-silver)',
  gold:    'var(--tier-gold)',
  platinum:'var(--tier-platinum)',
  common:  'var(--text-tertiary)',
  'special-fan':   'var(--danger)',
  'special-coach': 'var(--success)',
};

function PackOpenScene({ pack, cards = [], locale = 'fa',
                          onClose, inline = true,
                          initialStage = 'flash',
                          initialRevealIdx = 0,
                          reduced: reducedOverride }) {
  if (!pack) return null;
  const ll = window.SHOP_I18N ? window.SHOP_I18N[locale] : {};
  const palette = (window.SHOP_RARITY || {})[pack.rarity] || {};
  const reducedAuto = useShopReducedMotion();
  const reduced = reducedOverride != null ? reducedOverride : reducedAuto;

  // stage: 'flash' | 'idle' | 'reveal' | 'summary'
  // initialStage lets artboard previews freeze the scene mid-flow
  // (e.g. show the 3rd card of a 5-card pack without user interaction).
  const [stage, setStage] = shUseState(reduced ? 'summary' : initialStage);
  const [revealIdx, setRevealIdx] = shUseState(
    initialStage === 'reveal' ? initialRevealIdx :
    initialStage === 'summary' ? Math.max(0, cards.length - 1) : -1
  );
  const [burst, setBurst] = shUseState(0);            // first reveal tap only (pack drop)
  // reveal card motion: entering → settled → exiting → next entering
  const [cardMotion, setCardMotion] = shUseState(
    reduced || initialStage === 'reveal' || initialStage === 'summary' ? 'settled' : 'settled'
  );
  const [cardBusy, setCardBusy] = shUseState(false);
  const sceneRef = shUseRef(null);
  const timersRef = shUseRef([]);

  const CARD_EXIT_MS = 520;   // fallback if animationend does not fire
  const CARD_ENTER_MS = 760;

  const clearSceneTimers = () => {
    timersRef.current.forEach(clearTimeout);
    timersRef.current = [];
  };
  const scheduleScene = (fn, ms) => {
    const id = setTimeout(fn, ms);
    timersRef.current.push(id);
    return id;
  };
  shUseEffect(() => () => clearSceneTimers(), []);

  // Stage 1 (flash) → 2 (idle). Snappy but readable (~380 ms).
  shUseEffect(() => {
    if (reduced) return;
    if (stage !== 'flash') return;
    const t = setTimeout(() => setStage('idle'), 560);
    return () => clearTimeout(t);
  }, [stage, reduced]);

  // Hard re-entry: if `reduced` flips on mid-flow, snap to summary so the
  // user never sees a partial state without animations.
  shUseEffect(() => {
    if (reduced && stage !== 'summary') {
      clearSceneTimers();
      setCardBusy(false);
      setCardMotion('settled');
      setStage('summary');
    }
  }, [reduced]);

  // Keyboard: Esc closes; Space/Enter advances.
  shUseEffect(() => {
    const onKey = (e) => {
      if (e.key === 'Escape') { e.preventDefault(); onClose && onClose(); return; }
      if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); advance(); }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [stage, revealIdx, cards.length]);

  // Focus management — autofocus the scene so keyboard works without click.
  shUseEffect(() => {
    sceneRef.current?.focus?.();
  }, []);

  const finishCardEnter = () => {
    setCardMotion('settled');
    setCardBusy(false);
  };

  const startCardEnter = () => {
    if (reduced) {
      finishCardEnter();
      return;
    }
    clearSceneTimers();
    setCardMotion('entering');
    setCardBusy(true);
    scheduleScene(finishCardEnter, CARD_ENTER_MS);
  };

  const finishCardExit = () => {
    setRevealIdx((i) => i + 1);
    setBurst((b) => b + 1);
    startCardEnter();
  };

  const advanceToNextCard = () => {
    if (reduced) {
      setRevealIdx((i) => i + 1);
      setCardMotion('settled');
      return;
    }
    clearSceneTimers();
    setCardMotion('exiting');
    setCardBusy(true);
    scheduleScene(finishCardExit, CARD_EXIT_MS);
  };

  const handleCardAnimEnd = (e) => {
    if (reduced || e.target !== e.currentTarget) return;
    const name = e.animationName || '';
    clearSceneTimers();
    if (name.includes('card-drop-in') && cardMotion === 'entering') {
      finishCardEnter();
    } else if (name.includes('card-drop-out') && cardMotion === 'exiting') {
      finishCardExit();
    }
  };

  const advance = () => {
    if (stage === 'flash')   return;
    if (cardBusy) return;
    if (stage === 'idle') {
      setStage('reveal');
      setRevealIdx(0);
      setBurst((b) => b + 1);
      startCardEnter();
      return;
    }
    if (stage === 'reveal') {
      if (cardMotion !== 'settled') return;
      if (revealIdx < cards.length - 1) {
        advanceToNextCard();
      } else {
        setCardBusy(true);
        scheduleScene(() => {
          setStage('summary');
          setCardBusy(false);
        }, 800);
      }
      return;
    }
    if (stage === 'summary') {
      onClose && onClose();
    }
  };

  const skipToSummary = (e) => {
    e?.stopPropagation();
    clearSceneTimers();
    setCardBusy(false);
    setCardMotion('settled');
    setStage('summary');
  };
  const handleClose = (e) => {
    e?.stopPropagation();
    onClose && onClose();
  };

  // Rarity tally for the summary footer.
  const tally = shUseMemo(() => {
    const counts = {};
    cards.forEach((c) => {
      const k = _cardTierKey(c);
      counts[k] = (counts[k] || 0) + 1;
    });
    return counts;
  }, [cards]);

  // Names + colors for the tally chips. Order matches reward distribution.
  const TIER_LABEL = {
    platinum: ll.tierPlatinum, gold: ll.tierGold, silver: ll.tierSilver,
    bronze: ll.tierBronze, common: ll.tierCommon,
    'special-fan': ll.tierFan || 'هوادار', 'special-coach': ll.tierCoach || 'مربی',
  };
  const tallyOrder = ['platinum', 'gold', 'silver', 'bronze', 'special-coach', 'special-fan', 'common'];

  const isMythic = pack.rarity === 'mythic';
  const accent = pack.accent || palette.accent || 'var(--accent-primary)';
  const glow   = palette.glow || 'var(--accent-glow)';

  // ─── Stage renderers ─────────────────────────────────────────────────────
  const renderFlash = () => (
    <div className="sh-scene-flash" aria-hidden="true"
         style={{ '--scene-accent': accent }}/>
  );

  const renderIdle = () => (
    <div className="sh-scene-stage sh-scene-stage--idle"
         style={{ '--scene-accent': accent, '--scene-glow': glow }}>
      <div className="sh-scene-pack-wrap">
        <div className="sh-scene-glow" aria-hidden="true"/>
        <div className="sh-scene-pack">
          <PackArt rarity={pack.rarity} accent={pack.accent}
                   themeTeam={pack.themeTeam} artStyle={pack.artStyle}
                   coverImage={pack.coverImage}/>
        </div>
      </div>
      <div className="sh-scene-meta">
        <RarityBadge locale={locale} rarity={pack.rarity}/>
        <h2 className="sh-scene-title">{pack.name[locale]}</h2>
        <p className="sh-scene-sub">{ll.openTapHint || 'بزن تا بازش کنی'}</p>
        <span className="sh-scene-arrow" aria-hidden="true">▼</span>
      </div>
    </div>
  );

  const renderReveal = () => {
    const current = cards[revealIdx];
    const tier = _cardTierKey(current);
    const tAccent = _TIER_ACCENT[tier] || 'var(--accent-primary)';
    const showPackPop = revealIdx === 0 && burst <= 1;
    return (
      <div className="sh-scene-stage sh-scene-stage--reveal"
           style={{ '--scene-accent': accent,
                    '--scene-glow':   glow,
                    '--card-accent':  tAccent }}>
        {showPackPop && (
          <div className="sh-scene-pack-pop" aria-hidden="true" key="pack-pop">
            <PackArt rarity={pack.rarity} accent={pack.accent}
                     themeTeam={pack.themeTeam} artStyle={pack.artStyle}
                     coverImage={pack.coverImage}/>
          </div>
        )}

        <div className="sh-scene-drop-zone"
             style={{
               '--drop-tilt': revealIdx % 2 === 0 ? '-11deg' : '9deg',
               '--exit-tilt': revealIdx % 2 === 0 ? '12deg' : '-10deg',
             }}>
          <div className={`sh-scene-card-motion is-${cardMotion}`} key={revealIdx}>
            <div className="sh-scene-card-motion-inner"
                 onAnimationEnd={handleCardAnimEnd}>
              {_renderRewardCard(current, locale, 'compact')}
            </div>
          </div>
        </div>

        <div className="sh-scene-progress" aria-live="polite">
          <span>
            {ll.openCardOf
              ? ll.openCardOf(_shT(revealIdx + 1, locale), _shT(cards.length, locale))
              : `${_shT(revealIdx + 1, locale)} / ${_shT(cards.length, locale)}`}
          </span>
        </div>

        <div className="sh-scene-meta sh-scene-meta--bottom">
          {isMythic && revealIdx === 0 && (
            <span className="sh-scene-mythic-banner">
              {ll.openMythicBanner || 'پاداش اسطوره‌ای!'}
            </span>
          )}
          <p className="sh-scene-sub">
            {revealIdx < cards.length - 1
              ? (ll.openTapNext || 'بزن برای کارت بعدی')
              : (ll.openTapDone || 'تمومه — بزن برای جمع‌بندی')}
          </p>
          <span className="sh-scene-arrow" aria-hidden="true">▼</span>
        </div>
      </div>
    );
  };

  const renderSummary = () => (
    <div className="sh-scene-stage sh-scene-stage--summary"
         style={{ '--scene-accent': accent, '--scene-glow': glow }}
         onClick={(e) => e.stopPropagation()}>
      <header className="sh-scene-sum-head">
        <RarityBadge locale={locale} rarity={pack.rarity}/>
        <h2 className="sh-scene-title">
          {ll.openSummaryTitle || 'کارت‌هات آماده‌ست!'}
        </h2>
        <p className="sh-scene-sub">
          {ll.openSummaryHint
            ? ll.openSummaryHint(_shT(cards.length, locale))
            : `${_shT(cards.length, locale)} کارت جدید به مجموعه‌ات اضافه شد`}
        </p>
      </header>

      <div className="sh-scene-sum-grid">
        {cards.map((c, i) => (
          <div key={i} className="sh-scene-sum-card"
               style={{ '--c-accent': _TIER_ACCENT[_cardTierKey(c)] }}>
            {_renderRewardCard(c, locale, 'compact')}
          </div>
        ))}
      </div>

      <div className="sh-scene-tally">
        {tallyOrder.filter((k) => tally[k]).map((k) => (
          <span key={k} className="sh-scene-tally-chip"
                style={{ '--t-accent': _TIER_ACCENT[k] }}>
            <span className="sh-scene-tally-dot" aria-hidden="true"/>
            <span className="sh-scene-tally-label">{TIER_LABEL[k] || k}</span>
            <span className="sh-scene-tally-count tabular-nums">{_shT(tally[k], locale)}</span>
          </span>
        ))}
      </div>

      <button type="button" className="sh-scene-close-cta"
              onClick={handleClose}>
        <span>{ll.openAddAll || ll.openBackToShop || 'بستن'}</span>
      </button>
    </div>
  );

  return (
    <div ref={sceneRef}
         className={`sh-scene sh-scene--${stage} ${isMythic ? 'is-mythic' : ''} ${reduced ? 'is-reduced' : ''} ${inline ? 'is-inline' : 'is-fixed'}`}
         role="dialog"
         aria-modal="true"
         aria-label={`${ll.openAria || 'باز کردن پک'}: ${pack.name[locale]}`}
         tabIndex={-1}
         onClick={advance}>
      <div className="sh-scene-backdrop" aria-hidden="true"/>

      {stage === 'flash'   && renderFlash()}
      {stage === 'idle'    && renderIdle()}
      {stage === 'reveal'  && renderReveal()}
      {stage === 'summary' && renderSummary()}

      {/* Top-end chrome — always visible */}
      <div className="sh-scene-chrome" onClick={(e) => e.stopPropagation()}>
        {stage !== 'summary' && (
          <button type="button" className="sh-scene-skip" onClick={skipToSummary}>
            {ll.openSkip || 'رد کن'}
          </button>
        )}
        <button type="button" className="sh-scene-close" onClick={handleClose}
                aria-label={ll.openBackToShop || 'بستن'}>
          <span aria-hidden="true">×</span>
        </button>
      </div>
    </div>
  );
}

// ─── Export to window ──────────────────────────────────────────────────────
Object.assign(window, {
  PriceTag, RarityBadge, PackCard, BundleCard, GiftCodeRow, GiftCodeInput,
  SponsorPack, SponsorBox, PackOpenScene,
});

// ─── CSS (sh- prefix, token-only) ──────────────────────────────────────────
const SHOP_PIECES_CSS = `
/* ─── PriceTag ─────────────────────────────────────────────────────────── */
.sh-price {
  display: inline-flex; align-items: baseline; gap: 5px;
  font-family: var(--font-current);
  font-weight: 700;
  white-space: nowrap;
}
.sh-price--inline { font-size: 13px; }
.sh-price--large  { font-size: 22px; gap: 8px; }
.sh-price-icon { display: inline-flex; align-self: center; }
.sh-price--gold   .sh-price-icon { color: var(--tier-gold); }
.sh-price--gold   .sh-price-value { color: var(--text-primary); }
.sh-price--accent .sh-price-icon { color: var(--accent-primary); }
.sh-price--accent .sh-price-value { color: var(--text-primary); }
.sh-price-value { letter-spacing: -0.01em; }
/* Sale — was price on first line, coin + now on second (pack card foot) */
.sh-price--sale {
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  line-height: 1.15;
  min-width: 0;
}
.sh-price--sale.sh-price--inline { font-size: 12px; }
.sh-price--sale.sh-price--large  { font-size: 20px; gap: 4px; }
.sh-price-was {
  font-size: 0.82em; font-weight: 600;
  color: var(--text-tertiary);
  text-decoration: line-through;
  text-decoration-thickness: 1.5px;
}
.sh-price--sale .sh-price-was {
  font-size: 1em;
  line-height: 1.2;
}
.sh-price-now {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.sh-price--sale.sh-price--large .sh-price-now { gap: 8px; }
.sh-price--free .sh-price-value {
  color: var(--success);
  font-weight: 800;
  letter-spacing: 0;
}

/* ─── RarityBadge ──────────────────────────────────────────────────────── */
.sh-rarity {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 8px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--rarity-accent, var(--accent-primary)) 32%, transparent);
  background: color-mix(in srgb, var(--rarity-accent, var(--accent-primary)) 12%, var(--bg-elevated));
  color: var(--text-primary);
  font-family: var(--font-current);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.sh-rarity.is-dot { padding: 4px; }
.sh-rarity-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--rarity-accent, var(--accent-primary));
  box-shadow: 0 0 8px var(--rarity-glow, var(--accent-glow));
}
.sh-rarity-label { line-height: 1; }
.sh-rarity--mythic { box-shadow: 0 0 16px -4px var(--rarity-glow); }

/* ─── PackBadge ────────────────────────────────────────────────────────── */
.sh-badge {
  display: inline-flex; align-items: center;
  padding: 4px 8px;
  border-radius: 999px;
  font-family: var(--font-current);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.02em;
  box-shadow: 0 2px 8px -2px rgba(0,0,0,0.40);
}
.sh-badge--info    { background: color-mix(in srgb, var(--info) 22%, var(--bg-elevated));    color: var(--info); }
.sh-badge--danger  { background: color-mix(in srgb, var(--danger) 22%, var(--bg-elevated));  color: var(--danger); }
.sh-badge--warning { background: color-mix(in srgb, var(--warning-2) 22%, var(--bg-elevated)); color: var(--warning); }
.sh-badge--accent  { background: linear-gradient(135deg, var(--accent-primary), var(--accent-primary-2)); color: white; }
.sh-badge--success { background: color-mix(in srgb, var(--success) 22%, var(--bg-elevated)); color: var(--success); }

/* ─── PackArt ──────────────────────────────────────────────────────────── */
.sh-pack-art {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 14px;
  overflow: hidden;
  background:
    linear-gradient(135deg, var(--pack-surface-from), var(--pack-surface-to)),
    radial-gradient(80% 100% at 50% 0%, color-mix(in srgb, var(--pack-accent) 28%, transparent) 0%, transparent 60%),
    var(--bg-elevated);
  border: 1px solid color-mix(in srgb, var(--pack-accent) 28%, var(--border-soft, rgba(255,255,255,0.08)));
  isolation: isolate;
}
/* Cover photo + colored halo (no blur — pack art stays sharp) */
.sh-pack-art.has-cover {
  background: var(--bg-deep);
  border-color: color-mix(in srgb, var(--pack-accent) 32%, var(--border-soft, rgba(255,255,255,0.1)));
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--pack-accent) 18%, transparent),
    0 12px 32px -14px color-mix(in srgb, var(--pack-accent) 45%, transparent);
}
.sh-pack-art-photo {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}
/* Anchor to top — bottom may crop outside the frame */
.sh-pack-art-photo img {
  width: 100%;
  height: 118%;
  max-width: none;
  object-fit: cover;
  object-position: top center;
  display: block;
}
.sh-pack-art-halo {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    radial-gradient(ellipse 95% 55% at 50% -5%,
      color-mix(in srgb, var(--pack-accent) 42%, transparent) 0%,
      transparent 58%),
    radial-gradient(ellipse 55% 45% at 0% 85%,
      color-mix(in srgb, var(--pack-accent) 30%, transparent) 0%,
      transparent 55%),
    radial-gradient(ellipse 55% 45% at 100% 85%,
      color-mix(in srgb, var(--pack-accent) 30%, transparent) 0%,
      transparent 55%);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--pack-accent) 28%, transparent);
}
.sh-pack-art.has-cover .sh-pack-art-glow {
  z-index: 1;
  opacity: 0.85;
  background: radial-gradient(65% 55% at 50% 8%,
    color-mix(in srgb, var(--pack-accent) 35%, transparent) 0%,
    transparent 72%);
}
.sh-pack.is-locked .sh-pack-art-photo img,
.sh-pack.is-coming .sh-pack-art-photo img {
  filter: grayscale(0.65) brightness(0.52);
}
.sh-pack.is-locked .sh-pack-art-halo,
.sh-pack.is-coming .sh-pack-art-halo {
  opacity: 0.55;
}
.sh-pack-art-spine {
  position: absolute;
  inset-block: 12%;
  inset-inline-start: 10%;
  width: 6px;
  border-radius: 4px;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--pack-accent) 70%, transparent),
    color-mix(in srgb, var(--pack-accent) 30%, transparent));
  box-shadow: 0 0 18px var(--pack-accent);
}
.sh-pack-art-face {
  position: absolute;
  inset: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--pack-accent);
  filter: drop-shadow(0 4px 14px color-mix(in srgb, var(--pack-accent) 40%, transparent));
}
.sh-pack-art-shine {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: linear-gradient(115deg,
    transparent 30%,
    rgba(255,255,255,0.14) 50%,
    transparent 70%);
  transform: translateX(-100%);
  transition: transform 800ms cubic-bezier(0.16, 1, 0.3, 1);
  pointer-events: none;
}
.sh-pack-art.has-cover .sh-pack-art-shine {
  z-index: 3;
  opacity: 0.55;
  background: linear-gradient(115deg,
    transparent 32%,
    rgba(255,255,255,0.18) 50%,
    transparent 70%);
}
.sh-pack-art-glow {
  position: absolute;
  inset: -10%;
  background: radial-gradient(50% 50% at 50% 30%,
    color-mix(in srgb, var(--pack-accent) 22%, transparent) 0%,
    transparent 70%);
  pointer-events: none;
  z-index: -1;
}
.sh-pack:hover .sh-pack-art-shine { transform: translateX(100%); }

/* artStyle variants nudge the surface look */
.sh-pack-art--gradient {
  background:
    linear-gradient(135deg,
      color-mix(in srgb, var(--pack-accent) 32%, transparent) 0%,
      color-mix(in srgb, var(--pack-accent) 8%, transparent) 60%,
      transparent 100%),
    radial-gradient(80% 100% at 50% 0%, color-mix(in srgb, var(--pack-accent) 22%, transparent) 0%, transparent 60%),
    var(--bg-elevated);
}
.sh-pack-art--gold .sh-pack-art-face { color: var(--tier-gold); }
.sh-pack-art--team .sh-pack-art-face { color: var(--text-primary); }

/* ─── PackCard ─────────────────────────────────────────────────────────── */
.sh-pack {
  position: relative;
  display: flex; flex-direction: column;
  background: linear-gradient(180deg,
    rgba(255,255,255,0.04) 0%,
    rgba(255,255,255,0.015) 100%);
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
  border-radius: 18px;
  padding: 12px;
  backdrop-filter: blur(20px) saturate(120%);
  cursor: pointer;
  transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1),
              border-color 200ms ease,
              box-shadow 200ms ease;
  font-family: var(--font-current);
  isolation: isolate;
}
.sh-pack:hover {
  transform: translateY(-2px);
  border-color: rgba(255,255,255,0.14);
  box-shadow: 0 14px 40px -20px rgba(0,0,0,0.65);
}
.sh-pack:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
.sh-pack--mythic { border-color: color-mix(in srgb, var(--accent-primary) 32%, var(--border-soft, rgba(255,255,255,0.08))); }
.sh-pack--mythic:hover { box-shadow: 0 18px 50px -16px var(--accent-glow); }

.sh-pack-top {
  position: relative;
  margin-bottom: 12px;
}
.sh-pack-badge-wrap {
  position: absolute;
  top: 8px;
  inset-inline-end: 8px;
  z-index: 2;
}
.sh-pack-rarity-wrap {
  position: absolute;
  bottom: 8px;
  inset-inline-start: 8px;
  z-index: 2;
}
.sh-pack-state-overlay {
  position: absolute;
  inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(7,7,12,0.50);
  backdrop-filter: blur(2px);
  color: var(--text-secondary);
  border-radius: 14px;
  z-index: 3;
}
.sh-pack-state-overlay--coming {
  background: rgba(7,7,12,0.65);
  backdrop-filter: blur(4px);
}
.sh-pack.is-locked .sh-pack-art-face,
.sh-pack.is-locked .sh-pack-art-spine { opacity: 0.4; }
.sh-pack.is-coming .sh-pack-art-face,
.sh-pack.is-coming .sh-pack-art-spine { opacity: 0.3; }

.sh-pack-body {
  display: flex; flex-direction: column;
  gap: 6px;
}
.sh-pack-name {
  margin: 0;
  font-family: var(--font-display-current);
  font-size: 16px;
  font-weight: 800;
  color: var(--text-primary);
  line-height: 1.2;
  letter-spacing: -0.01em;
}
.sh-pack-tagline {
  margin: 0;
  font-size: 11px;
  color: var(--text-secondary);
  line-height: 1.4;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.sh-pack-meta {
  display: inline-flex; align-items: center; gap: 10px;
  margin-top: 2px;
  flex-wrap: wrap;
}
.sh-pack-meta-item {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 10px;
  font-weight: 600;
  color: var(--text-tertiary);
}
.sh-pack-meta-item svg { color: var(--text-tertiary); }
.sh-pack-meta-item--guaranteed {
  color: var(--success);
}
.sh-pack-meta-item--guaranteed svg { color: var(--success); }
.sh-pack-foot {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 8px;
  margin-top: 8px;
  padding-top: 10px;
  border-top: 1px solid rgba(255,255,255,0.06);
}
.sh-pack-foot .sh-price {
  flex: 1 1 auto;
  min-width: 0;
}
.sh-pack-foot .sh-pack-cta {
  flex: 0 0 auto;
}

/* Pack CTA — glass primary CTA per §13.2 of the Design Log */
.sh-pack-cta {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 8px 14px;
  border-radius: 12px;
  border: 1px solid rgba(255,255,255,0.16);
  background: linear-gradient(180deg, rgba(255,255,255,0.16) 0%, rgba(255,255,255,0.05) 100%);
  backdrop-filter: blur(8px) saturate(140%);
  color: var(--text-primary);
  font-family: var(--font-current);
  font-size: 12px;
  font-weight: 700;
  text-shadow: 0 1px 0 rgba(0,0,0,0.35);
  cursor: pointer;
  transition: border-color 180ms ease, transform 120ms ease;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,0.18),
    0 6px 16px -10px rgba(0,0,0,0.50);
}
.sh-pack-cta:hover  { border-color: rgba(255,255,255,0.32); }
.sh-pack-cta:active { transform: scale(0.97); }
.sh-pack-cta--ghost {
  background: transparent;
  border-color: rgba(255,255,255,0.10);
  color: var(--text-tertiary);
}
.sh-pack-cta--disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
html[dir="rtl"] .sh-pack-cta svg:last-child { transform: scaleX(-1); }

.sh-pack--compact { padding: 10px; }
.sh-pack--compact .sh-pack-art { aspect-ratio: 16 / 10; }
.sh-pack--compact .sh-pack-name { font-size: 14px; }

/* ─── BundleCard ───────────────────────────────────────────────────────── */
.sh-bundle {
  display: flex; flex-direction: column;
  gap: 10px;
  padding: 14px;
  border-radius: 18px;
  border: 1px solid color-mix(in srgb, var(--bundle-accent) 32%, var(--border-soft, rgba(255,255,255,0.08)));
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--bundle-accent) 12%, transparent) 0%,
      rgba(255,255,255,0.02) 100%),
    var(--bg-elevated);
  font-family: var(--font-current);
  cursor: pointer;
  transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1), border-color 200ms ease;
  isolation: isolate;
}
.sh-bundle:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--bundle-accent) 55%, transparent);
}
.sh-bundle-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
}
.sh-bundle-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 9px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bundle-accent) 22%, var(--bg-deep));
  border: 1px solid color-mix(in srgb, var(--bundle-accent) 40%, transparent);
  color: var(--bundle-accent);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.sh-bundle-savings {
  font-size: 11px;
  font-weight: 700;
  color: var(--success);
}
.sh-bundle-name {
  margin: 0;
  font-family: var(--font-display-current);
  font-size: 18px;
  font-weight: 800;
  color: var(--text-primary);
  letter-spacing: -0.01em;
}
.sh-bundle-tagline {
  margin: 0;
  font-size: 12px;
  color: var(--text-secondary);
}
.sh-bundle-stack {
  display: flex; align-items: center; justify-content: flex-start;
  padding: 12px 0 4px;
  min-height: 50px;
}
.sh-bundle-mini {
  width: 36px; height: 50px;
  border-radius: 8px;
  background: linear-gradient(150deg,
    color-mix(in srgb, var(--mini-accent) 35%, var(--bg-deep)),
    color-mix(in srgb, var(--mini-accent) 12%, var(--bg-deep)));
  border: 1px solid color-mix(in srgb, var(--mini-accent) 50%, transparent);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--mini-accent);
  box-shadow: 0 6px 14px -6px rgba(0,0,0,0.50);
}
.sh-bundle-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  padding-top: 8px;
  border-top: 1px solid rgba(255,255,255,0.06);
}
.sh-bundle-foot-meta {
  display: inline-flex; align-items: baseline; gap: 8px;
}
.sh-bundle-pack-count {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-tertiary);
}
.sh-bundle-was {
  font-size: 11px;
  color: var(--text-tertiary);
  text-decoration: line-through;
}

/* ─── GiftCodeRow / GiftCodeInput ──────────────────────────────────────── */
.sh-gift-input {
  display: flex; flex-direction: column;
  gap: 4px;
}
.sh-gift-input-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px;
  align-items: stretch;
}
.sh-gift-input-field {
  appearance: none;
  border: 1px solid var(--border-soft, rgba(255,255,255,0.10));
  background: var(--bg-elevated);
  color: var(--text-primary);
  border-radius: 12px;
  padding: 12px 14px;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: 0.05em;
  outline: none;
  transition: border-color 180ms ease, background 180ms ease;
}
.sh-gift-input-field::placeholder {
  font-family: var(--font-current);
  letter-spacing: 0;
  color: var(--text-tertiary);
  text-align: start;
}
.sh-gift-input-field:focus {
  border-color: var(--accent-primary);
  background: var(--bg-elevated);
}
.sh-gift-input-cta {
  appearance: none;
  border: 1px solid rgba(255,255,255,0.18);
  background: linear-gradient(180deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.06) 100%);
  color: var(--text-primary);
  font-family: var(--font-current);
  font-weight: 700;
  font-size: 12px;
  padding: 0 18px;
  border-radius: 12px;
  cursor: pointer;
  transition: border-color 180ms ease, transform 120ms ease;
}
.sh-gift-input-cta:hover  { border-color: rgba(255,255,255,0.32); }
.sh-gift-input-cta:active { transform: scale(0.97); }
.sh-gift-input-err {
  font-size: 11px;
  color: var(--danger);
  margin-inline-start: 4px;
}

.sh-gift-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 12px;
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
  font-family: var(--font-current);
}
.sh-gift-row.is-redeemed { opacity: 0.65; }
.sh-gift-row.is-expired  { opacity: 0.55; }
.sh-gift-row-icon {
  width: 32px; height: 32px;
  border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg-elevated);
  color: var(--text-tertiary);
}
.sh-gift-row.is-available .sh-gift-row-icon { color: var(--accent-primary); }
.sh-gift-row.is-redeemed  .sh-gift-row-icon { color: var(--success); }
.sh-gift-row-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sh-gift-row-code {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--text-primary);
  text-transform: uppercase;
}
.sh-gift-row-sub {
  font-size: 10.5px;
  color: var(--text-tertiary);
}
.sh-gift-row-cta {
  appearance: none;
  border: 1px solid rgba(255,255,255,0.16);
  background: linear-gradient(180deg, rgba(255,255,255,0.16) 0%, rgba(255,255,255,0.05) 100%);
  color: var(--text-primary);
  font-family: var(--font-current);
  font-weight: 700;
  font-size: 11.5px;
  padding: 7px 12px;
  border-radius: 10px;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 4px;
}
.sh-gift-row-cta:hover  { border-color: rgba(255,255,255,0.32); }
.sh-gift-row-pill {
  width: 28px; height: 28px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
}
.sh-gift-row-pill--success {
  background: color-mix(in srgb, var(--success) 22%, var(--bg-elevated));
  color: var(--success);
}
html[dir="rtl"] .sh-gift-row-cta svg:last-child { transform: scaleX(-1); }

/* ─── Page-level helpers ───────────────────────────────────────────────── */
.sh-screen {
  position: relative;
  width: 100%; height: 100%;
  display: flex; flex-direction: column;
  background:
    radial-gradient(ellipse 80% 50% at 20% 10%, rgba(113,99,217,0.08) 0%, transparent 60%),
    radial-gradient(ellipse 70% 50% at 80% 90%, rgba(252,211,77,0.05) 0%, transparent 60%),
    var(--bg-deep);
  font-family: var(--font-current);
  color: var(--text-primary);
  overflow: hidden;
}
.sh-screen-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.sh-body {
  padding: 14px 16px 120px;
  display: flex; flex-direction: column;
  gap: 18px;
}

/* Browse grid — 2-col mobile (ShopMobile), 3-col desktop main only.
   Do NOT use viewport @media: artboards are 420px but the browser viewport
   is often 1400px+, which would bleed desktop columns into mobile frames.
   Scope desktop columns to .sh-desktop-main. */
.sh-pack-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
/* SponsorPack — full row on mobile (2-col grid) for visibility */
.sh-pack-grid .sh-sponsor-pack {
  grid-column: 1 / -1;
}
.sh-desktop-main .sh-pack-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
.sh-desktop-main .sh-pack-grid .sh-sponsor-pack {
  grid-column: auto;
}

/* Bundle grid — 1-col mobile; 3-col desktop main column */
.sh-bundle-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}
.sh-desktop-main .sh-bundle-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* Gift-code list */
.sh-gift-list {
  display: flex; flex-direction: column;
  gap: 8px;
}

/* Desktop 2-col layout for Shop (mirrors pr-desktop pattern §13.11) */
.sh-desktop {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  grid-template-areas: "main side";
  gap: 24px;
  padding: 20px 24px 32px;
  max-width: 1400px;
  margin: 0 auto;
  align-items: start;
}
.sh-desktop-main {
  grid-area: main;
  display: flex; flex-direction: column;
  gap: 18px;
  min-width: 0;
}
.sh-desktop-side {
  grid-area: side;
  display: flex; flex-direction: column;
  gap: 16px;
  position: sticky;
  top: 16px;
  align-self: start;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
}
.sh-desktop-screen {
  position: relative;
  width: 100%; height: 100%;
  display: flex; flex-direction: column;
  background:
    radial-gradient(ellipse 80% 50% at 20% 10%, rgba(113,99,217,0.08) 0%, transparent 60%),
    radial-gradient(ellipse 70% 50% at 80% 90%, rgba(252,211,77,0.05) 0%, transparent 60%),
    var(--bg-deep);
  font-family: var(--font-current);
  color: var(--text-primary);
  overflow: hidden;
}
.sh-desktop-scroll { flex: 1 1 auto; overflow-y: auto; }

/* Section title spacing nudge — SectionHead has its own; add a gap below
   for the Shop grids specifically. */
.sh-body > section { display: flex; flex-direction: column; gap: 10px; }
.sh-desktop-main > section { display: flex; flex-direction: column; gap: 10px; }

/* ──────────────────────────────────────────────────────────────────────── */
/* SponsorPack (v1.5.1) — pack-shaped sponsor card inside the grid          */
/* ──────────────────────────────────────────────────────────────────────── */
.sh-sponsor-pack {
  position: relative;
  display: flex; flex-direction: column;
  gap: 10px;
  padding: 14px;
  border-radius: 18px;
  overflow: hidden;
  isolation: isolate;
  background:
    linear-gradient(155deg,
      color-mix(in srgb, var(--brand, var(--accent-primary)) 26%, transparent) 0%,
      color-mix(in srgb, var(--brand-2, var(--accent-primary-2)) 12%, transparent) 60%,
      rgba(255, 255, 255, 0.02) 100%),
    var(--bg-elevated);
  border: 1px solid color-mix(in srgb, var(--brand, var(--accent-primary)) 32%, transparent);
  box-shadow:
    0 18px 36px -22px color-mix(in srgb, var(--brand, var(--accent-primary)) 70%, transparent),
    inset 0 0 0 1px rgba(255, 255, 255, 0.04);
  min-height: 200px;
  cursor: default;
}
.sh-sponsor-pack-eye {
  display: inline-flex; align-self: flex-start;
  padding: 3px 8px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.34);
  color: color-mix(in srgb, var(--brand, var(--accent-primary)) 30%, white 70%);
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.sh-sponsor-pack-mark {
  position: absolute;
  top: 38px; inset-inline-end: 14px;
  width: 56px; height: 56px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-2) 100%);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display-current);
  font-size: 28px;
  font-weight: 800;
  color: white;
  box-shadow: 0 10px 24px -8px color-mix(in srgb, var(--brand) 70%, transparent);
  overflow: hidden;
}
.sh-sponsor-pack-mark:has(.sh-sponsor-pack-logo) {
  background: rgba(255, 255, 255, 0.96);
  padding: 6px;
}
.sh-sponsor-pack-logo {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.sh-sponsor-pack-body {
  display: flex; flex-direction: column;
  gap: 6px;
  padding-inline-end: 64px;
  margin-top: 2px;
}
.sh-sponsor-pack-name {
  margin: 0;
  font-family: var(--font-display-current);
  font-size: 16px;
  font-weight: 800;
  color: var(--text-primary);
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.sh-sponsor-pack-tag {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-secondary);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.sh-sponsor-pack-bonus {
  display: inline-flex; align-items: center; gap: 4px;
  align-self: flex-start;
  padding: 3px 8px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid color-mix(in srgb, var(--brand) 28%, transparent);
  color: var(--text-primary);
  font-size: 11px;
  font-weight: 700;
}
.sh-sponsor-pack-bonus svg { color: var(--tier-gold); }
.sh-sponsor-pack-cta {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 6px;
  margin-top: auto;
  padding: 9px 14px;
  border-radius: 10px;
  border: none;
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-2) 100%);
  color: white;
  font-family: var(--font-current);
  font-size: 12px;
  font-weight: 800;
  cursor: pointer;
  transition: transform 120ms ease, box-shadow 180ms ease;
  box-shadow: 0 8px 18px -8px color-mix(in srgb, var(--brand) 75%, transparent);
}
.sh-sponsor-pack-cta:hover  { box-shadow: 0 12px 26px -8px color-mix(in srgb, var(--brand) 80%, transparent); }
.sh-sponsor-pack-cta:active { transform: scale(0.98); }
.sh-sponsor-pack-shine {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: -1;
  background:
    radial-gradient(ellipse 60% 50% at 100% 0%,
      color-mix(in srgb, var(--brand) 30%, transparent) 0%,
      transparent 60%);
}

/* ──────────────────────────────────────────────────────────────────────── */
/* SponsorBox (v1.5.1) — full-width horizontal sponsor banner               */
/* ──────────────────────────────────────────────────────────────────────── */
.sh-sponsor-box {
  position: relative;
  display: grid;
  grid-template-columns: 56px 1fr auto;
  gap: 14px;
  align-items: center;
  padding: 14px 16px;
  border-radius: 16px;
  overflow: hidden;
  isolation: isolate;
  background:
    linear-gradient(115deg,
      color-mix(in srgb, var(--brand, var(--accent-primary)) 22%, transparent) 0%,
      color-mix(in srgb, var(--brand-2, var(--accent-primary-2)) 10%, transparent) 50%,
      rgba(255, 255, 255, 0.02) 100%),
    var(--bg-elevated);
  border: 1px solid color-mix(in srgb, var(--brand, var(--accent-primary)) 28%, transparent);
  box-shadow:
    0 12px 30px -16px color-mix(in srgb, var(--brand, var(--accent-primary)) 60%, transparent),
    inset 0 0 0 1px rgba(255, 255, 255, 0.04);
}
.sh-sponsor-box-mark {
  width: 56px; height: 56px;
  border-radius: 14px;
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-2) 100%);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display-current);
  font-size: 26px;
  font-weight: 800;
  color: white;
  box-shadow: 0 10px 22px -8px color-mix(in srgb, var(--brand) 70%, transparent);
  overflow: hidden;
}
.sh-sponsor-box-mark:has(.sh-sponsor-box-logo) {
  background: rgba(255, 255, 255, 0.96);
  padding: 6px;
}
.sh-sponsor-box-logo {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.sh-sponsor-box-body {
  display: flex; flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.sh-sponsor-box-eye {
  font-family: var(--font-mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--brand) 30%, white 70%);
}
.sh-sponsor-box-name {
  font-family: var(--font-display-current);
  font-size: 15px;
  font-weight: 800;
  color: var(--text-primary);
  letter-spacing: -0.01em;
}
.sh-sponsor-box-tag {
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-secondary);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.sh-sponsor-box-bonus {
  display: inline-flex; align-self: flex-start; align-items: center; gap: 4px;
  margin-top: 2px;
  padding: 2px 8px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.3);
  border: 1px solid color-mix(in srgb, var(--brand) 28%, transparent);
  font-size: 10px;
  font-weight: 700;
  color: var(--text-primary);
}
.sh-sponsor-box-bonus svg { color: var(--tier-gold); }
.sh-sponsor-box-cta {
  align-self: center;
  padding: 10px 16px;
  border-radius: 12px;
  border: none;
  background: linear-gradient(135deg, var(--brand) 0%, var(--brand-2) 100%);
  color: white;
  font-family: var(--font-current);
  font-size: 12px;
  font-weight: 800;
  cursor: pointer;
  white-space: nowrap;
  transition: transform 120ms ease, box-shadow 180ms ease;
  box-shadow: 0 8px 18px -8px color-mix(in srgb, var(--brand) 75%, transparent);
}
.sh-sponsor-box-cta:hover  { box-shadow: 0 12px 26px -8px color-mix(in srgb, var(--brand) 80%, transparent); }
.sh-sponsor-box-cta:active { transform: scale(0.98); }
.sh-sponsor-box-shine {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: -1;
  background:
    radial-gradient(ellipse 50% 60% at 100% 50%,
      color-mix(in srgb, var(--brand) 26%, transparent) 0%,
      transparent 70%);
}

/* Stack helper for ≥1 SponsorBox in a vertical column */
.sh-sponsor-stack {
  display: flex; flex-direction: column;
  gap: 10px;
}
@media (max-width: 480px) {
  .sh-sponsor-box {
    grid-template-columns: 48px 1fr;
    grid-template-areas:
      "mark body"
      "cta cta";
    row-gap: 10px;
  }
  .sh-sponsor-box-mark { grid-area: mark; width: 48px; height: 48px; }
  .sh-sponsor-box-body { grid-area: body; }
  .sh-sponsor-box-cta  { grid-area: cta; width: 100%; }
}

/* Stats strip — 3 StatTiles aligned in a row (mirrors pr-stats-grid) */
.sh-stats-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}
@media (max-width: 420px) {
  .sh-stats-grid { gap: 8px; }
}

/* ──────────────────────────────────────────────────────────────────────── */
/* PackOpenScene v1.5.2 — perf-first (opacity/transform only, no blur)     */
/* ──────────────────────────────────────────────────────────────────────── */
.sh-scene {
  position: fixed; inset: 0;
  z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-current);
  color: var(--text-primary);
  user-select: none;
  outline: none;
  overflow: hidden;
  contain: layout style paint;
}
.sh-scene.is-inline { position: absolute; }
.sh-scene-backdrop {
  position: absolute; inset: 0;
  background:
    radial-gradient(ellipse 80% 55% at 50% 30%,
      color-mix(in srgb, var(--scene-accent, var(--accent-primary)) 22%, transparent) 0%,
      transparent 62%),
    rgba(7, 7, 12, 0.96);
  animation: sh-scene-backdrop-in 320ms var(--ease-out) both;
}
@keyframes sh-scene-backdrop-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.sh-scene-stage {
  position: relative;
  width: min(420px, 100%);
  height: 100%;
  max-height: 100%;
  padding: 24px 20px 32px;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 20px;
  z-index: 1;
}
.sh-scene-stage--reveal { justify-content: center; gap: 14px; }
.sh-scene-stage--summary { gap: 18px; padding-top: 32px; }

/* Stage 1 · flash */
.sh-scene-flash {
  position: absolute; inset: 0;
  background: radial-gradient(circle at 50% 45%,
    color-mix(in srgb, var(--scene-accent, var(--accent-primary)) 65%, white 35%) 0%,
    transparent 55%);
  animation: sh-scene-flash 560ms cubic-bezier(0.25, 0, 0.2, 1) both;
  pointer-events: none;
}
@keyframes sh-scene-flash {
  0%   { opacity: 0; transform: scale(0.85); }
  30%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.12); }
}

/* Stage 2 · idle — glow ring + pack float (no filter:blur) */
.sh-scene-pack-wrap {
  position: relative;
  width: min(240px, 62%);
  aspect-ratio: 3 / 4;
}
.sh-scene-glow {
  position: absolute;
  inset: -12%;
  border-radius: 28px;
  background: radial-gradient(circle at 50% 50%,
    color-mix(in srgb, var(--scene-accent, var(--accent-primary)) 38%, transparent) 0%,
    transparent 68%);
  animation: sh-scene-glow-pulse 2.4s ease-in-out infinite;
  pointer-events: none;
}
@keyframes sh-scene-glow-pulse {
  0%, 100% { opacity: 0.4; }
  50%      { opacity: 0.85; }
}
.sh-scene-stage--idle .sh-scene-pack {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 20px 48px -18px var(--scene-glow, var(--accent-glow));
  animation: sh-scene-pack-in 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.sh-scene-stage--idle .sh-scene-pack .sh-pack-art { aspect-ratio: 3 / 4; }
@keyframes sh-scene-pack-in {
  from { opacity: 0; transform: translateY(28px) scale(0.88); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.sh-scene-meta {
  display: flex; flex-direction: column;
  align-items: center; text-align: center;
  gap: 8px;
  position: relative;
  z-index: 2;
}
.sh-scene-meta--bottom { gap: 6px; }
.sh-scene-title {
  margin: 0;
  font-family: var(--font-display-current);
  font-size: 22px;
  font-weight: 800;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  max-width: 22ch;
}
.sh-scene-sub {
  margin: 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
}
.sh-scene-arrow {
  display: inline-block;
  margin-top: 2px;
  font-size: 14px;
  color: color-mix(in srgb, var(--scene-accent, var(--accent-primary)) 80%, white 20%);
  animation: sh-scene-arrow-pulse 1.6s ease-in-out infinite;
}
@keyframes sh-scene-arrow-pulse {
  0%, 100% { transform: translateY(0);   opacity: 0.45; }
  50%      { transform: translateY(3px); opacity: 1; }
}

/* Stage 3 · reveal — pack falls away, card drops in tilted */
.sh-scene-pack-pop {
  position: absolute;
  left: 50%; top: 42%;
  width: min(220px, 58%);
  aspect-ratio: 3 / 4;
  transform: translate(-50%, -50%);
  border-radius: 20px;
  overflow: hidden;
  z-index: 1;
  pointer-events: none;
  animation: sh-scene-pack-drop 600ms cubic-bezier(0.45, 0, 0.85, 1) both;
}
@keyframes sh-scene-pack-drop {
  0%   { opacity: 1; transform: translate(-50%, -50%) rotate(0deg); }
  40%  { opacity: 0.9; transform: translate(-50%, calc(-50% + 24px)) rotate(5deg); }
  100% { opacity: 0; transform: translate(-50%, calc(-50% + 96px)) rotate(16deg); }
}
.sh-scene-pack-pop .sh-pack-art { aspect-ratio: 3 / 4; }

.sh-scene-drop-zone {
  position: relative;
  width: min(220px, 60%);
  min-height: min(280px, 52vh);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
}
.sh-scene-card-motion {
  width: 100%;
  display: flex;
  justify-content: center;
}
.sh-scene-card-motion-inner {
  transform-origin: center center;
  backface-visibility: hidden;
}
.sh-scene-card-motion.is-settled .sh-scene-card-motion-inner {
  opacity: 1;
  transform: translate3d(0, 0, 0) rotate(0deg) scale(0.82);
}
.sh-scene-card-motion.is-entering .sh-scene-card-motion-inner {
  animation: sh-scene-card-drop-in 760ms cubic-bezier(0.16, 1, 0.28, 1) forwards;
}
.sh-scene-card-motion.is-exiting .sh-scene-card-motion-inner {
  animation: sh-scene-card-drop-out 520ms cubic-bezier(0.42, 0, 0.78, 1) forwards;
}
/* Single transform track (incl. scale) — smooth ease-out landing, no mid keyframes */
@keyframes sh-scene-card-drop-in {
  0% {
    opacity: 0;
    transform: translate3d(14px, -88px, 0) rotate(var(--drop-tilt, -10deg)) scale(0.82);
  }
  100% {
    opacity: 1;
    transform: translate3d(0, 0, 0) rotate(0deg) scale(0.82);
  }
}
@keyframes sh-scene-card-drop-out {
  0% {
    opacity: 1;
    transform: translate3d(0, 0, 0) rotate(0deg) scale(0.82);
  }
  100% {
    opacity: 0;
    transform: translate3d(-12px, 88px, 0) rotate(var(--exit-tilt, 11deg)) scale(0.82);
  }
}

.sh-scene-progress {
  position: absolute;
  top: 18px; left: 50%; transform: translateX(-50%);
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.1);
  font-size: 12px;
  font-weight: 700;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
  z-index: 4;
}

/* Mythic banner (only shown over the first card of a mythic pack) */
.sh-scene-mythic-banner {
  display: inline-flex; align-items: center;
  padding: 6px 14px;
  border-radius: 999px;
  background: linear-gradient(120deg,
    color-mix(in srgb, var(--accent-primary) 70%, var(--tier-gold) 30%) 0%,
    color-mix(in srgb, var(--accent-primary) 40%, var(--tier-gold) 60%) 100%);
  color: white;
  font-family: var(--font-current);
  font-size: 12px; font-weight: 800;
  letter-spacing: 0.02em;
  box-shadow: 0 10px 28px -10px var(--accent-glow);
  animation: sh-scene-banner-in 480ms cubic-bezier(0.16, 1, 0.3, 1) 120ms both;
}
@keyframes sh-scene-banner-in {
  from { opacity: 0; transform: translateY(-6px) scale(0.94); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* Stage 4 · summary */
.sh-scene-sum-head {
  display: flex; flex-direction: column; align-items: center;
  gap: 8px; text-align: center;
  animation: sh-scene-sum-head 420ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes sh-scene-sum-head {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.sh-scene-sum-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  width: 100%;
  max-height: 60vh;
  overflow-y: auto;
  padding: 8px 0;
}
.sh-scene-sum-card {
  position: relative;
  display: flex; align-items: center; justify-content: center;
  padding: 4px;
  border-radius: 14px;
  background: linear-gradient(145deg,
    color-mix(in srgb, var(--c-accent, var(--accent-primary)) 18%, transparent),
    transparent 70%);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--c-accent, var(--accent-primary)) 24%, transparent),
              0 12px 28px -14px color-mix(in srgb, var(--c-accent, var(--accent-primary)) 60%, transparent);
  animation: sh-scene-sum-fade 260ms ease both;
}
.sh-scene-sum-card > * { transform: scale(0.78); transform-origin: center; }
.sh-scene-sum-card:nth-child(2)  { animation-delay: 35ms; }
.sh-scene-sum-card:nth-child(3)  { animation-delay: 70ms; }
.sh-scene-sum-card:nth-child(4)  { animation-delay: 105ms; }
.sh-scene-sum-card:nth-child(5)  { animation-delay: 140ms; }
.sh-scene-sum-card:nth-child(6)  { animation-delay: 175ms; }
.sh-scene-sum-card:nth-child(n+7){ animation-delay: 210ms; }
@keyframes sh-scene-sum-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.sh-scene-tally {
  display: inline-flex; flex-wrap: wrap; justify-content: center;
  gap: 6px;
  max-width: 100%;
}
.sh-scene-tally-chip {
  display: inline-flex; align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid color-mix(in srgb, var(--t-accent, var(--text-tertiary)) 26%, transparent);
  font-size: 11px;
  font-weight: 700;
  color: var(--text-secondary);
}
.sh-scene-tally-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--t-accent, var(--text-tertiary));
  box-shadow: 0 0 8px var(--t-accent, var(--text-tertiary));
}
.sh-scene-tally-count { color: var(--text-primary); }

.sh-scene-close-cta {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; max-width: 360px;
  padding: 14px 22px;
  border-radius: 14px;
  border: none;
  background: linear-gradient(135deg,
    var(--scene-accent, var(--accent-primary)) 0%,
    color-mix(in srgb, var(--scene-accent, var(--accent-primary)) 70%, black 30%) 100%);
  color: white;
  font-family: var(--font-current);
  font-size: 14px; font-weight: 800;
  cursor: pointer;
  box-shadow: 0 14px 36px -12px var(--scene-glow, var(--accent-glow));
  transition: transform 120ms ease, box-shadow 180ms ease;
}
.sh-scene-close-cta:hover  { box-shadow: 0 18px 44px -10px var(--scene-glow, var(--accent-glow)); }
.sh-scene-close-cta:active { transform: scale(0.98); }

/* ── Top-end chrome (always visible) ───────────────────────────────── */
.sh-scene-chrome {
  position: absolute;
  top: 14px; inset-inline-end: 14px;
  display: inline-flex; align-items: center; gap: 8px;
  z-index: 6;
}
.sh-scene-skip {
  appearance: none;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: var(--text-secondary);
  border-radius: 999px;
  padding: 6px 12px;
  font-size: 11px;
  font-weight: 700;
  font-family: var(--font-current);
  cursor: pointer;
  transition: color 120ms, background 120ms;
}
.sh-scene-skip:hover { color: var(--text-primary); background: rgba(255, 255, 255, 0.14); }
.sh-scene-close {
  appearance: none;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: var(--text-primary);
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background 120ms;
}
.sh-scene-close:hover { background: rgba(255, 255, 255, 0.16); }

/* Reduced motion */
.sh-scene.is-reduced .sh-scene-backdrop,
.sh-scene.is-reduced .sh-scene-flash,
.sh-scene.is-reduced .sh-scene-pack,
.sh-scene.is-reduced .sh-scene-glow,
.sh-scene.is-reduced .sh-scene-pack-pop,
.sh-scene.is-reduced .sh-scene-card-motion.is-entering .sh-scene-card-motion-inner,
.sh-scene.is-reduced .sh-scene-card-motion.is-exiting .sh-scene-card-motion-inner,
.sh-scene.is-reduced .sh-scene-sum-head,
.sh-scene.is-reduced .sh-scene-sum-card,
.sh-scene.is-reduced .sh-scene-arrow,
.sh-scene.is-reduced .sh-scene-mythic-banner {
  animation: none !important;
}
@media (prefers-reduced-motion: reduce) {
  .sh-scene-backdrop,
  .sh-scene-flash,
  .sh-scene-pack,
  .sh-scene-glow,
  .sh-scene-pack-pop,
  .sh-scene-card-motion.is-entering .sh-scene-card-motion-inner,
  .sh-scene-card-motion.is-exiting .sh-scene-card-motion-inner,
  .sh-scene-sum-head,
  .sh-scene-sum-card,
  .sh-scene-arrow,
  .sh-scene-mythic-banner {
    animation: none !important;
  }
}

/* ── Mobile tightening ────────────────────────────────────────────── */
@media (max-width: 420px) {
  .sh-scene-stage { padding: 18px 14px 24px; gap: 18px; }
  .sh-scene-title { font-size: 18px; }
  .sh-scene-sum-grid { gap: 10px; }
}
`;
const __shopPiecesStyle = document.createElement('style');
__shopPiecesStyle.textContent = SHOP_PIECES_CSS;
document.head.appendChild(__shopPiecesStyle);
