// leaderboard-app.jsx — Mounts the Leaderboard design canvas + Tweaks
// panel. Sprint v1.6 (Design Log §16.x).
//
// Mobile-first 420px + Desktop 1400px on the canonical baseline §12 chrome
// (AppBar / DesktopTopbar / BottomNav / SectionHead / FilterChips /
// HHEmptyState) + the page-specific Leaderboard primitives from
// leaderboard-pieces.jsx (UserRankHero, LeaderboardRow, RewardTierStrip,
// HistoryCard).
//
// The entry-detail bottom sheet is built on `MyCardsDetailModal
// layout="sheetCustom"` — same canonical pattern as PredictionMatchSheet
// (§14.0) and PackDetailSheet (Shop v1.5).

const { useState: laUseState, useEffect: laUseEffect, useMemo: laUseMemo,
        useRef: laUseRef, useCallback: laUseCallback } = React;


// ─── Phone frame ──────────────────────────────────────────────────────────
function LeaderboardPhone({ w = 420, h = 920, children }) {
  return (
    <div className="lb-phone" style={{ width: w, height: h }}>
      <div className="lb-phone-inner">{children}</div>
    </div>
  );
}


// ─── userToEntry — turn LB_USER into an entry-shaped row ──────────────────
// Used by the sticky "you" row (when the user is below the visible cutoff)
// and by EntryDetailSheet when the entry being inspected IS the user.
function userToEntry(user, locale = 'fa') {
  if (!user) return null;
  return {
    id:         user.id,
    rank:       user.rank,
    score:      user.score,
    deltaRank:  user.deltaRank,
    name:       user.name || { fa: 'تو', en: 'You', ar: 'أنت' },
    team:       user.team,
    teamCode:   user.teamCode,
    teamColor:  user.teamColor,
    region:     user.region,
    avatarSeed: user.avatarSeed || 'you',
    badges:     user.badges || [],
    isYou:      true,
  };
}


// ─── useLBSheet ───────────────────────────────────────────────────────────
// Single source of truth for: active league filter, which entry is being
// inspected in the bottom sheet, and the (mock) follow state. Mirrors
// `usePRMatchSheet` / `useShopSheet` patterns.
//
// Args
//   initial   — { league?, sheetEntryId?, user? } — used by artboards to
//               force a specific opening state (e.g. open the sheet on
//               rank 1 for the "detail" artboard).
function useLBSheet(initial = {}) {
  const [league, setLeague] = laUseState(initial.league || 'overall');
  const [sheetEntryId, setSheetEntryId] = laUseState(initial.sheetEntryId || null);
  const [followedIds, setFollowedIds] = laUseState(() => new Set());
  const [user] = laUseState(() => (initial.user || window.LB_USER));

  // Slice the canonical entry pool by league (the predicate lives in
  // leaderboard-data.jsx — single source of truth).
  const entries = laUseMemo(() => {
    const pool = window.LB_ENTRIES || [];
    return (typeof window.filterEntries === 'function')
      ? window.filterEntries(pool, league)
      : pool;
  }, [league]);

  const openSheet  = (entry) => setSheetEntryId(entry ? entry.id : null);
  const closeSheet = () => setSheetEntryId(null);

  const activeEntry = laUseMemo(() => {
    if (!sheetEntryId) return null;
    if (sheetEntryId === user?.id) return userToEntry(user);
    return entries.find((e) => e.id === sheetEntryId)
        || (window.LB_ENTRIES || []).find((e) => e.id === sheetEntryId)
        || null;
  }, [sheetEntryId, entries, user]);

  const toggleFollow = (entryId) => {
    setFollowedIds((cur) => {
      const next = new Set(cur);
      if (next.has(entryId)) next.delete(entryId); else next.add(entryId);
      return next;
    });
  };

  // Counts for the FilterChips — based on the canonical pool, not the
  // currently filtered slice.
  const counts = laUseMemo(() => {
    const out = {};
    const pool = window.LB_ENTRIES || [];
    (window.LB_LEAGUES || []).forEach((lg) => {
      out[lg.id] = (typeof window.filterEntries === 'function')
        ? window.filterEntries(pool, lg.id).length
        : pool.length;
    });
    return out;
  }, []);

  return { league, setLeague, entries, counts,
           user, sheetEntryId, activeEntry, openSheet, closeSheet,
           followedIds, toggleFollow };
}


// ─── EntryDetailSheet ─────────────────────────────────────────────────────
// Wrapper around `MyCardsDetailModal layout="sheetCustom"` — same canonical
// bottom-sheet pattern as PackDetailSheet / PredictionMatchSheet.
//
// Sheet anatomy:
//   • header — big avatar + name + rank pill + tier chip + team line
//   • body   — meta tiles (score / delta / streak / best rank) + last 3
//              matchweek mini-history + badge cluster
//   • footer — Follow / Unfollow CTA (mock toggle)
function EntryDetailSheet({ entry, open, onClose, locale = 'fa',
                              followed = false, onToggleFollow, inline = true }) {
  if (!entry) return null;
  const ll = window.LB_I18N ? window.LB_I18N[locale] : {};
  const rank = entry.rank;
  const podium = rank > 0 && rank <= 3;
  const podiumTier = rank === 1 ? 'gold' : rank === 2 ? 'silver' : rank === 3 ? 'bronze' : null;
  const tier = (typeof window.getTierForRank === 'function')
    ? window.getTierForRank(rank) : null;
  const tierName = tier && ll[tier.nameKey];
  const tierAccent = tier?.accent || 'var(--accent-primary)';

  const dlt = entry.deltaRank;
  const dltSign = typeof dlt === 'number' ? Math.sign(dlt) : 0;

  // Derive 1st letter for the LBAvatar
  const initials = {
    fa: (entry.name?.fa || '').trim().charAt(0) || '?',
    en: (entry.name?.en || '').trim().charAt(0).toUpperCase() || '?',
    ar: (entry.name?.ar || '').trim().charAt(0) || '?',
  };

  // Build a mini history. For the canonical user we have a real one;
  // for others, fabricate from rank/delta so the artboard isn't empty.
  const history = entry.isYou && window.LB_USER?.history?.length
    ? window.LB_USER.history
    : [
        { week: 27, rank: rank - (dlt || 0),       deltaRank: -(dlt || 0) },
        { week: 26, rank: rank - (dlt || 0) - 3,   deltaRank: +3 },
        { week: 25, rank: rank - (dlt || 0) - 5,   deltaRank: -2 },
      ];

  // Best rank — simple min of history (mock for non-user rows)
  const bestRank = Math.min(rank, ...history.map((h) => h.rank).filter((n) => typeof n === 'number'));

  const badges = (entry.badges || []).map((id) => {
    const meta = window.LB_BADGES?.[id];
    if (!meta) return null;
    return { id, label: ll[meta.i18nKey] || id, glyph: meta.glyph, accent: meta.accent };
  }).filter(Boolean);

  // Profile stats (added per v1.6 visual feedback). For the canonical user
  // we read straight from LB_USER; for the 50 mock entries we synthesize
  // plausible values via getEntryStats.
  const stats = (typeof window.getEntryStats === 'function')
    ? window.getEntryStats(entry) : null;
  const fmt = (n) => (window.toLocaleDigits
    ? window.toLocaleDigits(typeof n === 'number' ? n.toLocaleString('en-US') : String(n), locale)
    : String(n));

  const header = (
    <div className="lb-sheet-head"
         style={{ '--lb-tier-accent': tierAccent }}>
      <div className="lb-sheet-head-bg" aria-hidden="true"/>
      <div className="lb-sheet-head-row">
        {window.LBAvatar
          ? <window.LBAvatar seed={entry.avatarSeed}
                             initial={initials}
                             size={64}
                             ringTier={podium ? podiumTier : (entry.isYou ? 'accent' : null)}
                             locale={locale}/>
          : null}
        <div className="lb-sheet-head-meta">
          <div className="lb-sheet-head-name">
            {entry.isYou && (
              <span className="lb-sheet-head-you">{ll.youLabel || 'You'}</span>
            )}
            <span>{entry.name?.[locale]}</span>
          </div>
          {entry.team?.[locale] && (
            <div className="lb-sheet-head-team">
              <span>{ll.sheetTeamLabel}</span>
              <span>·</span>
              <span>{entry.team[locale]}</span>
            </div>
          )}
        </div>
        <div className="lb-sheet-head-rank-col">
          {podium && window.RankBadge
            ? <window.RankBadge rank={rank} locale={locale} size="lg"/>
            : (
              <div className="lb-sheet-head-rank-plain tabular-nums">
                {ll.rankNumber?.((window.toLocaleDigits
                  ? window.toLocaleDigits(rank, locale)
                  : String(rank)))}
              </div>
            )}
          {tierName && (
            <span className="lb-sheet-head-tier"
                  style={{ '--lb-tier-accent': tierAccent }}>
              {tierName}
            </span>
          )}
        </div>
      </div>
    </div>
  );

  const body = (
    <div className="lb-sheet-body">
      {/* meta tiles */}
      <div className="lb-sheet-tiles">
        <div className="lb-sheet-tile">
          <span className="lb-sheet-tile-label">{ll.colScore}</span>
          <span className="lb-sheet-tile-val tabular-nums">
            {window.toLocaleDigits
              ? window.toLocaleDigits(entry.score.toLocaleString('en-US'), locale)
              : entry.score}
          </span>
        </div>
        <div className="lb-sheet-tile">
          <span className="lb-sheet-tile-label">{ll.deltaSinceWeek}</span>
          <span className={`lb-sheet-tile-val lb-sheet-tile-val--delta is-${dltSign > 0 ? 'up' : dltSign < 0 ? 'down' : 'flat'}`}>
            <span aria-hidden="true">{dltSign > 0 ? '▲' : dltSign < 0 ? '▼' : '—'}</span>
            {dltSign !== 0 && (
              <span className="tabular-nums">
                {window.toLocaleDigits ? window.toLocaleDigits(Math.abs(dlt), locale) : Math.abs(dlt)}
              </span>
            )}
          </span>
        </div>
        <div className="lb-sheet-tile">
          <span className="lb-sheet-tile-label">{ll.sheetSubBest}</span>
          <span className="lb-sheet-tile-val tabular-nums">
            {ll.rankNumber?.(window.toLocaleDigits
              ? window.toLocaleDigits(bestRank, locale)
              : bestRank)}
          </span>
        </div>
      </div>

      {/* Profile stats — XP / Predict-accuracy / Lineups (v1.6 fb) */}
      {stats && (
        <div className="lb-sheet-profile">
          <div className="lb-sheet-section-h">
            <span>{ll.sheetProfileTitle}</span>
          </div>
          <div className="lb-sheet-profile-tiles">
            <div className="lb-sheet-ptile">
              <span className="lb-sheet-ptile-icon" aria-hidden="true">
                {window.HHIcon && <window.HHIcon name="bolt" size={14}/>}
              </span>
              <span className="lb-sheet-ptile-label">{ll.sheetXP}</span>
              <span className="lb-sheet-ptile-val tabular-nums">
                {ll.sheetXPVal?.(fmt(stats.totalXP))}
              </span>
              {typeof stats.level === 'number' && (
                <span className="lb-sheet-ptile-sub">L{fmt(stats.level)}</span>
              )}
            </div>
            <div className="lb-sheet-ptile">
              <span className="lb-sheet-ptile-icon" aria-hidden="true">
                {window.HHIcon && <window.HHIcon name="target" size={14}/>}
              </span>
              <span className="lb-sheet-ptile-label">{ll.sheetAccuracy}</span>
              <span className="lb-sheet-ptile-val tabular-nums">
                {ll.sheetAccuracyVal?.(fmt(stats.predictAccuracy))}
              </span>
              <span className="lb-sheet-ptile-sub">
                {ll.sheetAccuracySub?.(fmt(stats.predictTotal))}
              </span>
            </div>
            <div className="lb-sheet-ptile">
              <span className="lb-sheet-ptile-icon" aria-hidden="true">
                {window.HHIcon && <window.HHIcon name="lineup" size={14}/>}
              </span>
              <span className="lb-sheet-ptile-label">{ll.sheetLineups}</span>
              <span className="lb-sheet-ptile-val tabular-nums">
                {fmt(stats.lineupsCount)}
              </span>
              <span className="lb-sheet-ptile-sub">
                {ll.sheetLineupsVal?.(fmt(stats.lineupsCount))}
              </span>
            </div>
          </div>
        </div>
      )}

      {/* ─────────────────────────────────────────────────────────────
          Best lineup (v1.6 fb-2) — matches Wireframe §S37 exactly.
          Renders a horizontally-scrollable strip of position tiles
          (rating + position), interleaved with "+ chip" bonuses and
          "× chip" multipliers, followed by a formula row that breaks
          down the calculation and shows the final total. Multipliers
          flagged `showInStrip:false` (e.g. festival) are mentioned by
          name in the formula text but not chipped. */}
      {stats?.bestLineup?.positions?.length > 0 && (() => {
        const bl = stats.bestLineup;
        // Build the formula text dynamically so the math always agrees
        // with the data. Format: "(base + bonus1 + …) × m1 × m2 [festival]"
        const bonusTerms = (bl.bonuses || [])
          .map(b => ` + ${fmt(b.value)}`).join('');
        const innerExpr = bl.bonuses?.length
          ? `(${fmt(bl.basePoints)}${bonusTerms})`
          : fmt(bl.basePoints);
        const multTerms = (bl.multipliers || [])
          .map(m => ` × ${fmt(m.value)}`).join('');
        const hiddenMult = (bl.multipliers || [])
          .find(m => !m.showInStrip && m.labelKey);
        const festivalLabel = hiddenMult ? ` ${ll[hiddenMult.labelKey] || ''}` : '';
        const formulaText = `${innerExpr}${multTerms}${festivalLabel}`.trim();

        return (
          <div className="lb-sheet-best">
            <div className="lb-sheet-section-h">
              <span>{ll.sheetBestLineup}</span>
              <span className="lb-sheet-section-sub">
                {ll.bestLineupDaysAgo?.(fmt(bl.daysAgo))}
              </span>
            </div>
            <div className="lb-sheet-best-card"
                 style={{ '--lb-tier-accent': tierAccent }}>
              {/* Horizontal scroll strip — positions ▸ bonuses ▸ multipliers */}
              <div className="lb-sheet-best-strip" role="list">
                {bl.positions.map((p, i) => (
                  <div className="lb-sheet-best-tile" role="listitem" key={`p-${i}`}>
                    <span className="lb-sheet-best-tile-score tabular-nums">{fmt(p.score)}</span>
                    <span className="lb-sheet-best-tile-pos">{p.pos}</span>
                  </div>
                ))}
                {(bl.bonuses || []).map((b, i) => (
                  <React.Fragment key={`b-${i}`}>
                    <span className="lb-sheet-best-sep lb-sheet-best-sep--plus" aria-hidden="true">+</span>
                    <div className="lb-sheet-best-chip lb-sheet-best-chip--plus" role="listitem">
                      <span className="lb-sheet-best-chip-label">{b.code}</span>
                      <span className="lb-sheet-best-chip-value tabular-nums">{`+${fmt(b.value)}`}</span>
                    </div>
                  </React.Fragment>
                ))}
                {(bl.multipliers || []).filter(m => m.showInStrip).map((m, i) => (
                  <React.Fragment key={`m-${i}`}>
                    <span className="lb-sheet-best-sep lb-sheet-best-sep--times" aria-hidden="true">×</span>
                    <div className="lb-sheet-best-chip lb-sheet-best-chip--times" role="listitem">
                      <span className="lb-sheet-best-chip-label">{ll[m.labelKey] || ''}</span>
                      <span className="lb-sheet-best-chip-value tabular-nums">{`×${fmt(m.value)}`}</span>
                    </div>
                  </React.Fragment>
                ))}
              </div>

              {/* Formula row — dashed top separator, tertiary text on the
                  left, accent-coloured total on the right. */}
              <div className="lb-sheet-best-formula">
                <span className="lb-sheet-best-formula-text">{formulaText}</span>
                <span className="lb-sheet-best-formula-total tabular-nums">
                  {fmt(bl.total)}
                </span>
              </div>
            </div>
          </div>
        );
      })()}

      {/* mini history */}
      <div className="lb-sheet-history">
        <div className="lb-sheet-section-h">
          <span>{ll.secHistory}</span>
          <span className="lb-sheet-section-sub">{ll.secHistorySub}</span>
        </div>
        {window.HistoryCard && (
          <window.HistoryCard history={history} locale={locale} variant="rail"/>
        )}
      </div>

      {/* badges */}
      {badges.length > 0 && (
        <div className="lb-sheet-badges">
          <div className="lb-sheet-section-h">
            <span>{ll.sheetBadgesTitle}</span>
          </div>
          <div className="lb-sheet-badges-grid">
            {badges.map((b) => (
              <span key={b.id}
                    className="lb-sheet-badge"
                    style={{ '--b-accent': b.accent }}>
                <span className="lb-sheet-badge-icon">
                  {window.HHIcon ? <window.HHIcon name={b.glyph} size={14}/> : null}
                </span>
                <span className="lb-sheet-badge-label">{b.label}</span>
              </span>
            ))}
          </div>
        </div>
      )}
    </div>
  );

  const footer = !entry.isYou && (
    <div className="lb-sheet-foot">
      <button type="button"
              className={`lb-sheet-cta ${followed ? 'is-following' : ''}`}
              onClick={() => onToggleFollow && onToggleFollow(entry.id)}>
        {window.HHIcon && (
          <window.HHIcon name={followed ? 'check' : 'plus'} size={14}/>
        )}
        <span>{followed ? ll.sheetUnfollowCta : ll.sheetFollowCta}</span>
      </button>
    </div>
  );

  if (!window.MyCardsDetailModal) return null;

  return (
    <window.MyCardsDetailModal
      open={open}
      onClose={onClose}
      layout="sheetCustom"
      locale={locale}
      mobileHeight="90vh"
      inline={inline}
      accent={tierAccent}
      header={header}
      body={body}
      footer={footer || null}
    />
  );
}


// ─── useHeroVisibility ────────────────────────────────────────────────────
// Scroll-driven visibility hook. Watches the UserRankHero element against
// its scroll container; returns `heroOffscreen=true` whenever the hero has
// scrolled fully above the viewport top. Drives the top sticky-bar
// mount/unmount + slide transition (Phase 3 v1.6 feedback).
//
// Why IntersectionObserver instead of a scroll listener?
//   • cheaper (no scroll-handler thrash),
//   • respects fractional intersection via threshold,
//   • root: scrollEl supports nested artboard scroll containers (the
//     phone frame in DesignCanvas).
function useHeroVisibility(heroRef, scrollRef) {
  const [heroOffscreen, setHeroOffscreen] = laUseState(false);
  laUseEffect(() => {
    const hero = heroRef.current;
    const root = scrollRef.current;
    if (!hero || !root || typeof IntersectionObserver !== 'function') return;
    const obs = new IntersectionObserver((entries) => {
      const e = entries[0];
      if (!e) return;
      // Hero is "offscreen above" only when it is fully out of view AND
      // its top edge sits above the scroller's top. (We don't show the
      // sticky bar when the hero is merely below the viewport — that's
      // the initial mount state.)
      const aboveTop = e.boundingClientRect.bottom <= (e.rootBounds?.top ?? 0) + 4;
      setHeroOffscreen(!e.isIntersecting && aboveTop);
    }, { root, threshold: [0, 0.01] });
    obs.observe(hero);
    return () => obs.disconnect();
  }, [heroRef, scrollRef]);
  return heroOffscreen;
}


// ─── useDelayedUnmount ────────────────────────────────────────────────────
// Tiny helper so we get an exit animation on the sticky bar. While `active`
// is true we are mounted + visible. When it flips to false we stay mounted
// (visible=false → exit animation runs) for `delay` ms, then unmount.
function useDelayedUnmount(active, delay = 320) {
  const [mounted, setMounted] = laUseState(active);
  const [visible, setVisible] = laUseState(active);
  laUseEffect(() => {
    let t;
    if (active) {
      setMounted(true);
      // Two RAFs so the browser commits the .lb-sticky-bar enter state
      // (translateY(-100%) + opacity 0) BEFORE we add .is-visible — gives
      // a real CSS transition instead of an instant snap.
      t = requestAnimationFrame(() => requestAnimationFrame(() => setVisible(true)));
      return () => cancelAnimationFrame(t);
    }
    setVisible(false);
    t = setTimeout(() => setMounted(false), delay);
    return () => clearTimeout(t);
  }, [active, delay]);
  return { mounted, visible };
}


// ─── LeaderboardMobile ────────────────────────────────────────────────────
// Mobile 420px layout — updated per v1.6 visual feedback:
//
//   AppBar
//   ┌─ scroll container ─────────────────────────────────────────────┐
//   │  [sticky-bar] ← absent until UserRankHero scrolls off-screen,  │
//   │                then slides down with a transform+opacity       │
//   │                transition; slides back up when hero returns.   │
//   │  SponsorBox            ← moved above hero (eyebrow banner)     │
//   │  SectionHead "رتبه شما"                                        │
//   │  UserRankHero (large)  ← tracked by IntersectionObserver       │
//   │  RewardTierStrip       ← moved below hero (rank → reward)      │
//   │  FilterChips                                                   │
//   │  SectionHead "جدول"                                            │
//   │  LeaderboardRow × N                                            │
//   └────────────────────────────────────────────────────────────────┘
//   BottomNav
//
// Props
//   locale            'fa' | 'en' | 'ar'
//   defaultLeague     initial chip key (default 'overall')
//   sheetEntryId      optional — start with the detail sheet open
//   forceEmpty        toggle empty-state preview
//   userOverride      override LB_USER (used by Top-3 / Below-cutoff
//                     artboards to swap rank/score without mutating the
//                     canonical user)
//   visibleTopN       how many rows to render. Default 30. The sticky-bar
//                     appears for ANY user (the sheet hero is the
//                     primary surface; this is just the secondary).
function LeaderboardMobile({
  locale = 'fa',
  defaultLeague = 'overall',
  sheetEntryId = null,
  forceEmpty = false,
  userOverride = null,
  visibleTopN = 30,
}) {
  const effectiveUser = userOverride || window.LB_USER;
  const sheet = useLBSheet({
    league: defaultLeague,
    sheetEntryId,
    user: effectiveUser,
  });
  const ll = window.LB_I18N ? window.LB_I18N[locale] : {};

  const list = forceEmpty ? [] : sheet.entries.slice(0, visibleTopN);

  // Scroll-driven sticky bar — only meaningful when there's a user and
  // we're not in the empty / friends-only state.
  const scrollRef = laUseRef(null);
  const heroRef   = laUseRef(null);
  const heroOffscreen = useHeroVisibility(heroRef, scrollRef);
  const stickyEligible = !forceEmpty
    && effectiveUser
    && sheet.league !== 'friends';
  const { mounted: stickyMounted, visible: stickyVisible } =
    useDelayedUnmount(stickyEligible && heroOffscreen, 320);

  const chips = (window.LB_LEAGUES || []).map((lg) => ({
    key:   lg.id,
    label: ll[lg.i18nKey] || lg.id,
    count: sheet.counts[lg.id] ?? 0,
  }));

  const renderRow = (e) => (
    <window.LeaderboardRow
      key={e.id}
      entry={e}
      locale={locale}
      isYou={effectiveUser && e.rank === effectiveUser.rank}
      onTap={(en) => sheet.openSheet(en)}
    />
  );

  const stickyYouEntry = userToEntry(effectiveUser, locale);

  return (
    <div className="lb-screen">
      {window.AppBar && (
        <window.AppBar locale={locale}
                       user={{ ...(window.HH_USER || {}),
                               coinBalance: 0,
                               level: 7 }}
                       density="compact"
                       avatarSide="start"
                       showStreak={false} showXP={true}
                       notifications={0}/>
      )}

      {/* lb-screen-stage owns the scroll area + the floating sticky bar.
           Placing the bar OUTSIDE .lb-screen-scroll (as a sibling, absolute-
           positioned to the top of this stage) sidesteps the well-known
           sticky + flex-column issues we hit when the bar lived inside the
           scroller. The stage's top edge is exactly below the AppBar, so
           `inset-block-start: 0` puts the bar exactly where we want it. */}
      <div className="lb-screen-stage">
        {stickyMounted && stickyYouEntry && window.LeaderboardRow && (
          <div className={`lb-sticky-bar ${stickyVisible ? 'is-visible' : ''}`}
               aria-hidden={!stickyVisible}>
            <div className="lb-sticky-bar-inner">
              <window.LeaderboardRow
                entry={stickyYouEntry}
                locale={locale}
                isYou={true}
                isSticky={true}
                onTap={(en) => sheet.openSheet(en)}
              />
            </div>
          </div>
        )}

        <div className="lb-screen-scroll" ref={scrollRef}>
        <div className="lb-body">
          {/* Sponsor — eyebrow banner above the hero (v1.6 feedback) */}
          {!forceEmpty && window.SponsorBox && window.LB_SPONSOR && (
            <section className="lb-sponsor-wrap">
              <window.SponsorBox locale={locale} sponsor={window.LB_SPONSOR}/>
            </section>
          )}

          {/* Hero */}
          {window.SectionHead && (
            <window.SectionHead title={ll.secHero}/>
          )}
          {window.UserRankHero && (
            <div ref={heroRef} className="lb-hero-anchor">
              <window.UserRankHero user={effectiveUser}
                                   locale={locale}
                                   variant="large"/>
            </div>
          )}

          {/* Reward tiers — below the hero so users see "what's at stake"
              before they start filtering / scrolling the list. */}
          {!forceEmpty && window.RewardTierStrip && (
            <section>
              {window.SectionHead && (
                <window.SectionHead title={ll.secRewards} sub={ll.secRewardsSub}/>
              )}
              <window.RewardTierStrip userRank={effectiveUser?.rank}
                                       locale={locale}
                                       variant="horizontal"/>
            </section>
          )}

          {/* League chips */}
          {window.FilterChips && (
            <window.FilterChips chips={chips}
                                value={sheet.league}
                                onChange={sheet.setLeague}
                                locale={locale}/>
          )}

          {/* List */}
          {window.SectionHead && (
            <window.SectionHead title={ll.secList}
                                sub={ll.secListSub?.(window.toLocaleDigits
                                  ? window.toLocaleDigits(sheet.entries.length, locale)
                                  : sheet.entries.length)}/>
          )}

          {forceEmpty && window.HHEmptyState && (
            <window.HHEmptyState locale={locale}
                                 glyphIcon="trophy"
                                 title={ll.emptyTitle}
                                 body={ll.emptyBody}
                                 ctaLabel={ll.emptyCta}
                                 ctaIcon="lineup"
                                 onCta={() => {}}
                                 secondaryCtaLabel={ll.emptySecondary}
                                 secondaryCtaIcon="bell"
                                 onSecondaryCta={() => {}}/>
          )}

          {!forceEmpty && list.length > 0 && (
            <div className="lb-list">
              {list.map(renderRow)}
            </div>
          )}
        </div>
        </div>
      </div>

      {window.BottomNav && (
        <window.BottomNav locale={locale} activeTab="leaderboard"/>
      )}

      <EntryDetailSheet entry={sheet.activeEntry}
                        open={!!sheet.sheetEntryId}
                        onClose={sheet.closeSheet}
                        locale={locale}
                        followed={sheet.activeEntry
                          ? sheet.followedIds.has(sheet.activeEntry.id) : false}
                        onToggleFollow={sheet.toggleFollow}
                        inline={true}/>
    </div>
  );
}


// ─── LeaderboardDesktop ───────────────────────────────────────────────────
// Desktop 1400px layout, per spec:
//   DesktopTopbar
//   .lb-desktop (grid: 1fr 360px via template-areas — same RTL-safe
//                pattern as v1.3.2 prediction desktop)
//     .lb-desktop-main:
//       SectionHead → FilterChips → list (with optional sticky you row)
//     .lb-desktop-side (sticky):
//       UserRankHero compact → RewardTierStrip vertical → HistoryCard
//       stack → SponsorBox
function LeaderboardDesktop({
  locale = 'fa',
  defaultLeague = 'overall',
  sheetEntryId = null,
  userOverride = null,
  visibleTopN = 30,
}) {
  const effectiveUser = userOverride || window.LB_USER;
  const sheet = useLBSheet({
    league: defaultLeague,
    sheetEntryId,
    user: effectiveUser,
  });
  const ll = window.LB_I18N ? window.LB_I18N[locale] : {};

  const list = sheet.entries.slice(0, visibleTopN);
  const userInList = effectiveUser && list.some((e) => e.rank === effectiveUser.rank);
  const showStickyYou = effectiveUser
    && !userInList
    && sheet.league !== 'friends';

  const chips = (window.LB_LEAGUES || []).map((lg) => ({
    key:   lg.id,
    label: ll[lg.i18nKey] || lg.id,
    count: sheet.counts[lg.id] ?? 0,
  }));

  const renderRow = (e) => (
    <window.LeaderboardRow
      key={e.id}
      entry={e}
      locale={locale}
      isYou={effectiveUser && e.rank === effectiveUser.rank}
      onTap={(en) => sheet.openSheet(en)}
    />
  );

  const stickyYouEntry = userToEntry(effectiveUser, locale);

  return (
    <div className="lb-desktop-wrap">
    <div className="lb-desktop-screen">
      {window.DesktopTopbar && (
        <window.DesktopTopbar locale={locale}
                              user={{ ...(window.HH_USER || {}),
                                      coinBalance: 0, level: 7 }}
                              activeTab="leaderboard"/>
      )}

      <div className="lb-desktop-scroll">
        <div className="lb-desktop">
          <div className="lb-desktop-main">
            {window.SectionHead && (
              <window.SectionHead title={ll.secList}
                                  sub={ll.secListSub?.(window.toLocaleDigits
                                    ? window.toLocaleDigits(sheet.entries.length, locale)
                                    : sheet.entries.length)}/>
            )}
            {window.FilterChips && (
              <window.FilterChips chips={chips}
                                  value={sheet.league}
                                  onChange={sheet.setLeague}
                                  locale={locale}/>
            )}
            <div className="lb-list lb-list--desktop">
              {list.map(renderRow)}
              {showStickyYou && stickyYouEntry && window.LeaderboardRow && (
                <window.LeaderboardRow
                  entry={stickyYouEntry}
                  locale={locale}
                  isYou={true}
                  isSticky={true}
                  onTap={(en) => sheet.openSheet(en)}
                />
              )}
            </div>
          </div>

          <aside className="lb-desktop-side">
            {window.UserRankHero && (
              <window.UserRankHero user={effectiveUser}
                                   locale={locale}
                                   variant="compact"/>
            )}
            {window.RewardTierStrip && (
              <section>
                {window.SectionHead && (
                  <window.SectionHead title={ll.secRewards} sub={ll.secRewardsSub}/>
                )}
                <window.RewardTierStrip userRank={effectiveUser?.rank}
                                         locale={locale}
                                         variant="vertical"/>
              </section>
            )}
            {window.HistoryCard && (
              <section>
                {window.SectionHead && (
                  <window.SectionHead title={ll.secHistory} sub={ll.secHistorySub}/>
                )}
                <window.HistoryCard user={effectiveUser}
                                     locale={locale}
                                     variant="stack"/>
              </section>
            )}
            {window.SponsorBox && window.LB_SPONSOR && (
              <section className="lb-sponsor-wrap">
                <window.SponsorBox locale={locale} sponsor={window.LB_SPONSOR}/>
              </section>
            )}
          </aside>
        </div>
      </div>

    </div>

    <EntryDetailSheet entry={sheet.activeEntry}
                      open={!!sheet.sheetEntryId}
                      onClose={sheet.closeSheet}
                      locale={locale}
                      followed={sheet.activeEntry
                        ? sheet.followedIds.has(sheet.activeEntry.id) : false}
                      onToggleFollow={sheet.toggleFollow}
                      inline={true}/>
    </div>
  );
}


// ─── App shell ────────────────────────────────────────────────────────────
// In Phase 3 we ship a minimal DesignCanvas with just two smoke-test
// artboards (Mobile Default + Desktop Default). Phase 4 expands this to
// the 10+ artboards required by the spec.
const LB_TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "locale": "fa"
}/*EDITMODE-END*/;

function LeaderboardApp() {
  const [t, setTweak] = useTweaks(LB_TWEAK_DEFAULTS);

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

  return (
    <React.Fragment>
      <DesignCanvas>
        {/* ── Mobile (smoke test) ── */}
        <DCSection id="mobile-leaderboard"
                   title="Mobile · Leaderboard (موبایل)"
                   subtitle="Phase 3 — smoke test. Artboard کامل (۱۰+) در Phase 4.">
          <DCArtboard id="m-default" label="A · Default (موبایل ۴۲۰)"
                      width={420} height={2200}>
            <LeaderboardPhone w={420} h={2200}>
              <LeaderboardMobile locale={t.locale}/>
            </LeaderboardPhone>
          </DCArtboard>
        </DCSection>

        {/* ── Desktop (smoke test) ── */}
        <DCSection id="desktop-leaderboard"
                   title="Desktop · Leaderboard ۱۴۰۰"
                   subtitle="Phase 3 — smoke test. Layout 2-col با grid-template-areas (مطابق pattern v1.3.2).">
          <DCArtboard id="d-default" label="A · Default" width={1400} height={1400}>
            <div className="lb-desktop-frame">
              <LeaderboardDesktop locale={t.locale}/>
            </div>
          </DCArtboard>
        </DCSection>
      </DesignCanvas>

      <TweaksPanel title="Tweaks · Leaderboard">
        <TweakSection label="نمایش">
          <TweakRadio label="زبان" value={t.locale}
            options={[
              { value: 'fa', label: 'فا' },
              { value: 'en', label: 'EN' },
              { value: 'ar', label: 'ع' },
            ]}
            onChange={(v) => setTweak('locale', v)}/>
        </TweakSection>
      </TweaksPanel>
    </React.Fragment>
  );
}


// ─── Mount ────────────────────────────────────────────────────────────────
// ─── Exports for unified app shell (Phase 1) ─────────────────────────────────
Object.assign(window, {
  LeaderboardMobile, LeaderboardDesktop, EntryDetailSheet,
});

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


// ─── App-level CSS (lb- prefix, token-only) ───────────────────────────────
const LB_APP_CSS = `
/* ─── Phone frame (mirrors sh-phone / hh-phone) ─────────────────────── */
.lb-phone {
  position: relative;
  border-radius: 32px;
  background: var(--bg-base, #0E0E16);
  box-shadow:
    0 24px 50px -20px rgba(0,0,0,0.55),
    0 0 0 1px var(--border-soft, rgba(255,255,255,0.06)) inset;
  overflow: hidden;
}
.lb-phone-inner {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  overflow: hidden;
}

/* ─── Mobile screen shell ───────────────────────────────────────────── */
.lb-screen {
  position: relative;
  display: flex; flex-direction: column;
  width: 100%; height: 100%;
  background:
    radial-gradient(ellipse 70% 50% at 20% 0%, rgba(113,99,217,0.10) 0%, transparent 60%),
    radial-gradient(ellipse 60% 40% at 80% 100%, rgba(252,211,77,0.05) 0%, transparent 60%),
    var(--bg-deep, #07070C);
  color: var(--text-primary);
  font-family: var(--font-current);
  isolation: isolate;
}
.lb-screen-stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  isolation: isolate;
}
.lb-screen-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  /* Make room for the floating BottomNav */
  padding-block-end: 96px;
}
.lb-body {
  display: flex; flex-direction: column;
  gap: 14px;
  padding: 12px 14px;
}

/* ─── List wrapper (gap is provided by row's own margin-block-start) ── */
.lb-list {
  display: flex; flex-direction: column;
  position: relative;
}
/* On desktop the rows breathe a little more. */
.lb-list--desktop > .lb-row + .lb-row { margin-block-start: 8px; }

/* ─── Desktop screen shell ──────────────────────────────────────────── */
/* lb-desktop-wrap — the containing block for mc-mb--inline modals.
   Must be position:relative + height:100% (not min-height) so that
   the modal's inset:0 fills exactly the visible artboard frame. */
.lb-desktop-wrap {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.lb-desktop-screen {
  flex: 1 1 auto;
  display: flex; flex-direction: column;
  width: 100%;
  min-height: 0;
  background:
    radial-gradient(ellipse 80% 50% at 20% 10%, rgba(113,99,217,0.07) 0%, transparent 60%),
    radial-gradient(ellipse 70% 50% at 80% 90%, rgba(147,197,253,0.05) 0%, transparent 60%),
    var(--bg-deep, #07070C);
  color: var(--text-primary);
  font-family: var(--font-current);
}
.lb-desktop-frame {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--bg-deep, #07070C);
}
.lb-desktop-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
}
.lb-desktop {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  grid-template-areas: "main side";
  align-items: start;
  gap: 24px;
  max-width: 1280px;
  margin-inline: auto;
  padding: 24px 28px 48px;
}
.lb-desktop-main {
  grid-area: main;
  display: flex; flex-direction: column;
  gap: 16px;
}
.lb-desktop-side {
  grid-area: side;
  position: sticky;
  inset-block-start: 16px;
  align-self: start;
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  display: flex; flex-direction: column;
  gap: 18px;
  padding-block-end: 4px;
}

/* ─── Sponsor wrap — small section spacing override ─────────────────── */
.lb-sponsor-wrap { display: flex; flex-direction: column; gap: 8px; }

/* ─── Hero anchor — invisible wrapper used as IntersectionObserver target.
   NOT display:contents (that would give us a 0×0 bounding rect → the
   observer would never fire). A plain block wrapper that mirrors the
   hero's natural height is enough. */
.lb-hero-anchor { display: block; }

/* ─── Top sticky "you" bar (v1.6 visual fb) ─────────────────────────────
   Two-layer setup, both layers SIDE-STEP the scroll container:
     • .lb-sticky-bar       — position:absolute relative to .lb-screen-stage
                              (which lives below AppBar). This avoids the
                              well-known sticky + flex-column issues where
                              a sticky element inside a vertically-flexed
                              scroll container fails to engage in some
                              engines. The bar floats over the scroll
                              content with a soft gradient backdrop, so it
                              always pins to the visible top edge.
     • .lb-sticky-bar-inner — owns transform + opacity transition for the
                              slide-in / slide-out. We animate ONLY
                              transform + opacity — within the v1 motion
                              budget (no blur, no box-shadow animation). */
.lb-sticky-bar {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inset-inline-end: 0;
  z-index: 10;
  padding: 6px 14px 14px;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--bg-deep, #07070C) 96%, transparent) 0%,
      color-mix(in srgb, var(--bg-deep, #07070C) 82%, transparent) 70%,
      transparent 100%);
  pointer-events: none;
}
.lb-sticky-bar.is-visible { pointer-events: auto; }
.lb-sticky-bar-inner {
  transform: translateY(-130%);
  opacity: 0;
  transition:
    transform 280ms var(--ease-out, cubic-bezier(0.22,1,0.36,1)),
    opacity   220ms var(--ease-out, cubic-bezier(0.22,1,0.36,1));
  will-change: transform, opacity;
}
.lb-sticky-bar.is-visible .lb-sticky-bar-inner {
  transform: translateY(0);
  opacity: 1;
}
.lb-sticky-bar-inner > .lb-row { width: 100%; }
@media (prefers-reduced-motion: reduce) {
  .lb-sticky-bar-inner {
    transition: opacity 120ms linear !important;
    transform: none !important;
  }
}

/* =========================================================================
   EntryDetailSheet — header / body / footer styling
   ========================================================================= */
.lb-sheet-head {
  position: relative;
  padding: 18px 18px 14px;
  isolation: isolate;
}
.lb-sheet-head-bg {
  position: absolute; inset: 0;
  background:
    radial-gradient(circle at 12% 0%,
                    color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 22%, transparent) 0%,
                    transparent 55%),
    radial-gradient(circle at 100% 110%,
                    color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 14%, transparent) 0%,
                    transparent 60%);
  pointer-events: none;
  z-index: 0;
}
.lb-sheet-head-row {
  position: relative; z-index: 1;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 14px;
}
.lb-sheet-head-meta {
  display: flex; flex-direction: column; gap: 4px;
  min-width: 0;
}
.lb-sheet-head-name {
  font-size: 18px;
  font-weight: 800;
  color: var(--text-primary);
  display: inline-flex; gap: 8px; align-items: center;
  letter-spacing: -0.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lb-sheet-head-you {
  display: inline-flex;
  padding: 2px 8px;
  border-radius: 999px;
  background: linear-gradient(135deg, var(--accent-primary), var(--accent-primary-2));
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  line-height: 1.4;
}
.lb-sheet-head-team {
  display: inline-flex; gap: 6px; align-items: center;
  font-size: 12px;
  color: var(--text-secondary);
  font-weight: 600;
}
.lb-sheet-head-rank-col {
  display: flex; flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}
.lb-sheet-head-rank-plain {
  font-family: var(--font-display-current, var(--font-current));
  font-size: 28px;
  font-weight: 900;
  letter-spacing: -0.03em;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.lb-sheet-head-tier {
  display: inline-flex; align-items: center;
  padding: 3px 9px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 14%, var(--bg-elevated));
  border: 1px solid color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 36%, transparent);
  color: var(--text-primary);
  font-size: 10.5px;
  font-weight: 700;
}

.lb-sheet-body {
  display: flex; flex-direction: column;
  gap: 14px;
  padding: 4px 14px 14px;
}
.lb-sheet-tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}
.lb-sheet-tile {
  display: flex; flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(255,255,255,0.025), rgba(255,255,255,0.012));
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
}
.lb-sheet-tile-label {
  font-size: 10.5px;
  color: var(--text-tertiary);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.lb-sheet-tile-val {
  font-size: 16px;
  font-weight: 800;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.lb-sheet-tile-val--delta {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 14px;
}
.lb-sheet-tile-val--delta.is-up   { color: var(--success); }
.lb-sheet-tile-val--delta.is-down { color: var(--danger); }
.lb-sheet-tile-val--delta.is-flat { color: var(--text-tertiary); }

.lb-sheet-section-h {
  display: flex; align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-block-end: 6px;
}
.lb-sheet-section-h > span:first-child {
  font-size: 12.5px;
  color: var(--text-primary);
  font-weight: 800;
  letter-spacing: 0.01em;
}
.lb-sheet-section-sub {
  font-size: 11px;
  color: var(--text-tertiary);
  font-weight: 600;
}

.lb-sheet-badges-grid {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.lb-sheet-badge {
  display: inline-flex; align-items: center;
  gap: 6px;
  padding: 5px 10px 5px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--b-accent, var(--accent-primary)) 12%, var(--bg-elevated));
  border: 1px solid color-mix(in srgb, var(--b-accent, var(--accent-primary)) 32%, transparent);
  font-size: 11.5px;
  font-weight: 700;
  color: var(--text-primary);
}
.lb-sheet-badge-icon { color: var(--b-accent, var(--accent-primary)); display: inline-flex; }

.lb-sheet-foot {
  padding: 12px 14px 14px;
  display: flex; justify-content: center;
  border-block-start: 1px solid var(--border-soft, rgba(255,255,255,0.05));
}
.lb-sheet-cta {
  width: 100%;
  display: inline-flex; align-items: center; justify-content: center;
  gap: 8px;
  padding: 12px 14px;
  border-radius: 14px;
  border: 1px solid color-mix(in srgb, var(--accent-primary) 30%, transparent);
  background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-primary-2) 100%);
  box-shadow: 0 8px 20px -8px var(--accent-glow);
  color: #fff;
  font-family: var(--font-current);
  font-size: 13.5px;
  font-weight: 800;
  cursor: pointer;
  transition: transform 160ms var(--ease-out, ease), box-shadow 160ms var(--ease-out, ease);
}
.lb-sheet-cta:hover { transform: translateY(-1px); }
.lb-sheet-cta.is-following {
  background: var(--bg-elevated);
  border-color: var(--border-strong, rgba(255,255,255,0.14));
  color: var(--text-secondary);
  box-shadow: none;
}

/* =========================================================================
   EntryDetailSheet — profile stats tiles (XP / Accuracy / Lineups)
   Added per v1.6 visual feedback. Sits between the meta tiles and the
   best-lineup card.
   ========================================================================= */
.lb-sheet-profile { display: flex; flex-direction: column; }
.lb-sheet-profile-tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}
.lb-sheet-ptile {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto auto;
  grid-template-areas:
    "ic  lbl"
    "ic  val"
    "ic  sub";
  column-gap: 10px;
  row-gap: 1px;
  padding: 10px 12px;
  border-radius: 14px;
  background: linear-gradient(180deg, rgba(255,255,255,0.030), rgba(255,255,255,0.012));
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
}
.lb-sheet-ptile-icon {
  grid-area: ic;
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--accent-primary) 18%, transparent);
  color: var(--accent-primary);
  align-self: center;
}
.lb-sheet-ptile-label {
  grid-area: lbl;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  line-height: 1;
}
.lb-sheet-ptile-val {
  grid-area: val;
  font-size: 14.5px;
  font-weight: 800;
  color: var(--text-primary);
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  line-height: 1.15;
}
.lb-sheet-ptile-sub {
  grid-area: sub;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--text-tertiary);
  line-height: 1.1;
}

/* =========================================================================
   EntryDetailSheet — best lineup card (v1.6 fb-2)
   Rebuilt to match Wireframes §S37 exactly: a horizontally-scrollable
   strip of tiny position tiles (rating + GK/DEF/MID/FWD), interleaved
   with "+ chip" bonuses (e.g. GER +400) and "× chip" multipliers
   (e.g. کلوپ ×2). Below the strip a dashed-top "formula row" describes
   the math and the right side shows the final total. All token-based;
   no new variables introduced.
   ========================================================================= */
.lb-sheet-best { display: flex; flex-direction: column; }

.lb-sheet-best-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border-radius: 12px;
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
  background: var(--bg-elevated);
}

/* Horizontal scroll strip — positions then + bonuses then × multipliers.
   The 'scrollbar-width:none' rule (and ::-webkit-scrollbar:none) hides
   the bar while still allowing touch / wheel scroll. */
.lb-sheet-best-strip {
  display: flex;
  align-items: center;
  gap: 4px;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* Edge fade so users get an affordance that more content is hidden. */
  mask-image: linear-gradient(
    to right,
    transparent 0,
    #000 8px,
    #000 calc(100% - 8px),
    transparent 100%);
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0,
    #000 8px,
    #000 calc(100% - 8px),
    transparent 100%);
}
.lb-sheet-best-strip::-webkit-scrollbar { display: none; }

/* Position tile — small square with rating on top, position abbrev. */
.lb-sheet-best-tile {
  flex: 0 0 auto;
  min-width: 38px;
  padding: 4px 6px;
  border-radius: 8px;
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
  background: var(--bg-deeper, color-mix(in srgb, var(--bg-elevated) 70%, #000));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
}
.lb-sheet-best-tile-score {
  font-size: 12px;
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1;
}
.lb-sheet-best-tile-pos {
  font-size: 8px;
  font-weight: 600;
  color: var(--text-tertiary);
  line-height: 1;
  letter-spacing: 0.04em;
}

/* Inline separators between tiles ↔ chips ↔ chips. The plus/times glyph
   inherits its colour from the chip family that FOLLOWS the separator,
   so they read as continuous additive vs multiplicative groupings. */
.lb-sheet-best-sep {
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 700;
  line-height: 1;
  padding-inline: 2px;
}
.lb-sheet-best-sep--plus  { color: var(--accent-primary); }
.lb-sheet-best-sep--times { color: var(--text-primary); }

/* Chip — same physical footprint as a tile but a stronger border colour
   so the "additive" and "multiplicative" entries stand apart visually. */
.lb-sheet-best-chip {
  flex: 0 0 auto;
  min-width: 44px;
  padding: 4px 7px;
  border-radius: 8px;
  background: var(--bg-deeper, color-mix(in srgb, var(--bg-elevated) 70%, #000));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
}
.lb-sheet-best-chip-label {
  font-size: 8px;
  font-weight: 600;
  color: var(--text-tertiary);
  line-height: 1;
  letter-spacing: 0.04em;
}
.lb-sheet-best-chip-value {
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
}
/* "+ chip" → accent ring + accent number (matches Wireframe purple) */
.lb-sheet-best-chip--plus {
  border: 1px solid color-mix(in srgb, var(--accent-primary) 65%, transparent);
}
.lb-sheet-best-chip--plus .lb-sheet-best-chip-value {
  color: var(--accent-primary);
}
/* "× chip" → strong primary-text ring (matches Wireframe dark border) */
.lb-sheet-best-chip--times {
  border: 1px solid color-mix(in srgb, var(--text-primary) 55%, transparent);
}
.lb-sheet-best-chip--times .lb-sheet-best-chip-value {
  color: var(--text-primary);
}

/* Formula row — dashed top separator, formula text on the leading edge,
   accent total on the trailing edge. */
.lb-sheet-best-formula {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-block-start: 8px;
  border-block-start: 1px dashed var(--border-soft, rgba(255,255,255,0.08));
}
.lb-sheet-best-formula-text {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-tertiary);
  line-height: 1.3;
  /* Allow wrapping but keep numbers grouped — formulas can be long
     in compact mobile widths. */
  overflow-wrap: anywhere;
}
.lb-sheet-best-formula-total {
  flex: 0 0 auto;
  font-size: 14px;
  font-weight: 700;
  color: var(--accent-primary);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* =========================================================================
   EntryDetailSheet — close button override (v1.6 fb-4)
   backdrop-filter on a child inside a parent that already has
   backdrop-filter applied has no visible effect (the inner blur is
   painting over an already-blurred surface, not the underlying content).
   Instead we use a solid-enough dark fill + bright border + top-edge
   inset shine to produce a clear "glass pill" look at any header colour.
   Scoped via :has(.lb-sheet-head) so Shop / Prediction sheets are safe.
   ========================================================================= */
.mc-mb-sheet-card--custom:has(.lb-sheet-head) .mc-mb-close--header {
  position: relative;
  z-index: 10;
  background: rgba(12, 12, 20, 0.92);
  border: 1.5px solid rgba(255, 255, 255, 0.55);
  color: #fff;
  box-shadow:
    0 2px 10px rgba(0, 0, 0, 0.60),
    inset 0 1px 0 rgba(255, 255, 255, 0.22);
}
.mc-mb-sheet-card--custom:has(.lb-sheet-head) .mc-mb-close--header:hover {
  background: rgba(26, 26, 40, 0.96);
  border-color: rgba(255, 255, 255, 0.72);
  color: #fff;
}
.mc-mb-sheet-card--custom:has(.lb-sheet-head) .mc-mb-close--header:active {
  transform: scale(0.94);
}
/* Fallback for browsers without :has() */
@supports not selector(:has(*)) {
  .mc-mb-sheet-card--custom .mc-mb-close--header {
    position: relative;
    z-index: 10;
    background: rgba(12, 12, 20, 0.92);
    border: 1.5px solid rgba(255, 255, 255, 0.55);
    color: #fff;
    box-shadow:
      0 2px 10px rgba(0, 0, 0, 0.60),
      inset 0 1px 0 rgba(255, 255, 255, 0.22);
  }
}

/* =========================================================================
   Reduced motion
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  .lb-sheet-cta { transition: none !important; }
}
`;
const __lbAppStyle = document.createElement('style');
__lbAppStyle.textContent = LB_APP_CSS;
document.head.appendChild(__lbAppStyle);
