// shop-app.jsx — Mounts the Shop design canvas + Tweaks panel.
//
// Shop module v1.5 (Design Log §16.5). Mobile-first 420px + Desktop 1400px,
// using the canonical baseline §12 chrome (AppBar / DesktopTopbar /
// BottomNav / SectionHead / FilterChips / StatTile / HHEmptyState) +
// the page-specific Shop primitives from shop-pieces.jsx (PackCard,
// BundleCard, GiftCodeRow, PriceTag, RarityBadge, PackOpenScene).
//
// The pack-detail bottom sheet is built on `MyCardsDetailModal
// layout="sheetCustom"` with three slots (header/body/footer) — same
// canonical pattern as PredictionMatchSheet (§14.0 / §14.4).

const { useState: saUseState, useEffect: saUseEffect, useMemo: saUseMemo,
        useRef: saUseRef, useCallback: saUseCallback } = React;

// ─── Phone frame (re-uses the homehub one, but rebuilt here so the file is
// self-contained — same surface treatment as Home Hub / Prediction Hub) ──
function ShopPhone({ w = 420, h = 920, children }) {
  return (
    <div className="sh-phone" style={{ width: w, height: h }}>
      <div className="sh-phone-inner">{children}</div>
    </div>
  );
}

// ─── ShopOpenStage — artboard helper to preview PackOpenScene mid-flow ────
// Seeds the reward cards deterministically (seed=42) so artboards stay
// stable across reloads. Renders inline within the phone frame so the
// design canvas captures a static-looking moment of the scene.
function ShopOpenStage({ packId, stage = 'idle', revealIdx = 0,
                          locale = 'fa', forceReduced = false }) {
  const pack  = window.SHOP_PACKS?.find((p) => p.id === packId);
  const cards = saUseMemo(() => (pack && window.generateRewardCards
    ? window.generateRewardCards(pack, 42)
    : []), [packId]);
  if (!pack) return null;
  return (
    <window.PackOpenScene
      pack={pack}
      cards={cards}
      locale={locale}
      initialStage={stage}
      initialRevealIdx={revealIdx}
      reduced={forceReduced || undefined}
      inline={true}
      onClose={() => {}}/>
  );
}

// ─── Sheet hook ────────────────────────────────────────────────────────────
// Mirrors `usePRMatchSheet` from prediction-app.jsx — single source of
// truth for which pack is currently open in the bottom sheet.
function useShopSheet(initialId = null) {
  const [packs, setPacks] = saUseState(() => SHOP_PACKS.map((p) => ({ ...p })));
  const [bundles] = saUseState(() => SHOP_BUNDLES.map((b) => ({ ...b })));
  const [user, setUser]   = saUseState(() => ({ ...SHOP_USER, ownedPackIds: SHOP_USER.ownedPackIds.slice() }));
  const [sheetPackId, setSheetPackId] = saUseState(initialId);
  const [openingScene, setOpeningScene] = saUseState(null);  // { pack, cards } when opening

  const openSheet  = (packOrBundle) => {
    if (!packOrBundle) return;
    // Bundles open their own variant; for now, opening a bundle opens the
    // first member pack so the demo flow stays consistent.
    if (packOrBundle.packIds) {
      setSheetPackId(packOrBundle.packIds[0]);
    } else {
      setSheetPackId(packOrBundle.id);
    }
  };
  const closeSheet = () => setSheetPackId(null);
  const activePack = packs.find((p) => p.id === sheetPackId) || null;

  const buyPack = (pack) => {
    if (!pack) return;
    const can = canBuyPack(pack, user);
    if (!can.ok) return;
    const result = applyPurchase(pack, user);
    setUser((cur) => ({
      ...cur,
      coinBalance: result.newBalance,
      ownedPackIds: result.newOwnedIds,
      freePacksOpenedToday: result.newFreePacksOpened,
    }));
    // Mark pack as owned in local list so the card flips to its owned state
    setPacks((cur) => cur.map((p) =>
      p.id === pack.id ? { ...p, state: pack.isFree ? 'owned' : 'owned' } : p));
    // Generate reward cards and open the pack-open scene
    const rewardCards = generateRewardCards(pack);
    setOpeningScene({ pack, cards: rewardCards });
    closeSheet();
  };

  const closeOpeningScene = () => setOpeningScene(null);

  return { packs, bundles, user, sheetPackId, activePack, openSheet, closeSheet,
           buyPack, openingScene, closeOpeningScene };
}

// ─── PackDetailSheet ───────────────────────────────────────────────────────
// Wrapper around `MyCardsDetailModal layout="sheetCustom"` — exactly the
// same canonical bottom-sheet pattern shipped in Prediction Hub v1.3.
// Sheet anatomy:
//   • header = full pack art (4:3) + name + rarity badge + tagline + meta
//   • body   = description + reward odds rows + (optional) availability
//              expiry meta + (optional) sample reward preview row
//   • footer = state-aware CTA (Buy / Open / Locked / Already owned)
function PackDetailSheet({ pack, open, onClose, onBuy, locale = 'fa',
                            user, inline = true }) {
  if (!pack) return null;
  const ll = SHOP_I18N[locale];
  const rarity = pack.rarity;
  const palette = SHOP_RARITY[rarity] || {};
  const can = window.canBuyPack ? canBuyPack(pack, user) : { ok: true };

  // Sample reward preview — deterministic seeded to keep artboards stable
  const previewCards = saUseMemo(() => generateRewardCards(pack, 42).slice(0, Math.min(pack.cardCount, 4)),
                                  [pack.id]);

  const header = (
    <div className="sh-sheet-head">
      <div className="sh-sheet-art-wrap"
           style={{ '--pack-accent': pack.accent || palette.accent,
                    '--pack-glow':   palette.glow }}>
        <PackArt rarity={rarity}
                 accent={pack.accent}
                 themeTeam={pack.themeTeam}
                 artStyle={pack.artStyle}
                 coverImage={pack.coverImage}/>
      </div>
      <div className="sh-sheet-head-meta">
        <div className="sh-sheet-head-row">
          <RarityBadge locale={locale} rarity={rarity}/>
          {pack.badge && (() => {
            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' },
            }[pack.badge];
            return map ? (
              <span className={`sh-badge sh-badge--${map.tone}`}>{map.label}</span>
            ) : null;
          })()}
        </div>
        <h2 className="sh-sheet-head-title">{pack.name[locale]}</h2>
        <p className="sh-sheet-head-tagline">{pack.tagline[locale]}</p>
        <div className="sh-sheet-head-meta-row">
          <span className="sh-sheet-head-meta-item">
            {window.HHIcon && <window.HHIcon name="cards" size={12}/>}
            <span>{ll.cardCount(window.toLocaleDigits ? window.toLocaleDigits(pack.cardCount, locale) : pack.cardCount)}</span>
          </span>
          {pack.guaranteedTier && (
            <span className="sh-sheet-head-meta-item sh-sheet-head-meta-item--guaranteed">
              {window.HHIcon && <window.HHIcon name="check" size={12}/>}
              <span>{ll.guaranteedTier(
                pack.guaranteedTier === 'gold'     ? ll.tierGold :
                pack.guaranteedTier === 'platinum' ? ll.tierPlatinum :
                pack.guaranteedTier
              )}</span>
            </span>
          )}
        </div>
      </div>
    </div>
  );

  // Reward distribution rows (sorted high-to-low so platinum sits at top)
  const distOrder = ['platinum', 'gold', 'silver', 'bronze', 'fan', 'coach', 'null'];
  const distLabel = {
    platinum: ll.oddsPlatinum, gold: ll.oddsGold,
    silver:   ll.oddsSilver,   bronze: ll.oddsBronze,
    fan:      ll.oddsSpecial,  coach:  ll.oddsSpecial,
    null:     ll.oddsSpecial,
  };
  const distAccent = {
    platinum: 'var(--tier-platinum)', gold: 'var(--tier-gold)',
    silver:   'var(--tier-silver)',   bronze: 'var(--tier-bronze)',
    fan:      'var(--danger)',        coach:  'var(--success)',
    null:     'var(--accent-primary)',
  };

  const body = (
    <div className="sh-sheet-body-inner">
      <section className="sh-sheet-sec">
        <h3 className="sh-sheet-sec-title">{ll.sheetDescription}</h3>
        <p className="sh-sheet-desc">{pack.description[locale]}</p>
      </section>
      <section className="sh-sheet-sec">
        <h3 className="sh-sheet-sec-title">{ll.sheetRewards}</h3>
        <div className="sh-sheet-odds">
          {distOrder
            .filter((k) => pack.rewardDistribution[k] != null && pack.rewardDistribution[k] > 0)
            .map((k) => (
              <div key={k} className="sh-sheet-odds-row" style={{ '--odd-accent': distAccent[k] }}>
                <span className="sh-sheet-odds-dot" aria-hidden="true"></span>
                <span className="sh-sheet-odds-label">{distLabel[k]}</span>
                <span className="sh-sheet-odds-bar" aria-hidden="true">
                  <span style={{ width: `${Math.round(pack.rewardDistribution[k] * 100)}%` }}></span>
                </span>
                <span className="sh-sheet-odds-pct tabular-nums">
                  {ll.oddsPct(window.toLocaleDigits ? window.toLocaleDigits(Math.round(pack.rewardDistribution[k] * 100), locale) : Math.round(pack.rewardDistribution[k] * 100))}
                </span>
              </div>
            ))}
        </div>
      </section>
      {previewCards.length > 0 && (
        <section className="sh-sheet-sec">
          <h3 className="sh-sheet-sec-title">{ll.sheetWhatYouGet}</h3>
          <div className="sh-sheet-preview">
            {previewCards.map((card, i) => {
              const resolveName = (obj, prop) =>
                typeof obj?.[prop] === 'object' ? obj[prop][locale] : obj?.[prop];
              if (card.kind === 'player' && window.PlayerCard) {
                return (
                  <div key={i} className="sh-sheet-preview-card">
                    <window.PlayerCard
                      tier={card.props.tier}
                      locale={locale}
                      size="compact"
                      position={card.props.position}
                      overall={card.props.overall}
                      statKey={card.props.statKey}
                      statValue={card.props.statValue}
                      playerName={resolveName(card.props, 'playerName')}
                      teamName={resolveName(card.props, 'teamName')}
                      playerImage={card.props.playerImage}/>
                  </div>
                );
              }
              if (card.kind === 'fan' && window.FanCard) {
                return (
                  <div key={i} className="sh-sheet-preview-card">
                    <window.FanCard
                      locale={locale}
                      size="compact"
                      bonusValue={card.props.bonusValue}
                      team={resolveName(card.props, 'team')}
                      image={card.props.image}/>
                  </div>
                );
              }
              if (card.kind === 'coach' && window.CoachCard) {
                return (
                  <div key={i} className="sh-sheet-preview-card">
                    <window.CoachCard
                      locale={locale}
                      size="compact"
                      multiplier={card.props.multiplier}
                      coachName={resolveName(card.props, 'coachName')}
                      condition={resolveName(card.props, 'condition')}
                      image={card.props.image}/>
                  </div>
                );
              }
              if (card.kind === 'null' && window.NullCard) {
                return (
                  <div key={i} className="sh-sheet-preview-card">
                    <window.NullCard
                      locale={locale}
                      size="compact"
                      tier={card.props.tier}
                      overall={card.props.overall}
                      image={card.props.image}/>
                  </div>
                );
              }
              return null;
            })}
          </div>
        </section>
      )}
    </div>
  );

  // Footer — accent-gradient buy CTA per §13.9 (one of the "moments" where
  // accent gradient is canonical: "بازکردن پک"). Disabled / messaging
  // variants for locked / comingSoon / insufficient / owned.
  const footer = (() => {
    if (pack.state === 'locked') {
      return (
        <div className="sh-sheet-foot sh-sheet-foot--info">
          {window.HHIcon && <window.HHIcon name="lock" size={14}/>}
          <span>{pack.unlockHint ? pack.unlockHint[locale] : ll.btnLocked('')}</span>
        </div>
      );
    }
    if (pack.state === 'comingSoon') {
      return (
        <div className="sh-sheet-foot sh-sheet-foot--info">
          {window.HHIcon && <window.HHIcon name="clock" size={14}/>}
          <span>{ll.btnComingSoon}</span>
        </div>
      );
    }
    if (!can.ok && can.reason === 'insufficient') {
      return (
        <div className="sh-sheet-foot sh-sheet-foot--insufficient">
          <div className="sh-sheet-foot-msg">
            {window.HHIcon && <window.HHIcon name="coin" size={14}/>}
            <span>{ll.insufficientCoins}</span>
          </div>
          <button type="button" className="sh-sheet-cta sh-sheet-cta--ghost"
                  onClick={() => { /* would route to coin shop */ }}>
            <span>{ll.insufficientCoinsCta}</span>
          </button>
        </div>
      );
    }
    if (pack.state === 'owned' && pack.isFree) {
      return (
        <button type="button" className="sh-sheet-cta sh-sheet-cta--primary"
                onClick={() => onBuy && onBuy(pack)}>
          <span className="sh-sheet-cta-label">{ll.btnOpenFree}</span>
        </button>
      );
    }
    if (pack.state === 'owned') {
      return (
        <div className="sh-sheet-foot sh-sheet-foot--info">
          {window.HHIcon && <window.HHIcon name="check" size={14}/>}
          <span>{ll.btnOwned}</span>
        </div>
      );
    }
    return (
      <button type="button" className="sh-sheet-cta sh-sheet-cta--primary"
              onClick={() => onBuy && onBuy(pack)}>
        <span className="sh-sheet-cta-label">{ll.btnBuy}</span>
        <span className="sh-sheet-cta-price">
          <PriceTag locale={locale} value={pack.price} wasPrice={pack.priceWas} tone="accent"/>
        </span>
      </button>
    );
  })();

  const mobileHeight = inline ? '92%' : '92vh';
  if (!window.MyCardsDetailModal) return null;
  return (
    <window.MyCardsDetailModal
      open={open}
      onClose={onClose}
      locale={locale}
      layout="sheetCustom"
      mobileHeight={mobileHeight}
      swipeToDismiss={true}
      accent={pack.accent || palette.accent}
      inline={inline}
      header={header}
      body={body}
      footer={footer}
    />
  );
}

// ─── ShopMobile — full mobile screen (420×) ────────────────────────────────
function ShopMobile({ locale = 'fa', dateMode = 'hybrid', forceEmpty = false,
                       defaultFilter = 'all', sheetPackId = null }) {
  const sheet = useShopSheet(sheetPackId);
  const [filter, setFilter] = saUseState(defaultFilter);
  const ll = SHOP_I18N[locale];

  const filtered = forceEmpty
    ? []
    : (filter === 'all' ? sheet.packs : sheet.packs.filter((p) => p.category === filter));
  const featured = filtered.filter((p) => p.rarity === 'mythic' || p.rarity === 'legendary');
  const rest     = filtered.filter((p) => p.rarity !== 'mythic' && p.rarity !== 'legendary');

  // Sponsor placements — `pack` rows are injected into the grid; `box` rows
  // are rendered between sections (mobile) or in the sidebar (desktop).
  const sponsorList = (window.SHOP_SPONSORS || []);
  const sponsorPack = sponsorList.find((s) => s.placement === 'pack');
  const sponsorBoxes = sponsorList.filter((s) => s.placement === 'box');

  const counts = saUseMemo(() => {
    const c = { all: sheet.packs.length, weekly: 0, seasonal: 0, special: 0, promo: 0 };
    sheet.packs.forEach((p) => { if (c[p.category] != null) c[p.category]++; });
    return c;
  }, [sheet.packs]);

  const chips = SHOP_CATEGORIES.map((cat) => ({
    key: cat.id,
    label: ll[cat.i18nKey],
    count: counts[cat.id],
  }));

  return (
    <div className="sh-screen">
      {window.AppBar && (
        <window.AppBar locale={locale}
                       user={{ ...window.HH_USER, coinBalance: sheet.user.coinBalance, level: sheet.user.level }}
                       density="compact"
                       avatarSide="start"
                       showStreak={false} showXP={true}
                       notifications={1}/>
      )}
      <div className="sh-screen-scroll">
        <div className="sh-body">
          <window.CoinPackTile locale={locale}
                               coinBalance={sheet.user.coinBalance}
                               freePacks={(SHOP_PACKS.find((p) => p.isFree)?.freePerDay || 0) - sheet.user.freePacksOpenedToday}
                               variant="hero"
                               ctaLabel={ll.coinHeroAction}
                               freeHint={ll.coinHeroFreeHint(window.toLocaleDigits ? window.toLocaleDigits(Math.max(0, (SHOP_PACKS.find((p) => p.isFree)?.freePerDay || 0) - sheet.user.freePacksOpenedToday), locale) : '')}
                               levelHint={ll.coinHeroLevel(window.toLocaleDigits ? window.toLocaleDigits(sheet.user.level, locale) : sheet.user.level)}
                               onCta={() => { /* would route to coin shop */ }}/>

          {window.FilterChips && (
            <window.FilterChips chips={chips} value={filter} onChange={setFilter} locale={locale}/>
          )}

          {filtered.length === 0 && window.HHEmptyState && (
            <window.HHEmptyState locale={locale}
                                 glyphIcon="gift"
                                 title={ll.emptyTitle}
                                 body={ll.emptyBody}
                                 ctaLabel={ll.emptyCta}
                                 ctaIcon="sparkle"
                                 onCta={() => setFilter('all')}
                                 secondaryCtaLabel={ll.emptySecondaryCta}
                                 secondaryCtaIcon="bell"
                                 onSecondaryCta={() => {}}/>
          )}

          {featured.length > 0 && (
            <section>
              {window.SectionHead && <window.SectionHead title={ll.secFeatured} sub={ll.secFeaturedSub}/>}
              <div className="sh-pack-grid">
                {featured.map((p) => (
                  <PackCard key={p.id} pack={p} locale={locale}
                            user={sheet.user}
                            onOpenDetails={(pk) => sheet.openSheet(pk)}/>
                ))}
                {sponsorPack && !forceEmpty && (
                  <window.SponsorPack locale={locale} sponsor={sponsorPack}/>
                )}
              </div>
            </section>
          )}

          {rest.length > 0 && (
            <section>
              {window.SectionHead && <window.SectionHead title={ll.secAll}/>}
              <div className="sh-pack-grid">
                {rest.map((p) => (
                  <PackCard key={p.id} pack={p} locale={locale}
                            user={sheet.user}
                            onOpenDetails={(pk) => sheet.openSheet(pk)}/>
                ))}
              </div>
            </section>
          )}

          {/* Sponsor banners — full-width horizontal between sections on mobile */}
          {sponsorBoxes.length > 0 && !forceEmpty && (
            <section>
              {window.SectionHead && <window.SectionHead title={ll.secSponsors} sub={ll.secSponsorsSub}/>}
              <div className="sh-sponsor-stack">
                {sponsorBoxes.map((s) => (
                  <window.SponsorBox key={s.id} locale={locale} sponsor={s}/>
                ))}
              </div>
            </section>
          )}

          {sheet.bundles.length > 0 && !forceEmpty && (
            <section>
              {window.SectionHead && <window.SectionHead title={ll.secBundles} sub={ll.secBundlesSub}/>}
              <div className="sh-bundle-grid">
                {sheet.bundles.map((b) => (
                  <BundleCard key={b.id} bundle={b} packs={sheet.packs} locale={locale}
                              onOpenDetails={(bn) => sheet.openSheet(bn)}/>
                ))}
              </div>
            </section>
          )}

          {!forceEmpty && (
            <section>
              {window.SectionHead && <window.SectionHead title={ll.secGiftCodes} sub={ll.secGiftCodesSub}/>}
              <window.GiftCodeInput locale={locale}
                                    onRedeem={(res) => res?.pack && sheet.openSheet(res.pack)}/>
              <div className="sh-gift-list" style={{ marginTop: 8 }}>
                {SHOP_GIFT_CODES.map((g) => (
                  <GiftCodeRow key={g.code} code={g} locale={locale}
                               onRedeem={(c) => {
                                 const res = window.redeemGiftCode(c.code, sheet.user);
                                 if (res?.pack) sheet.openSheet(res.pack);
                               }}/>
                ))}
              </div>
            </section>
          )}
        </div>
      </div>
      {window.BottomNav && (
        <window.BottomNav locale={locale} activeTab="shop"/>
      )}

      <PackDetailSheet pack={sheet.activePack}
                       open={!!sheet.sheetPackId}
                       onClose={sheet.closeSheet}
                       onBuy={sheet.buyPack}
                       locale={locale}
                       user={sheet.user}
                       inline={true}/>

      {sheet.openingScene && window.PackOpenScene && (
        <window.PackOpenScene pack={sheet.openingScene.pack}
                              cards={sheet.openingScene.cards}
                              locale={locale}
                              onClose={sheet.closeOpeningScene}
                              inline={true}/>
      )}
    </div>
  );
}

// ─── ShopDesktop — 2-col layout (1400×) ────────────────────────────────────
function ShopDesktop({ locale = 'fa', defaultFilter = 'all', sheetPackId = null }) {
  const sheet = useShopSheet(sheetPackId);
  const [filter, setFilter] = saUseState(defaultFilter);
  const ll = SHOP_I18N[locale];

  const filtered = filter === 'all' ? sheet.packs : sheet.packs.filter((p) => p.category === filter);
  const featured = filtered.filter((p) => p.rarity === 'mythic' || p.rarity === 'legendary');
  const rest     = filtered.filter((p) => p.rarity !== 'mythic' && p.rarity !== 'legendary');

  const counts = saUseMemo(() => {
    const c = { all: sheet.packs.length, weekly: 0, seasonal: 0, special: 0, promo: 0 };
    sheet.packs.forEach((p) => { if (c[p.category] != null) c[p.category]++; });
    return c;
  }, [sheet.packs]);
  const chips = SHOP_CATEGORIES.map((cat) => ({ key: cat.id, label: ll[cat.i18nKey], count: counts[cat.id] }));

  const sponsorList = (window.SHOP_SPONSORS || []);
  const sponsorPack = sponsorList.find((s) => s.placement === 'pack');
  const sponsorBoxes = sponsorList.filter((s) => s.placement === 'box');

  return (
    <div className="sh-desktop-screen">
      {window.DesktopTopbar && (
        <window.DesktopTopbar locale={locale}
                              user={{ ...window.HH_USER, coinBalance: sheet.user.coinBalance, level: sheet.user.level }}
                              activeTab="shop"/>
      )}
      <div className="sh-desktop-scroll">
        <div className="sh-desktop">
          <div className="sh-desktop-main">
            <window.CoinPackTile locale={locale}
                                 coinBalance={sheet.user.coinBalance}
                                 freePacks={(SHOP_PACKS.find((p) => p.isFree)?.freePerDay || 0) - sheet.user.freePacksOpenedToday}
                                 variant="hero"
                                 ctaLabel={ll.coinHeroAction}
                                 freeHint={ll.coinHeroFreeHint(window.toLocaleDigits ? window.toLocaleDigits(Math.max(0, (SHOP_PACKS.find((p) => p.isFree)?.freePerDay || 0) - sheet.user.freePacksOpenedToday), locale) : '')}
                                 levelHint={ll.coinHeroLevel(window.toLocaleDigits ? window.toLocaleDigits(sheet.user.level, locale) : sheet.user.level)}
                                 onCta={() => {}}/>
            {window.FilterChips && (
              <window.FilterChips chips={chips} value={filter} onChange={setFilter} locale={locale}/>
            )}
            {featured.length > 0 && (
              <section>
                {window.SectionHead && <window.SectionHead title={ll.secFeatured} sub={ll.secFeaturedSub}/>}
                <div className="sh-pack-grid">
                  {featured.map((p) => (
                    <PackCard key={p.id} pack={p} locale={locale} user={sheet.user}
                              onOpenDetails={(pk) => sheet.openSheet(pk)}/>
                  ))}
                  {sponsorPack && (
                    <window.SponsorPack locale={locale} sponsor={sponsorPack}/>
                  )}
                </div>
              </section>
            )}
            {rest.length > 0 && (
              <section>
                {window.SectionHead && <window.SectionHead title={ll.secAll}/>}
                <div className="sh-pack-grid">
                  {rest.map((p) => (
                    <PackCard key={p.id} pack={p} locale={locale} user={sheet.user}
                              onOpenDetails={(pk) => sheet.openSheet(pk)}/>
                  ))}
                </div>
              </section>
            )}
            {sheet.bundles.length > 0 && (
              <section>
                {window.SectionHead && <window.SectionHead title={ll.secBundles} sub={ll.secBundlesSub}/>}
                <div className="sh-bundle-grid">
                  {sheet.bundles.map((b) => (
                    <BundleCard key={b.id} bundle={b} packs={sheet.packs} locale={locale}
                                onOpenDetails={(bn) => sheet.openSheet(bn)}/>
                  ))}
                </div>
              </section>
            )}
          </div>
          <aside className="sh-desktop-side">
            <section>
              {window.SectionHead && <window.SectionHead title={ll.secGiftCodes} sub={ll.secGiftCodesSub}/>}
              <window.GiftCodeInput locale={locale}
                                    onRedeem={(res) => res?.pack && sheet.openSheet(res.pack)}/>
              <div className="sh-gift-list" style={{ marginTop: 8 }}>
                {SHOP_GIFT_CODES.map((g) => (
                  <GiftCodeRow key={g.code} code={g} locale={locale}
                               onRedeem={(c) => {
                                 const res = window.redeemGiftCode(c.code, sheet.user);
                                 if (res?.pack) sheet.openSheet(res.pack);
                               }}/>
                ))}
              </div>
            </section>
            {sponsorBoxes.length > 0 && (
              <section>
                {window.SectionHead && <window.SectionHead title={ll.secSponsors} sub={ll.secSponsorsSub}/>}
                <div className="sh-sponsor-stack">
                  {sponsorBoxes.map((s) => (
                    <window.SponsorBox key={s.id} locale={locale} sponsor={s}/>
                  ))}
                </div>
              </section>
            )}
          </aside>
        </div>
      </div>

      <PackDetailSheet pack={sheet.activePack}
                       open={!!sheet.sheetPackId}
                       onClose={sheet.closeSheet}
                       onBuy={sheet.buyPack}
                       locale={locale}
                       user={sheet.user}
                       inline={true}/>

      {sheet.openingScene && window.PackOpenScene && (
        <window.PackOpenScene pack={sheet.openingScene.pack}
                              cards={sheet.openingScene.cards}
                              locale={locale}
                              onClose={sheet.closeOpeningScene}
                              inline={true}/>
      )}
    </div>
  );
}

// ─── App entry ─────────────────────────────────────────────────────────────
const SHOP_TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "locale": "fa",
  "reducedMotion": false
}/*EDITMODE-END*/;

function ShopApp() {
  const [t, setTweak] = useTweaks(SHOP_TWEAK_DEFAULTS);

  saUseEffect(() => {
    document.documentElement.lang = t.locale;
    document.documentElement.dir  = t.locale === 'en' ? 'ltr' : 'rtl';
  }, [t.locale]);

  // Honor the reduced-motion tweak (mostly affects PackOpenScene later)
  saUseEffect(() => {
    document.documentElement.classList.toggle('sh-reduce-motion', !!t.reducedMotion);
  }, [t.reducedMotion]);

  return (
    <React.Fragment>
      <DesignCanvas>
        {/* ── Mobile ── */}
        <DCSection id="mobile-shop"
                   title="Mobile · Shop (موبایل)"
                   subtitle="چینش mobile-first: Coin hero → FilterChips → Featured grid → All packs → Bundles → Gift codes. Bottom sheet جزئیات هر پک با sheetCustom canonical.">
          <DCArtboard id="m-default" label="A · Default (موبایل ۴۲۰) — با sponsor section" width={420} height={2600}>
            <ShopPhone w={420} h={2600}>
              <ShopMobile locale={t.locale}/>
            </ShopPhone>
          </DCArtboard>
          <DCArtboard id="m-sheet" label="B · Pack detail · Mythic (sheetCustom 92%)" width={420} height={920}>
            <ShopPhone w={420} h={920}>
              <ShopMobile locale={t.locale} sheetPackId="pk-mythic-launch"/>
            </ShopPhone>
          </DCArtboard>
          <DCArtboard id="m-empty" label="C · Empty state (فیلتر بدون نتیجه)" width={420} height={920}>
            <ShopPhone w={420} h={920}>
              <ShopMobile locale={t.locale} forceEmpty={true}/>
            </ShopPhone>
          </DCArtboard>
        </DCSection>

        {/* ── Desktop ── */}
        <DCSection id="desktop-shop"
                   title="Desktop · Shop ۱۴۰۰"
                   subtitle="Layout 2-col با grid-template-areas (همان pattern v1.3.2): main = hero + filters + grids; side = gift codes + sponsor.">
          <DCArtboard id="d-default" label="A · Default" width={1400} height={1400}>
            <div className="sh-desktop-frame">
              <ShopDesktop locale={t.locale}/>
            </div>
          </DCArtboard>
          <DCArtboard id="d-sheet" label="B · Pack detail · Derby (sheetCustom over desktop)" width={1400} height={1100}>
            <div className="sh-desktop-frame">
              <ShopDesktop locale={t.locale} sheetPackId="pk-derby-clas"/>
            </div>
          </DCArtboard>
        </DCSection>

        {/* ── PackOpenScene · Phase 4 ── */}
        <DCSection id="pack-open-scene"
                   title="PackOpenScene · صحنه‌ی باز کردن پک"
                   subtitle="Fullscreen overlay با ۴ stage: flash → idle → reveal (tear+flip per card) → summary. tap/Enter پیشروی، Esc بستن، Skip به summary. Mythic burst + reduced-motion fallback.">
          <DCArtboard id="m-open-idle" label="A · Idle · Mythic pack mid-float" width={420} height={760}>
            <ShopPhone w={420} h={760}>
              <ShopOpenStage stage="idle" packId="pk-mythic-launch" locale={t.locale}/>
            </ShopPhone>
          </DCArtboard>
          <DCArtboard id="m-open-reveal-mythic" label="B · Reveal · Card drop (mythic pack)" width={420} height={760}>
            <ShopPhone w={420} h={760}>
              <ShopOpenStage stage="reveal" packId="pk-mythic-launch" revealIdx={0} locale={t.locale}/>
            </ShopPhone>
          </DCArtboard>
          <DCArtboard id="m-open-reveal-mid" label="C · Reveal · Card 3 of 5 (gold)" width={420} height={760}>
            <ShopPhone w={420} h={760}>
              <ShopOpenStage stage="reveal" packId="pk-mw28-gold" revealIdx={2} locale={t.locale}/>
            </ShopPhone>
          </DCArtboard>
          <DCArtboard id="m-open-summary" label="D · Summary · 5 cards + tally + close CTA" width={420} height={920}>
            <ShopPhone w={420} h={920}>
              <ShopOpenStage stage="summary" packId="pk-mw28-gold" locale={t.locale}/>
            </ShopPhone>
          </DCArtboard>
          <DCArtboard id="m-open-summary-mythic" label="E · Summary · Mythic pack (8 cards)" width={420} height={1040}>
            <ShopPhone w={420} h={1040}>
              <ShopOpenStage stage="summary" packId="pk-mythic-launch" locale={t.locale}/>
            </ShopPhone>
          </DCArtboard>
          <DCArtboard id="m-open-reduced" label="F · prefers-reduced-motion · instant summary" width={420} height={920}>
            <ShopPhone w={420} h={920}>
              <ShopOpenStage stage="summary" packId="pk-mw28-silver" locale={t.locale} forceReduced={true}/>
            </ShopPhone>
          </DCArtboard>
        </DCSection>

        {/* ── Primitives reference ── */}
        <DCSection id="primitives-shop"
                   title="Primitives · مرجع برای reuse"
                   subtitle="PackCard variants, BundleCard, GiftCodeRow, PriceTag, RarityBadge — هر کدوم در artboard مستقل برای کپی/ادپتیشن.">
          <DCArtboard id="p-pack-rarity" label="PackCard · ۵ rarity tier" width={840} height={520}>
            <ShopPrimitiveStack>
              <ShopPrimitiveLabel text="Common · Rare · Epic · Legendary · Mythic"/>
              <div className="sh-pack-grid" style={{ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))' }}>
                <PackCard pack={SHOP_PACKS.find((p) => p.id === 'pk-mw28-bronze')} locale={t.locale} user={SHOP_USER}/>
                <PackCard pack={SHOP_PACKS.find((p) => p.id === 'pk-mw28-silver')} locale={t.locale} user={SHOP_USER}/>
                <PackCard pack={SHOP_PACKS.find((p) => p.id === 'pk-mw28-gold')} locale={t.locale} user={SHOP_USER}/>
                <PackCard pack={SHOP_PACKS.find((p) => p.id === 'pk-derby-clas')} locale={t.locale} user={SHOP_USER}/>
              </div>
              <div style={{ height: 12 }}/>
              <ShopPrimitiveLabel text="Mythic · با accent gradient + glow"/>
              <div style={{ maxWidth: 360 }}>
                <PackCard pack={SHOP_PACKS.find((p) => p.id === 'pk-mythic-launch')} locale={t.locale} user={SHOP_USER}/>
              </div>
            </ShopPrimitiveStack>
          </DCArtboard>
          <DCArtboard id="p-pack-states" label="PackCard · ۴ state (available/owned/locked/team)" width={840} height={520}>
            <ShopPrimitiveStack>
              <div className="sh-pack-grid" style={{ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))' }}>
                <PackCard pack={SHOP_PACKS.find((p) => p.id === 'pk-mw28-silver')} locale={t.locale} user={SHOP_USER}/>
                <PackCard pack={SHOP_PACKS.find((p) => p.id === 'pk-newuser')} locale={t.locale} user={SHOP_USER}/>
                <PackCard pack={SHOP_PACKS.find((p) => p.id === 'pk-locked-elite')} locale={t.locale} user={SHOP_USER}/>
                <PackCard pack={SHOP_PACKS.find((p) => p.id === 'pk-team-per')} locale={t.locale} user={SHOP_USER}/>
              </div>
            </ShopPrimitiveStack>
          </DCArtboard>
          <DCArtboard id="p-bundles" label="BundleCard · ۳ variant" width={840} height={520}>
            <ShopPrimitiveStack>
              <div className="sh-bundle-grid" style={{ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))' }}>
                {SHOP_BUNDLES.map((b) => (
                  <BundleCard key={b.id} bundle={b} packs={SHOP_PACKS} locale={t.locale}/>
                ))}
              </div>
            </ShopPrimitiveStack>
          </DCArtboard>
          <DCArtboard id="p-gifts" label="GiftCodeInput + GiftCodeRow · ۳ state" width={520} height={360}>
            <ShopPrimitiveStack>
              <window.GiftCodeInput locale={t.locale}/>
              <div style={{ height: 8 }}/>
              <div className="sh-gift-list">
                {SHOP_GIFT_CODES.map((g) => (
                  <GiftCodeRow key={g.code} code={g} locale={t.locale}/>
                ))}
              </div>
            </ShopPrimitiveStack>
          </DCArtboard>
          <DCArtboard id="p-sponsors" label="SponsorPack + SponsorBox (v1.5.1)" width={840} height={620}>
            <ShopPrimitiveStack>
              <ShopPrimitiveLabel text="SponsorPack · پک‌شکل داخل grid"/>
              <div className="sh-pack-grid" style={{ gridTemplateColumns: 'repeat(3, minmax(0, 1fr))' }}>
                <PackCard pack={SHOP_PACKS.find((p) => p.id === 'pk-mw28-gold')} locale={t.locale} user={SHOP_USER}/>
                <window.SponsorPack locale={t.locale} sponsor={window.SHOP_SPONSORS?.find((s) => s.placement === 'pack')}/>
                <PackCard pack={SHOP_PACKS.find((p) => p.id === 'pk-mw28-silver')} locale={t.locale} user={SHOP_USER}/>
              </div>
              <div style={{ height: 18 }}/>
              <ShopPrimitiveLabel text="SponsorBox · بنر پهن"/>
              <div className="sh-sponsor-stack">
                {(window.SHOP_SPONSORS || []).filter((s) => s.placement === 'box').map((s) => (
                  <window.SponsorBox key={s.id} locale={t.locale} sponsor={s}/>
                ))}
              </div>
            </ShopPrimitiveStack>
          </DCArtboard>
          <DCArtboard id="p-atoms" label="PriceTag + RarityBadge · atoms" width={520} height={300}>
            <ShopPrimitiveStack>
              <ShopPrimitiveLabel text="PriceTag · inline · large · was/now · free"/>
              <div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', alignItems: 'baseline' }}>
                <PriceTag locale={t.locale} value={500} variant="inline" tone="gold"/>
                <PriceTag locale={t.locale} value={2500} wasPrice={3300} variant="inline" tone="gold"/>
                <PriceTag locale={t.locale} value={5500} variant="large" tone="accent"/>
                <PriceTag locale={t.locale} isFree={true} variant="inline"/>
              </div>
              <div style={{ height: 14 }}/>
              <ShopPrimitiveLabel text="RarityBadge · 5 tier"/>
              <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                {['common', 'rare', 'epic', 'legendary', 'mythic'].map((r) => (
                  <RarityBadge key={r} locale={t.locale} rarity={r}/>
                ))}
              </div>
            </ShopPrimitiveStack>
          </DCArtboard>
        </DCSection>
      </DesignCanvas>

      <TweaksPanel title="Tweaks · Shop">
        <TweakSection label="نمایش">
          <TweakRadio label="زبان" value={t.locale}
            options={[
              { value: 'fa', label: 'فا' },
              { value: 'en', label: 'EN' },
              { value: 'ar', label: 'ع' },
            ]}
            onChange={(v) => setTweak('locale', v)}/>
          <TweakToggle label="Reduced motion (شبیه‌سازی prefers-reduced-motion)"
                       value={t.reducedMotion}
                       onChange={(v) => setTweak('reducedMotion', v)}/>
        </TweakSection>
      </TweaksPanel>
    </React.Fragment>
  );
}

// ─── Primitive showcase helpers ────────────────────────────────────────────
function ShopPrimitiveStack({ children }) {
  return (
    <div style={{
      width: '100%', height: '100%',
      padding: 18,
      overflow: 'auto',
      background: 'var(--bg-deep)',
      display: 'flex', flexDirection: 'column',
    }}>{children}</div>
  );
}
function ShopPrimitiveLabel({ text }) {
  return (
    <div style={{
      fontSize: 10, color: 'var(--text-tertiary)',
      marginBottom: 8,
      fontFamily: 'JetBrains Mono, ui-monospace, monospace',
      letterSpacing: '0.04em',
    }}>{text}</div>
  );
}

// ─── Exports for unified app shell (Phase 1) ─────────────────────────────────
Object.assign(window, { ShopMobile, ShopDesktop, ShopPhone });

const __shRoot = document.getElementById('root');
if (__shRoot && !window.__APP_SHELL_MODE) {
  ReactDOM.createRoot(__shRoot).render(<ShopApp/>);
}

// ─── App-level CSS ────────────────────────────────────────────────────────
const SHOP_APP_CSS = `
.sh-phone {
  position: relative;
  background: var(--bg-deep);
  overflow: hidden;
  border-radius: 0;
}
.sh-phone-inner {
  position: absolute; inset: 0;
  overflow: hidden;
  display: flex; flex-direction: column;
}
.sh-phone-inner > * { flex: 1 1 auto; min-height: 0; }
.sh-desktop-frame {
  width: 100%; height: 100%;
  overflow: hidden;
  background: var(--bg-deep);
  color: var(--text-primary);
  display: flex; flex-direction: column;
}

/* PackDetailSheet — header / body / odds / preview ────────────────────── */
.sh-sheet-head {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: 12px;
  padding: 4px 0 8px;
  align-items: center;
}
.sh-sheet-art-wrap {
  width: 100%;
  aspect-ratio: 1 / 1;
  position: relative;
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 14px 36px -14px var(--pack-glow, var(--accent-glow));
}
.sh-sheet-art-wrap .sh-pack-art { aspect-ratio: 1 / 1; }
.sh-sheet-head-meta {
  display: flex; flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.sh-sheet-head-row {
  display: inline-flex; align-items: center; gap: 8px;
  flex-wrap: wrap;
}
.sh-sheet-head-title {
  margin: 0;
  font-family: var(--font-display-current);
  font-size: 20px;
  font-weight: 800;
  color: var(--text-primary);
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.sh-sheet-head-tagline {
  margin: 0;
  font-size: 12px;
  color: var(--text-secondary);
  line-height: 1.4;
}
.sh-sheet-head-meta-row {
  display: inline-flex; align-items: center; gap: 12px;
  margin-top: 4px;
}
.sh-sheet-head-meta-item {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-tertiary);
}
.sh-sheet-head-meta-item--guaranteed { color: var(--success); }
.sh-sheet-head-meta-item--guaranteed svg { color: var(--success); }

.sh-sheet-body-inner {
  display: flex; flex-direction: column;
  gap: 18px;
  padding: 8px 0;
  font-family: var(--font-current);
}
.sh-sheet-sec { display: flex; flex-direction: column; gap: 8px; }
.sh-sheet-sec-title {
  margin: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.sh-sheet-desc {
  margin: 0;
  font-size: 13px;
  line-height: 1.7;
  color: var(--text-secondary);
}

.sh-sheet-odds {
  display: flex; flex-direction: column;
  gap: 8px;
}
.sh-sheet-odds-row {
  display: grid;
  grid-template-columns: 8px auto 1fr 36px;
  align-items: center;
  gap: 10px;
}
.sh-sheet-odds-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--odd-accent, var(--accent-primary));
  box-shadow: 0 0 8px var(--odd-accent, var(--accent-primary));
}
.sh-sheet-odds-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary);
}
.sh-sheet-odds-bar {
  position: relative;
  height: 5px;
  border-radius: 999px;
  background: rgba(255,255,255,0.06);
  overflow: hidden;
}
.sh-sheet-odds-bar > span {
  display: block;
  height: 100%;
  background: var(--odd-accent, var(--accent-primary));
  border-radius: 999px;
}
.sh-sheet-odds-pct {
  font-size: 11px;
  font-weight: 700;
  color: var(--text-primary);
  text-align: end;
}

.sh-sheet-preview {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  align-items: start;
}
.sh-sheet-preview-card {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  width: 100%;
  min-width: 0;
  overflow: visible;
}
/* Compact cards at natural aspect ratio — no scale clip */
.sh-sheet-preview-card .pc {
  width: 100%;
  max-width: 168px;
  margin-inline: auto;
}

/* PackDetailSheet — footer CTA (accent gradient §13.9) ────────────────── */
.sh-sheet-cta {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 12px;
  width: 100%;
  padding: 14px 22px;
  border-radius: 14px;
  border: none;
  background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-primary-2) 100%);
  color: white;
  font-family: var(--font-current);
  font-size: 14px;
  font-weight: 800;
  cursor: pointer;
  box-shadow: 0 12px 32px -10px var(--accent-glow);
  transition: transform 120ms ease, box-shadow 180ms ease;
}
.sh-sheet-cta:hover  { box-shadow: 0 14px 40px -8px var(--accent-glow); }
.sh-sheet-cta:active { transform: scale(0.98); }
.sh-sheet-cta-label  { font-size: 14px; }
.sh-sheet-cta-price .sh-price-value { color: white; }
.sh-sheet-cta-price .sh-price-icon  { color: white; }

.sh-sheet-cta--ghost {
  background: transparent;
  color: var(--accent-primary);
  border: 1px solid color-mix(in srgb, var(--accent-primary) 38%, transparent);
  box-shadow: none;
}
.sh-sheet-foot {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px;
  border-radius: 14px;
  background: rgba(255,255,255,0.03);
  border: 1px solid rgba(255,255,255,0.08);
  font-family: var(--font-current);
  font-size: 13px;
  color: var(--text-secondary);
}
.sh-sheet-foot--info {
  flex-direction: row;
}
.sh-sheet-foot--info svg { color: var(--text-tertiary); }
.sh-sheet-foot--insufficient {
  display: flex; flex-direction: column;
  gap: 10px;
}
.sh-sheet-foot--insufficient .sh-sheet-foot-msg {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--danger);
  font-weight: 700;
}
.sh-sheet-foot--insufficient .sh-sheet-foot-msg svg { color: var(--danger); }
`;
const __shopAppStyle = document.createElement('style');
__shopAppStyle.textContent = SHOP_APP_CSS;
document.head.appendChild(__shopAppStyle);
