// leaderboard-pieces.jsx — Page-specific primitives for the Leaderboard
// module (sprint v1.6). All public components carry the `lb-` prefix.
//
// Conventions (locked):
//   • Token-only — no new CSS variables. Border / surface fallbacks mirror
//     the pattern already used by shop-pieces.jsx
//     (`var(--border-soft, rgba(255,255,255,0.08))` etc.).
//   • Logical properties (`margin-inline-*`, `inset-inline-*`,
//     `border-inline-*`) so RTL/LTR flip is automatic.
//   • No animation heavier than `transform + opacity`. The only motion in
//     this file is the optional gold sparkle on the rank-1 row (a tiny
//     ±2deg rotate + opacity loop).
//   • All strings flow through LB_I18N[locale]; all numbers go through
//     `window.toLocaleDigits(num, locale)` (provided by card-v2.jsx).
//
// Public primitives (Object.assign'd to window at the bottom of the file):
//   • UserRankHero     — variants: 'large' (mobile hero) / 'compact'
//                        (desktop sidebar). Includes rank + score + delta
//                        + percentile + tier pill + progress-to-next-tier.
//   • LeaderboardRow   — single row. Supports top-3 podium accents,
//                        `isYou` highlight, badge cluster, sparkle on
//                        rank 1.
//   • RewardTierStrip  — variants: 'horizontal' (mobile strip) / 'vertical'
//                        (desktop stack). 4 tier cards with coin/pack
//                        rewards.
//   • HistoryCard      — variants: 'rail' (mobile h-scroll) / 'stack'
//                        (desktop column). Last N weeks (typically 3).
//
// Internal helpers (NOT exported — composed inside the above):
//   • RankBadge        — gold/silver/bronze podium pill for ranks 1–3.
//   • LBAvatar         — asset-less gradient + initial. Mirrors the
//                        TeamCrest pattern in homehub-pieces.

// ─── helpers ───────────────────────────────────────────────────────────────
const _lbT = (n, locale) => (window.toLocaleDigits
  ? window.toLocaleDigits(n, locale)
  : String(n));

// Format a long score (e.g. 12_080 → "12,080" → "۱۲٬۰۸۰"). We rely on
// `Number.toLocaleString('en-US')` so the grouping separator is always a
// Latin comma before toLocaleDigits maps the digits. Persian/Arabic
// rendering uses U+066C "Arabic Thousands Separator" via toLocaleDigits.
const _lbScore = (n, locale) => {
  if (typeof n !== 'number') return String(n ?? '');
  return _lbT(n.toLocaleString('en-US'), locale);
};

// Proxy to the canonical HHIcon set (homehub-pieces.jsx). Falls back to a
// 1×1 transparent SVG when not yet loaded — keeps SSR-style mounting safe.
const _lbIcon = ({ name, size = 16, strokeWidth, style }) => {
  const HHIcon = window.HHIcon;
  if (typeof HHIcon === 'function') {
    return HHIcon({ name, size, strokeWidth, style });
  }
  return React.createElement('svg', {
    width: size, height: size, viewBox: '0 0 24 24', 'aria-hidden': 'true', style,
  });
};

// Resolve a tier object from LB_REWARD_TIERS by rank. Wraps the helper from
// leaderboard-data.jsx so pieces stay decoupled (we don't import directly).
const _lbTierFor = (rank) =>
  (typeof window.getTierForRank === 'function')
    ? window.getTierForRank(rank)
    : null;

// 0..1 progress to the next-better tier.
const _lbTierProgress = (rank) =>
  (typeof window.getProgressToNextTier === 'function')
    ? window.getProgressToNextTier(rank)
    : 0;


// ─── RankBadge (internal, podium 1–3) ──────────────────────────────────────
// Small circular pill with a gold / silver / bronze gradient. Used as the
// rank cell for the top-3 rows (in LeaderboardRow) and as a decorative
// medal inside UserRankHero when the user is on the podium.
//
// Props
//   rank   — 1 | 2 | 3 (anything else falls back to a plain glass pill)
//   locale — for digit conversion
//   size   — 'sm' (28) | 'md' (32) | 'lg' (44)  (default 'md')
function RankBadge({ rank, locale = 'fa', size = 'md' }) {
  const tier = rank === 1 ? 'gold'
             : rank === 2 ? 'silver'
             : rank === 3 ? 'bronze'
             : 'plain';
  return (
    <span className={`lb-rank-badge lb-rank-badge--${size} lb-rank-badge--${tier}`}
          aria-label={`rank ${rank}`}>
      <span className="lb-rank-badge-num tabular-nums">{_lbT(rank, locale)}</span>
    </span>
  );
}


// ─── LBAvatar (internal, asset-less) ───────────────────────────────────────
// Asset-less circular avatar built from a deterministic gradient palette +
// the entry's first letter. Mirrors the TeamCrest convention so the design
// never depends on real avatar uploads while we're prototyping.
//
// Props
//   seed     — string (entry.avatarSeed); the palette is hashed from this
//   initial  — { fa, en, ar } | string — first glyph
//   size     — pixel diameter (24..72)
//   ringTier — null | 'gold' | 'silver' | 'bronze' | 'platinum' | 'accent'
//              — colored ring outside the avatar; used to mark podium /
//              your-tier in LeaderboardRow.
//   locale   — picks the initial when initial is a locale dict
function LBAvatar({ seed, initial, size = 36, ringTier, locale = 'fa', dim = false }) {
  const pal = (typeof window.resolveAvatarPalette === 'function')
    ? window.resolveAvatarPalette(seed)
    : { from: '#7163D9', to: '#534AB7', initialColor: '#fff' };
  const glyph = typeof initial === 'string'
    ? initial
    : (initial && initial[locale]) || '?';
  return (
    <span className={`lb-avatar ${ringTier ? `lb-avatar--ring-${ringTier}` : ''} ${dim ? 'is-dim' : ''}`}
          style={{
            width: size, height: size,
            background: `linear-gradient(150deg, ${pal.from} 0%, ${pal.to} 100%)`,
            color: pal.initialColor,
          }}
          aria-hidden="true">
      <span className="lb-avatar-initial"
            style={{ fontSize: Math.max(11, Math.round(size * 0.42)) }}>
        {glyph}
      </span>
    </span>
  );
}


// ─── LeaderboardRow ────────────────────────────────────────────────────────
// One row in the leaderboard list. Renders five "cells" in a single grid
// that auto-flips under RTL:
//
//   [ rank ] [ avatar ] [ name + team (+ badges) ] [ score ] [ delta ]
//
// Props
//   entry           — row data from LB_ENTRIES (see leaderboard-data.jsx)
//   locale          — 'fa' | 'en' | 'ar'
//   isYou           — bool override (defaults to entry.isYou). When true,
//                     the row gains an accent border + tinted surface.
//   showBadges      — bool (default true). When false, suppresses the
//                     badge cluster — used by the very compact desktop
//                     density variant.
//   sparkleOnTop1   — bool (default true). Adds a small animated sparkle
//                     beside the rank for entry.rank === 1.
//   isSticky        — bool. Adds `.lb-row--sticky-you` modifier — used by
//                     LeaderboardMobile/Desktop when the user is outside
//                     the visible top-N and we pin them under the list.
//   onTap           — fn(entry) — opens the entry detail sheet
function LeaderboardRow({
  entry,
  locale = 'fa',
  isYou,
  showBadges = true,
  sparkleOnTop1 = true,
  isSticky = false,
  onTap,
}) {
  if (!entry) return null;
  const ll = window.LB_I18N ? window.LB_I18N[locale] : {};
  const you = isYou != null ? isYou : !!entry.isYou;
  const rank = entry.rank;
  const podium = rank <= 3;
  const podiumTier = rank === 1 ? 'gold' : rank === 2 ? 'silver' : rank === 3 ? 'bronze' : null;

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

  // Derive the avatar's first-letter glyph from the locale name.
  const firstLetterByLocale = (() => {
    const result = { fa: '', en: '', ar: '' };
    if (entry.name && typeof entry.name === 'object') {
      result.fa = (entry.name.fa || '').trim().charAt(0) || '?';
      result.en = (entry.name.en || '').trim().charAt(0).toUpperCase() || '?';
      result.ar = (entry.name.ar || '').trim().charAt(0) || '?';
    }
    return result;
  })();

  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);

  return (
    <button type="button"
            className={[
              'lb-row',
              podium ? `lb-row--podium lb-row--podium-${podiumTier}` : '',
              you ? 'lb-row--you' : '',
              rank === 1 ? 'lb-row--top1' : '',
              isSticky ? 'lb-row--sticky-you' : '',
            ].filter(Boolean).join(' ')}
            onClick={() => onTap && onTap(entry)}>
      {/* rank cell */}
      <span className="lb-row-rank">
        {podium
          ? <RankBadge rank={rank} locale={locale} size="md"/>
          : <span className="lb-row-rank-plain tabular-nums">{_lbT(rank, locale)}</span>}
        {rank === 1 && sparkleOnTop1 && (
          <span className="lb-row-spark" aria-hidden="true">
            {_lbIcon({ name: 'sparkle', size: 12 })}
          </span>
        )}
      </span>

      {/* avatar */}
      <span className="lb-row-av">
        <LBAvatar seed={entry.avatarSeed}
                  initial={firstLetterByLocale}
                  size={36}
                  ringTier={podium ? podiumTier : (you ? 'accent' : null)}
                  locale={locale}/>
      </span>

      {/* name + team + badges */}
      <span className="lb-row-meta">
        <span className="lb-row-name">
          {you
            ? <span className="lb-row-name-you">{ll.youLabel || 'You'}</span>
            : (entry.name && entry.name[locale])}
          {you && entry.name && entry.name[locale] && (
            <span className="lb-row-name-real">{entry.name[locale]}</span>
          )}
        </span>
        <span className="lb-row-sub">
          {entry.team?.[locale] && (
            <span className="lb-row-team">{entry.team[locale]}</span>
          )}
          {showBadges && badges.length > 0 && (
            <span className="lb-row-badges" role="list">
              {badges.slice(0, 3).map((b) => (
                <span key={b.id}
                      className="lb-row-badge"
                      style={{ color: b.accent }}
                      title={b.label}
                      role="listitem"
                      aria-label={b.label}>
                  {_lbIcon({ name: b.glyph, size: 12 })}
                </span>
              ))}
              {badges.length > 3 && (
                <span className="lb-row-badge lb-row-badge-more tabular-nums">
                  +{_lbT(badges.length - 3, locale)}
                </span>
              )}
            </span>
          )}
        </span>
      </span>

      {/* score */}
      <span className="lb-row-score tabular-nums">{_lbScore(entry.score, locale)}</span>

      {/* delta */}
      <span className={[
        'lb-row-delta',
        dltSign > 0 ? 'is-up' : dltSign < 0 ? 'is-down' : 'is-flat',
      ].join(' ')}
            aria-label={
              dltSign > 0 ? ll.deltaUp?.(Math.abs(dlt))
                : dltSign < 0 ? ll.deltaDown?.(Math.abs(dlt))
                : ll.deltaFlat}>
        <span className="lb-row-delta-arrow" aria-hidden="true">
          {dltSign > 0 ? '▲' : dltSign < 0 ? '▼' : '—'}
        </span>
        {dltSign !== 0 && (
          <span className="lb-row-delta-num tabular-nums">{_lbT(Math.abs(dlt), locale)}</span>
        )}
      </span>
    </button>
  );
}


// ─── UserRankHero ──────────────────────────────────────────────────────────
// Hero card showing the current user's standing. Two variants:
//
//   variant="large"    Mobile hero — top of the page. Huge rank number,
//                      percentile + delta pills, progress bar, score line.
//                      When `podium=true` (or auto-detected from rank ≤ 3)
//                      the surface tints to gold and the copy switches to
//                      "تو روی سکویی!".
//
//   variant="compact"  Desktop sidebar — same content in a tighter card.
//                      No eyebrow / sub.
//
// Props
//   user        — LB_USER-shaped record (rank/score/deltaRank/percentile/
//                 nextTierRank). Defaults to window.LB_USER.
//   locale      — 'fa' | 'en' | 'ar'
//   variant     — 'large' (default) | 'compact'
//   onTap       — optional click handler (e.g. open detail / share)
function UserRankHero({
  user = window.LB_USER,
  locale = 'fa',
  variant = 'large',
  onTap,
}) {
  if (!user) return null;
  const ll = window.LB_I18N ? window.LB_I18N[locale] : {};
  const rank = user.rank;
  const podium = rank > 0 && rank <= 3;
  const podiumTier = rank === 1 ? 'gold' : rank === 2 ? 'silver' : rank === 3 ? 'bronze' : null;
  const tier = _lbTierFor(rank);
  const tierName = tier && ll[tier.nameKey];
  const tierAccent = tier?.accent || 'var(--accent-primary)';
  const progress  = _lbTierProgress(rank);

  // Climb spots to next tier — only meaningful when not already in the
  // top tier. We compute it from the tier's upper-better-tier floor.
  let toNext = null;
  if (tier && !podium) {
    const tiers = window.LB_REWARD_TIERS || [];
    const curIdx = tiers.indexOf(tier);
    if (curIdx > 0) {
      const betterFloor = tiers[curIdx - 1].rankRange[1]; // e.g. 100 for top100
      toNext = Math.max(0, rank - betterFloor);
    }
  }

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

  const initials = {
    fa: (user.name?.fa || 'ت').trim().charAt(0),
    en: (user.name?.en || 'Y').trim().charAt(0).toUpperCase(),
    ar: (user.name?.ar || 'أ').trim().charAt(0),
  };

  const inner = (
    <>
      {variant === 'large' && (
        <div className="lb-hero-eye">
          {podium ? ll.podiumYouOn : (ll.eyebrow || '')}
        </div>
      )}

      {/* Top row: tier pill (start) + percentile (end). RTL flips inline-edges automatically. */}
      <div className="lb-hero-top">
        {tierName && (
          <span className="lb-hero-tier"
                style={{ '--lb-tier-accent': tierAccent }}>
            {_lbIcon({ name: 'trophy', size: 11 })}
            <span>{tierName}</span>
          </span>
        )}
        {typeof user.percentile === 'number' && (
          <span className="lb-hero-pct">
            {ll.topPercentile?.(_lbT(user.percentile, locale))}
          </span>
        )}
      </div>

      {/* Center: HUGE rank with avatar at the side */}
      <div className="lb-hero-rank-row">
        <span className={`lb-hero-rank ${podium ? `lb-hero-rank--${podiumTier}` : ''}`}>
          <span className="lb-hero-rank-hash" aria-hidden="true">#</span>
          <span className="lb-hero-rank-num tabular-nums">{_lbT(rank, locale)}</span>
        </span>
        {variant === 'large' && (
          <span className="lb-hero-av">
            <LBAvatar seed={user.avatarSeed || 'you'}
                      initial={user.avatarInitial || initials}
                      size={48}
                      ringTier={podium ? podiumTier : 'accent'}
                      locale={locale}/>
          </span>
        )}
      </div>

      {/* Score + delta line */}
      <div className="lb-hero-stat">
        <span className="lb-hero-score">
          <span className="lb-hero-score-label">{ll.yourScore}</span>
          <span className="lb-hero-score-val tabular-nums">{_lbScore(user.score, locale)}</span>
        </span>
        {dltSign !== 0 && (
          <span className={`lb-hero-delta ${dltSign > 0 ? 'is-up' : 'is-down'}`}>
            <span aria-hidden="true">{dltSign > 0 ? '▲' : '▼'}</span>
            <span className="tabular-nums">{_lbT(Math.abs(dlt), locale)}</span>
            {variant === 'large' && (
              <span className="lb-hero-delta-sub">{ll.deltaSinceWeek}</span>
            )}
          </span>
        )}
        {dltSign === 0 && variant === 'large' && (
          <span className="lb-hero-delta is-flat">
            <span aria-hidden="true">—</span>
            <span className="lb-hero-delta-sub">{ll.deltaFlat}</span>
          </span>
        )}
      </div>

      {/* Progress to next tier — hidden when already in podium */}
      {!podium && tier && (
        <div className="lb-hero-prog">
          <div className="lb-hero-prog-bar"
               style={{ '--lb-tier-accent': tierAccent }}
               aria-hidden="true">
            <span className="lb-hero-prog-fill"
                  style={{ width: `${Math.round(progress * 100)}%` }}/>
          </div>
          {typeof toNext === 'number' && toNext > 0 && (
            <div className="lb-hero-prog-hint">
              {ll.nextTier?.(_lbT(toNext, locale))}
            </div>
          )}
        </div>
      )}
    </>
  );

  return (
    <div className={`lb-hero lb-hero--${variant} ${podium ? 'lb-hero--podium' : ''}`}
         style={{ '--lb-tier-accent': tierAccent, '--lb-tier-glow': tier?.glow || 'var(--accent-glow)' }}
         onClick={onTap}
         role={onTap ? 'button' : undefined}
         tabIndex={onTap ? 0 : undefined}>
      <span className="lb-hero-halo" aria-hidden="true"/>
      <div className="lb-hero-body">{inner}</div>
    </div>
  );
}


// ─── RewardTierStrip ───────────────────────────────────────────────────────
// Strip of reward tiers (podium / top-10 / top-100 / top-1000). Each tier
// card surfaces: tier label + rank range + coin reward + optional pack
// reward. The user's current tier is glow-marked (`is-current`); tiers
// above the user are visually "locked" (`is-locked`).
//
// Variants
//   variant="horizontal"  Mobile/strip — single h-scrollable row of pills.
//                         Compact: shows range + coins only.
//   variant="vertical"    Desktop sidebar — stacked column of richer cards.
//
// Props
//   tiers       — defaults to window.LB_REWARD_TIERS
//   userRank    — number — used to mark `is-current` and `is-locked`
//   locale      — 'fa' | 'en' | 'ar'
//   variant     — 'horizontal' (default) | 'vertical'
//   packsLookup — { [id]: pack }  — optional. When supplied, the pack
//                 name appears beside the coin reward. Defaults to
//                 indexing window.SHOP_PACKS by id.
function RewardTierStrip({
  tiers = window.LB_REWARD_TIERS,
  userRank,
  locale = 'fa',
  variant = 'horizontal',
  packsLookup,
}) {
  if (!Array.isArray(tiers) || tiers.length === 0) return null;
  const ll = window.LB_I18N ? window.LB_I18N[locale] : {};

  const lookup = packsLookup || (window.SHOP_PACKS
    ? Object.fromEntries(window.SHOP_PACKS.map((p) => [p.id, p]))
    : {});

  const userTier = typeof userRank === 'number' ? _lbTierFor(userRank) : null;
  const userTierIdx = userTier ? tiers.indexOf(userTier) : -1;

  return (
    <div className={`lb-rewards lb-rewards--${variant}`}>
      <div className="lb-rewards-track">
        {tiers.map((t, idx) => {
          const [a, b] = t.rankRange;
          const isCurrent = userTier && t.id === userTier.id;
          const isLocked  = userTierIdx >= 0 && idx < userTierIdx;
          const pack = t.reward?.packId && lookup[t.reward.packId];
          const packName = pack?.name?.[locale];
          return (
            <div key={t.id}
                 className={[
                   'lb-rewards-tier',
                   `lb-rewards-tier--${t.id.replace('t-', '')}`,
                   isCurrent ? 'is-current' : '',
                   isLocked  ? 'is-locked'  : '',
                 ].filter(Boolean).join(' ')}
                 style={{ '--lb-tier-accent': t.accent, '--lb-tier-glow': t.glow || 'var(--accent-glow)' }}>
              <div className="lb-rewards-head">
                <span className="lb-rewards-name">{ll[t.nameKey] || t.id}</span>
                <span className="lb-rewards-range tabular-nums">
                  {ll.rewardRange?.(_lbT(a, locale), _lbT(b, locale))}
                </span>
              </div>
              <div className="lb-rewards-prizes">
                {typeof t.reward?.coins === 'number' && (
                  <span className="lb-rewards-prize lb-rewards-prize--coin">
                    {_lbIcon({ name: 'coin', size: 13 })}
                    <span className="tabular-nums">
                      {ll.rewardCoins?.(_lbScore(t.reward.coins, locale))}
                    </span>
                  </span>
                )}
                {t.reward?.packId && (
                  <span className="lb-rewards-prize lb-rewards-prize--pack">
                    {_lbIcon({ name: 'gift', size: 13 })}
                    <span>{packName ? ll.rewardPackNamed?.(packName) : ll.rewardPack}</span>
                  </span>
                )}
              </div>
              {isCurrent && (
                <span className="lb-rewards-flag" aria-label={ll.rewardYourTier}>
                  {_lbIcon({ name: 'check', size: 11 })}
                  <span>{ll.rewardYourTier}</span>
                </span>
              )}
              {isLocked && (
                <span className="lb-rewards-lock"
                      aria-label={ll.rewardLockedFor?.(_lbT(b, locale))}>
                  {ll.rewardLockedFor?.(_lbT(b, locale))}
                </span>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}


// ─── HistoryCard ───────────────────────────────────────────────────────────
// Shows the user's last N matchweeks. Each cell renders:
//   • matchweek number
//   • rank that week
//   • delta vs the prior week
//
// Variants
//   variant="rail"  Mobile — horizontally scrollable rail of 3 cells.
//   variant="stack" Desktop — vertical stack with denser cells.
//
// Props
//   history     — array (defaults to user.history). Shape:
//                 { week, rank, score?, deltaRank }
//   locale      — 'fa' | 'en' | 'ar'
//   variant     — 'rail' (default) | 'stack'
function HistoryCard({
  user = window.LB_USER,
  history,
  locale = 'fa',
  variant = 'rail',
}) {
  const ll = window.LB_I18N ? window.LB_I18N[locale] : {};
  const items = history || user?.history || [];
  if (items.length === 0) {
    return (
      <div className={`lb-history lb-history--${variant} lb-history--empty`}>
        <span className="lb-history-empty-txt">{ll.historyNoData}</span>
      </div>
    );
  }
  return (
    <div className={`lb-history lb-history--${variant}`}>
      <div className="lb-history-track">
        {items.map((h, idx) => {
          const dlt = h.deltaRank;
          const dltSign = typeof dlt === 'number' ? Math.sign(dlt) : 0;
          return (
            <div key={h.week ?? idx} className="lb-history-cell">
              <div className="lb-history-week">
                {ll.historyWeek?.(_lbT(h.week, locale))}
              </div>
              <div className="lb-history-rank tabular-nums">
                {ll.historyRankShort?.(_lbT(h.rank, locale))}
              </div>
              {dltSign !== 0 && (
                <div className={`lb-history-delta ${dltSign > 0 ? 'is-up' : 'is-down'}`}>
                  <span aria-hidden="true">{dltSign > 0 ? '▲' : '▼'}</span>
                  <span className="tabular-nums">{_lbT(Math.abs(dlt), locale)}</span>
                </div>
              )}
              {dltSign === 0 && (
                <div className="lb-history-delta is-flat">
                  <span aria-hidden="true">—</span>
                </div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}


// ─── Export ────────────────────────────────────────────────────────────────
Object.assign(window, {
  UserRankHero, LeaderboardRow, RewardTierStrip, HistoryCard,
  // Exposed for foundation/showcase reuse — not strictly necessary for
  // the app to function, but Phase 4 artboards use them directly.
  RankBadge, LBAvatar,
});


// ─── CSS (lb- prefix, token-only) ──────────────────────────────────────────
const LEADERBOARD_PIECES_CSS = `
/* =========================================================================
   Common surface — every primitive in this file uses one of two card
   recipes: "glass" (subtle white-on-dark gradient) and "tinted" (a
   tier-accent washed glow). Both reuse the canonical §13.1 / §13.10
   surface pattern.
   ========================================================================= */

/* ─── RankBadge (podium pill) ─────────────────────────────────────────── */
.lb-rank-badge {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px;
  font-family: var(--font-current);
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1;
  position: relative;
  isolation: isolate;
}
.lb-rank-badge--sm { width: 26px; height: 26px; font-size: 12px; }
.lb-rank-badge--md { width: 30px; height: 30px; font-size: 13px; }
.lb-rank-badge--lg { width: 44px; height: 44px; font-size: 18px; }
.lb-rank-badge--gold {
  background: linear-gradient(150deg, #FCD34D 0%, #D97706 100%);
  color: #1a1208;
  box-shadow: 0 4px 14px -6px rgba(252,211,77,0.55);
}
.lb-rank-badge--silver {
  background: linear-gradient(150deg, #E2E8F0 0%, #64748B 100%);
  color: #0E1115;
  box-shadow: 0 4px 14px -6px rgba(148,163,184,0.45);
}
.lb-rank-badge--bronze {
  background: linear-gradient(150deg, #F59E0B 0%, #92400E 100%);
  color: #1F1305;
  box-shadow: 0 4px 14px -6px rgba(217,119,6,0.45);
}
.lb-rank-badge--plain {
  background: var(--bg-elevated);
  color: var(--text-secondary);
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
}
.lb-rank-badge-num { font-variant-numeric: tabular-nums; }

/* ─── Avatar (asset-less gradient + initial) ──────────────────────────── */
.lb-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  flex: 0 0 auto;
  position: relative;
  font-family: var(--font-current);
  font-weight: 800;
  user-select: none;
  text-transform: uppercase;
}
.lb-avatar.is-dim { opacity: 0.55; }
.lb-avatar-initial { line-height: 1; }
/* Ring tiers — soft outer glow + tinted border, layered using box-shadow
   to keep the avatar's pixel size unchanged. */
.lb-avatar--ring-gold {
  box-shadow: 0 0 0 2px #FCD34D, 0 0 12px -2px rgba(252,211,77,0.55);
}
.lb-avatar--ring-silver {
  box-shadow: 0 0 0 2px #94A3B8, 0 0 10px -2px rgba(148,163,184,0.45);
}
.lb-avatar--ring-bronze {
  box-shadow: 0 0 0 2px #D97706, 0 0 10px -2px rgba(217,119,6,0.45);
}
.lb-avatar--ring-platinum {
  box-shadow: 0 0 0 2px var(--tier-platinum), 0 0 10px -2px rgba(147,197,253,0.45);
}
.lb-avatar--ring-accent {
  box-shadow: 0 0 0 2px var(--accent-primary), 0 0 10px -2px var(--accent-glow);
}

/* =========================================================================
   LeaderboardRow
   ========================================================================= */
.lb-row {
  display: grid;
  grid-template-columns: 44px 36px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  width: 100%;
  border-radius: 12px;
  border: 1px solid var(--border-soft, rgba(255,255,255,0.06));
  background: linear-gradient(180deg, rgba(255,255,255,0.025), rgba(255,255,255,0.012));
  color: var(--text-primary);
  font-family: var(--font-current);
  text-align: start;
  cursor: pointer;
  transition: transform 160ms var(--ease-out, ease), border-color 160ms var(--ease-out, ease);
  appearance: none;
}
.lb-row:hover { transform: translateY(-1px); border-color: var(--border-strong, rgba(255,255,255,0.12)); }
.lb-row:focus-visible {
  outline: 2px solid var(--accent-primary);
  outline-offset: 2px;
}
.lb-row + .lb-row { margin-block-start: 6px; }

.lb-row-rank {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px;
}
.lb-row-rank-plain {
  font-size: 14px;
  font-weight: 700;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.lb-row-spark {
  position: absolute;
  inset-inline-end: -4px;
  inset-block-start: -4px;
  color: var(--tier-gold);
  animation: lb-spark 1800ms var(--ease-out, ease) infinite;
  transform-origin: center;
}
@keyframes lb-spark {
  0%, 100% { transform: rotate(-3deg) scale(0.95); opacity: 0.85; }
  50%      { transform: rotate(3deg)  scale(1.08); opacity: 1; }
}

.lb-row-av { display: inline-flex; align-items: center; }

.lb-row-meta {
  display: flex; flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.lb-row-name {
  font-size: 14px; font-weight: 700;
  color: var(--text-primary);
  display: inline-flex; gap: 6px; align-items: baseline;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lb-row-name-you {
  display: inline-flex;
  padding: 1px 7px;
  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-row-name-real {
  font-size: 12.5px; font-weight: 600;
  color: var(--text-secondary);
}
.lb-row-sub {
  display: inline-flex;
  gap: 8px;
  align-items: center;
  font-size: 11.5px;
  color: var(--text-tertiary);
  min-width: 0;
}
.lb-row-team { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.lb-row-badges {
  display: inline-flex; align-items: center; gap: 4px;
  flex-shrink: 0;
}
.lb-row-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px;
  border-radius: 999px;
  background: color-mix(in srgb, currentColor 16%, transparent);
  border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
  flex-shrink: 0;
}
.lb-row-badge-more {
  width: auto; padding: 0 6px;
  background: var(--bg-elevated);
  border-color: var(--border-soft, rgba(255,255,255,0.08));
  color: var(--text-tertiary);
  font-size: 10px;
  font-weight: 700;
}

.lb-row-score {
  font-size: 14.5px;
  font-weight: 700;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  padding-inline-start: 4px;
}

.lb-row-delta {
  display: inline-flex; align-items: center;
  gap: 3px;
  padding: 4px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 800;
  min-width: 44px;
  justify-content: center;
}
.lb-row-delta.is-up    {
  color: var(--success);
  background: color-mix(in srgb, var(--success) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--success) 28%, transparent);
}
.lb-row-delta.is-down  {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 28%, transparent);
}
.lb-row-delta.is-flat  {
  color: var(--text-tertiary);
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
}
.lb-row-delta-arrow { line-height: 1; font-size: 9px; }
.lb-row-delta-num   { font-variant-numeric: tabular-nums; }

/* Podium tints — surface gradient subtly tinted with the medal color. */
.lb-row--podium {
  border-color: color-mix(in srgb, var(--text-primary) 6%, transparent);
}
.lb-row--podium-gold {
  background:
    linear-gradient(180deg, rgba(252,211,77,0.10), rgba(252,211,77,0.025));
  border-color: rgba(252,211,77,0.35);
}
.lb-row--podium-silver {
  background:
    linear-gradient(180deg, rgba(148,163,184,0.10), rgba(148,163,184,0.025));
  border-color: rgba(148,163,184,0.32);
}
.lb-row--podium-bronze {
  background:
    linear-gradient(180deg, rgba(217,119,6,0.10), rgba(217,119,6,0.025));
  border-color: rgba(217,119,6,0.30);
}
.lb-row--top1 .lb-row-score { color: #FFE9A3; }

/* The "you" highlight — accent border + tinted surface. Wins specificity
   over any podium tint so the user always stands out. */
.lb-row--you {
  border-color: color-mix(in srgb, var(--accent-primary) 50%, transparent);
  background:
    linear-gradient(180deg, var(--accent-glow), rgba(113,99,217,0.05));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-primary) 22%, transparent) inset;
}

/* Sticky "user row" companion — visual modifier ONLY (accent border +
   glow). Positioning is owned by the .lb-sticky-bar wrapper in the app
   file: a scroll-driven IntersectionObserver mounts the bar at the top
   of the scroll area when UserRankHero leaves the viewport, then slides
   it out when the hero comes back. Animating only transform + opacity,
   per the v1 motion budget. */
.lb-row--sticky-you {
  border-color: color-mix(in srgb, var(--accent-primary) 60%, transparent);
  box-shadow:
    0 12px 24px -16px var(--accent-glow),
    0 0 0 1px color-mix(in srgb, var(--accent-primary) 22%, transparent) inset;
}

/* =========================================================================
   UserRankHero
   ========================================================================= */
.lb-hero {
  position: relative;
  isolation: isolate;
  border-radius: 20px;
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
  background:
    linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.015)),
    var(--bg-elevated);
  color: var(--text-primary);
  font-family: var(--font-current);
  overflow: hidden;
}
.lb-hero-halo {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(circle at 12% 0%,
                    color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 18%, transparent) 0%,
                    transparent 55%),
    radial-gradient(circle at 100% 110%,
                    color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 12%, transparent) 0%,
                    transparent 60%);
  z-index: 0;
}
.lb-hero-body {
  position: relative;
  z-index: 1;
  display: flex; flex-direction: column;
  gap: 12px;
}
.lb-hero--large .lb-hero-body { padding: 18px 18px 16px; gap: 14px; }
.lb-hero--compact .lb-hero-body { padding: 14px 14px 12px; gap: 10px; }

.lb-hero-eye {
  font-size: 11px;
  font-weight: 700;
  color: var(--text-tertiary);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.lb-hero-top {
  display: flex; align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}
.lb-hero-tier {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px;
  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: 11px;
  font-weight: 700;
  line-height: 1;
}
.lb-hero-tier > svg { color: var(--lb-tier-accent, var(--accent-primary)); }
.lb-hero-pct {
  display: inline-flex; align-items: center;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
  color: var(--text-secondary);
  font-size: 11px;
  font-weight: 700;
}

.lb-hero-rank-row {
  display: flex; align-items: flex-end;
  justify-content: space-between;
  gap: 14px;
}
.lb-hero-rank {
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
  font-family: var(--font-display-current, var(--font-current));
  letter-spacing: -0.04em;
  font-weight: 900;
  line-height: 0.95;
  color: var(--text-primary);
}
.lb-hero--large   .lb-hero-rank { font-size: 64px; }
.lb-hero--compact .lb-hero-rank { font-size: 44px; }
.lb-hero-rank-hash {
  font-size: 0.58em;
  color: var(--text-tertiary);
  font-weight: 700;
  letter-spacing: 0;
  align-self: flex-start;
  margin-inline-end: 2px;
}
.lb-hero-rank-num { font-variant-numeric: tabular-nums; }
.lb-hero-rank--gold   { background: linear-gradient(135deg, #FCD34D, #F59E0B 60%, #FFF7CC);
                        -webkit-background-clip: text; background-clip: text;
                        color: transparent; }
.lb-hero-rank--silver { background: linear-gradient(135deg, #E2E8F0, #94A3B8 60%, #F8FAFC);
                        -webkit-background-clip: text; background-clip: text;
                        color: transparent; }
.lb-hero-rank--bronze { background: linear-gradient(135deg, #F59E0B, #92400E 60%, #FED7AA);
                        -webkit-background-clip: text; background-clip: text;
                        color: transparent; }
.lb-hero-av { display: inline-flex; align-items: center; }
.lb-hero--compact .lb-hero-av { display: none; } /* avatar already inferred from chrome */

.lb-hero-stat {
  display: flex; align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.lb-hero-score { display: flex; flex-direction: column; gap: 2px; }
.lb-hero-score-label { font-size: 11px; color: var(--text-tertiary); font-weight: 600; }
.lb-hero-score-val   { font-size: 18px; font-weight: 800; color: var(--text-primary);
                       font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }

.lb-hero-delta {
  display: inline-flex; align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 999px;
  font-weight: 800;
  font-size: 12.5px;
}
.lb-hero-delta.is-up   {
  color: var(--success);
  background: color-mix(in srgb, var(--success) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--success) 32%, transparent);
}
.lb-hero-delta.is-down {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 32%, transparent);
}
.lb-hero-delta.is-flat {
  color: var(--text-tertiary);
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
}
.lb-hero-delta-sub {
  font-size: 10.5px;
  font-weight: 600;
  opacity: 0.85;
  margin-inline-start: 2px;
}

.lb-hero-prog { display: flex; flex-direction: column; gap: 6px; }
.lb-hero-prog-bar {
  position: relative;
  height: 6px;
  border-radius: 999px;
  background: rgba(255,255,255,0.06);
  overflow: hidden;
}
.lb-hero-prog-fill {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  border-radius: 999px;
  background: linear-gradient(90deg,
              var(--lb-tier-accent, var(--accent-primary)),
              color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 60%, var(--accent-primary-2)));
  box-shadow: 0 0 12px -2px var(--lb-tier-glow, var(--accent-glow));
  transition: width 480ms var(--ease-out, ease);
}
.lb-hero-prog-hint {
  font-size: 11.5px;
  color: var(--text-secondary);
  font-weight: 600;
}

/* Podium-mode hero — when user is on the podium, the surface tilts toward
   the gold accent and the rank number gets the gradient text treatment. */
.lb-hero--podium { border-color: rgba(252,211,77,0.32); }
.lb-hero--podium .lb-hero-halo {
  background:
    radial-gradient(circle at 18% 0%, rgba(252,211,77,0.20) 0%, transparent 55%),
    radial-gradient(circle at 90% 110%, rgba(217,119,6,0.14) 0%, transparent 60%);
}

/* =========================================================================
   RewardTierStrip
   ========================================================================= */
.lb-rewards { width: 100%; }
.lb-rewards-track {
  display: grid;
  gap: 8px;
}
.lb-rewards--horizontal .lb-rewards-track {
  grid-auto-flow: column;
  grid-auto-columns: minmax(160px, 1fr);
  overflow-x: auto;
  scroll-snap-type: inline mandatory;
  scrollbar-width: thin;
  padding-block-end: 4px;
}
.lb-rewards--vertical .lb-rewards-track {
  grid-auto-flow: row;
  grid-template-columns: 1fr;
  gap: 10px;
}

.lb-rewards-tier {
  position: relative;
  padding: 12px;
  border-radius: 14px;
  background:
    linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.012)),
    color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 6%, var(--bg-elevated));
  border: 1px solid color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 24%, transparent);
  color: var(--text-primary);
  display: flex; flex-direction: column;
  gap: 6px;
  scroll-snap-align: start;
  min-width: 0;
  overflow: hidden;
}
.lb-rewards--vertical .lb-rewards-tier { padding: 14px; gap: 8px; }

.lb-rewards-head {
  display: flex; align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.lb-rewards-name {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.01em;
}
.lb-rewards-range {
  font-size: 11px;
  color: var(--text-tertiary);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.lb-rewards-prizes {
  display: flex; flex-wrap: wrap;
  gap: 6px;
}
.lb-rewards-prize {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 8px;
  border-radius: 999px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
  font-size: 11.5px;
  font-weight: 700;
  color: var(--text-primary);
  white-space: nowrap;
}
.lb-rewards-prize--coin svg { color: var(--tier-gold); }
.lb-rewards-prize--pack svg { color: var(--accent-primary); }

.lb-rewards-flag {
  display: inline-flex; align-items: center; gap: 5px;
  align-self: flex-start;
  padding: 3px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 38%, transparent);
  color: var(--text-primary);
  font-size: 10.5px;
  font-weight: 800;
}
.lb-rewards-flag svg { color: var(--lb-tier-accent, var(--accent-primary)); }

.lb-rewards-lock {
  align-self: flex-start;
  font-size: 10.5px;
  color: var(--text-tertiary);
  font-weight: 600;
}

.lb-rewards-tier.is-current {
  border-color: color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 60%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--lb-tier-accent, var(--accent-primary)) 20%, transparent) inset,
              0 12px 30px -16px var(--lb-tier-glow, var(--accent-glow));
}
.lb-rewards-tier.is-locked  { opacity: 0.6; }

/* =========================================================================
   HistoryCard
   ========================================================================= */
.lb-history {
  width: 100%;
  font-family: var(--font-current);
}
.lb-history--rail .lb-history-track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(120px, 1fr);
  gap: 8px;
  overflow-x: auto;
  scroll-snap-type: inline mandatory;
  padding-block-end: 4px;
}
.lb-history--stack .lb-history-track {
  display: grid;
  grid-auto-flow: row;
  grid-template-columns: 1fr;
  gap: 8px;
}
.lb-history-cell {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 8px;
  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));
  color: var(--text-primary);
  scroll-snap-align: start;
}
.lb-history--rail .lb-history-cell {
  grid-template-columns: 1fr;
  text-align: start;
  gap: 4px;
}
.lb-history-week {
  font-size: 11px;
  color: var(--text-tertiary);
  font-weight: 700;
  letter-spacing: 0.04em;
}
.lb-history-rank {
  font-family: var(--font-display-current, var(--font-current));
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.lb-history--stack .lb-history-rank { font-size: 16px; }
.lb-history-delta {
  display: inline-flex; align-items: center;
  gap: 4px;
  padding: 3px 7px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 800;
  justify-self: start;
}
.lb-history--stack .lb-history-delta { justify-self: end; }
.lb-history-delta.is-up {
  color: var(--success);
  background: color-mix(in srgb, var(--success) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--success) 28%, transparent);
}
.lb-history-delta.is-down {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 28%, transparent);
}
.lb-history-delta.is-flat {
  color: var(--text-tertiary);
  background: var(--bg-elevated);
  border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
}
.lb-history--empty {
  display: flex; align-items: center; justify-content: center;
  padding: 18px;
  border-radius: 14px;
  border: 1px dashed var(--border-soft, rgba(255,255,255,0.12));
  color: var(--text-tertiary);
}
.lb-history-empty-txt { font-size: 12px; }

/* =========================================================================
   Reduced motion — kill the only animation in this file
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  .lb-row-spark    { animation: none !important; }
  .lb-row          { transition: none !important; }
  .lb-hero-prog-fill { transition: none !important; }
}
`;
const __leaderboardPiecesStyle = document.createElement('style');
__leaderboardPiecesStyle.textContent = LEADERBOARD_PIECES_CSS;
document.head.appendChild(__leaderboardPiecesStyle);
