// prediction-pieces.jsx — Primitives for the Prediction Hub (پیش‌بینی هفته).
//
// Public primitives (exported to window at the bottom):
//   PredictionHero          — page eyebrow + week title + deadline countdown +
//                             total reward + sponsor LED. ۱۴۰۰ desktop hero variant
//                             with `desktop` prop.
//   PredictionStatsStrip    — 3-up counter strip (open · total Q · answered)
//   PredictionFilterChips   — Tab/chip row driving the visible match list
//   PredictionMatchRow      — Heterogeneous list item (4 visual states),
//                             inline-expand reveals PredictionForm.
//   PredictionFeaturedCard  — Big visual variant of a match (hero treatment;
//                             same expand mechanism).
//   PredictionForm          — Renders a match's question list (5 question types
//                             handled inline; submit + reward summary).
//   PredictionQuestionRow   — Single question (score/3way/range/bool/scorer).
//   PredictionRewardPreview — Sticky preview band: max reward if all correct +
//                             progress (X of Y answered).
//   PredictionHistoryCard   — Recent weeks list with coin earned + accuracy.
//   PredictionEmptyState    — "no open matches today" empty layout.

const _prT = (n, locale) =>
  (typeof window.toLocaleDigits === 'function')
    ? window.toLocaleDigits(n, locale)
    : String(n);

// Shared icon set reuses Home Hub's HHIcon when available — otherwise inline.
function PRIcon({ name, size = 16, strokeWidth = 1.7, style }) {
  if (typeof window.HHIcon === 'function') {
    // Home Hub already loaded — proxy through to keep stroke parity.
    return window.HHIcon === HHIcon
      ? <HHIcon name={name} size={size} strokeWidth={strokeWidth} style={style}/>
      : React.createElement(window.HHIcon, { name, size, strokeWidth, style });
  }
  const s = size;
  const common = {
    width: s, height: s, viewBox: '0 0 24 24', fill: 'none',
    stroke: 'currentColor', strokeWidth, strokeLinecap: 'round', strokeLinejoin: 'round', style,
  };
  switch (name) {
    case 'coin':     return <svg {...common}><circle cx="12" cy="12" r="8.5"/><circle cx="12" cy="12" r="5.2"/><circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none"/></svg>;
    case 'clock':    return <svg {...common}><circle cx="12" cy="12" r="8.5"/><path d="M12 7v5.5L15.5 15"/></svg>;
    case 'predict':  return <svg {...common}><path d="M12 3v3M12 18v3M3 12h3M18 12h3"/><circle cx="12" cy="12" r="3.2"/></svg>;
    case 'check':    return <svg {...common}><path d="M4 12.5l5 5 11-12"/></svg>;
    case 'chevron':  return <svg {...common}><path d="M9 6l6 6-6 6"/></svg>;
    case 'lock':     return <svg {...common}><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V8a4 4 0 1 1 8 0v3"/></svg>;
    case 'plus':     return <svg {...common}><path d="M12 5v14M5 12h14"/></svg>;
    case 'minus':    return <svg {...common}><path d="M5 12h14"/></svg>;
    case 'fire':     return <svg {...common}><path d="M12 22c-4 0-7-2.7-7-6.5 0-2.8 1.6-4.4 3-5.6 1.2-1 1.5-2 1.2-3.6 2 .8 3.4 2.3 3.5 4.3 2-1.7 2.8-3 2.8-4.6 2 2.5 3.5 5.4 3.5 8.6 0 4-3 7.4-7 7.4z"/></svg>;
    case 'trophy':   return <svg {...common}><path d="M8 4h8v4a4 4 0 1 1-8 0V4z"/><path d="M5 5h3v3a3 3 0 0 1-3-3zM19 5h-3v3a3 3 0 0 0 3-3zM10 13.5v3M14 13.5v3M8 20h8M9 17h6"/></svg>;
    case 'edit':     return <svg {...common}><path d="M14.5 5.5l4 4-9.5 9.5H5v-4l9.5-9.5z"/><path d="M13 7l4 4"/></svg>;
    case 'bell':     return <svg {...common}><path d="M6 16V11a6 6 0 1 1 12 0v5l1.5 2.2H4.5L6 16z"/><path d="M10 20.5a2 2 0 0 0 4 0"/></svg>;
    default:         return null;
  }
}

// ─── PredictionHero ────────────────────────────────────────────────────────
// Page title block. Shows: eyebrow + week N + date range, total reward, deadline
// countdown, sponsor LED billboard along the bottom (reuses the same animated
// marquee as the Home Hub PredictionSlider for brand parity).
function PredictionHero({ locale = 'fa', week = PR_WEEK, stats = PR_STATS,
                          sponsorId = 'mci', desktop = false, onAllMatches }) {
  const ll = PR_I18N[locale];
  const sp = PR_SPONSORS[sponsorId];
  const cd = `${_prT(String(week.deadline.h).padStart(2,'0'), locale)}:`
           + `${_prT(String(week.deadline.m).padStart(2,'0'), locale)}:`
           + `${_prT(String(week.deadline.s).padStart(2,'0'), locale)}`;
  return (
    <header className={`pr-hero ${desktop ? 'pr-hero--desktop' : ''}`}>
      <div className="pr-hero-bg" aria-hidden="true">
        <div className="pr-hero-bg-glow"></div>
        <div className="pr-hero-bg-vignette"></div>
      </div>

      {/* LED billboard — sponsor */}
      <div className="pr-hero-led" aria-hidden="true">
        <div className="pr-hero-led-track">
          {[0,1,2,3].map((i) => (
            <span key={i} className="pr-hero-led-item">
              <span className="pr-hero-led-eyebrow">{sp.tag[locale]}</span>
              <span className="pr-hero-led-sep">·</span>
              <span className="pr-hero-led-cta">{sp.tagline[locale]}</span>
              <span className="pr-hero-led-sep">·</span>
            </span>
          ))}
        </div>
        <div className="pr-hero-led-shine" aria-hidden="true"></div>
      </div>

      <div className="pr-hero-body">
        <div className="pr-hero-titles">
          <div className="pr-hero-eyebrow">
            <PRIcon name="predict" size={11}/>
            <span>{ll.pageEyebrow}</span>
          </div>
          <h1 className="pr-hero-title">
            {`${ll.weekOf} ${_prT(week.number, locale)}`}
            <span className="pr-hero-title-sep">·</span>
            <span className="pr-hero-title-sub">{week.competitionLabel[locale]}</span>
          </h1>
          <div className="pr-hero-daterange">{week.dateRange[locale]}</div>
        </div>

        <div className="pr-hero-meta">
          <div className="pr-hero-reward">
            <PRIcon name="coin" size={14}/>
            <span className="pr-hero-reward-val tabular-nums">
              {_prT(stats.rewardMax.toLocaleString('en-US'), locale)}
            </span>
            <span className="pr-hero-reward-sub">{ll.coinShort}</span>
          </div>
          <div className="pr-hero-deadline">
            <PRIcon name="clock" size={11}/>
            <span className="pr-hero-deadline-label">{ll.nextLock}</span>
            <span className="pr-hero-deadline-value tabular-nums">{cd}</span>
          </div>
        </div>
      </div>
    </header>
  );
}

// ─── PredictionStatsStrip ──────────────────────────────────────────────────
// v1.3: Refactored from a bespoke 3-cell strip to a grid of three canonical
// <StatTile/> primitives (homehub-pieces.jsx · §14.1 row 4 of the audit).
// Visual parity with the rest of the Home Hub stat row (XP, rank, …).
function PredictionStatsStrip({ locale = 'fa', stats = PR_STATS, onFilter }) {
  const ll = PR_I18N[locale];
  const Tile = window.StatTile;
  const cells = [
    { key: 'active', value: _prT(stats.active,   locale), label: ll.statsActive, icon: 'clock',   filter: 'today', accent: 'var(--info, #60A5FA)' },
    { key: 'total',  value: _prT(stats.totalQs,  locale), label: ll.statsTotal,  icon: 'predict', filter: 'all',   accent: 'var(--text-secondary, #B8B5AB)' },
    { key: 'done',   value: _prT(stats.answered, locale), label: ll.statsDone,   icon: 'check',   filter: 'done',  accent: 'var(--accent-primary, var(--accent, #7163D9))' },
  ];
  // Defensive fallback: render the legacy strip if StatTile isn't loaded.
  if (typeof Tile !== 'function') {
    return (
      <div className="pr-stats" role="list">
        {cells.map((c, i) => (
          <React.Fragment key={c.key}>
            <button type="button" role="listitem"
                    className="pr-stats-cell"
                    onClick={() => onFilter && onFilter(c.filter)}>
              <div className="pr-stats-value tabular-nums">{c.value}</div>
              <div className="pr-stats-label">{c.label}</div>
            </button>
            {i < cells.length - 1 && <span className="pr-stats-divider" aria-hidden="true"/>}
          </React.Fragment>
        ))}
      </div>
    );
  }
  return (
    <div className="pr-stats-grid" role="list">
      {cells.map((c) => (
        <button key={c.key} type="button" role="listitem"
                className="pr-stats-tile-btn"
                onClick={() => onFilter && onFilter(c.filter)}
                aria-label={`${c.label} · ${c.value}`}>
          <Tile locale={locale} mode="plain"
                icon={c.icon} label={c.label} value={c.value}
                accent={c.accent}/>
        </button>
      ))}
    </div>
  );
}

// ─── PredictionFilterChips ─────────────────────────────────────────────────
// v1.3: Adapter over the canonical <FilterChips/> primitive from
// lineup-pickers.jsx (Design Log §14.1 row 1). The canonical chip group
// already supports counts — see `c.count` in lineup-pickers — so this is a
// pure data adapter. Wrapped in `.pr-chips-scroll` to preserve horizontal
// overflow / scroll-snap behaviour on narrow mobile widths.
function PredictionFilterChips({ locale = 'fa', filter = 'all', counts = {}, onChange }) {
  const ll = PR_I18N[locale];
  const Canonical = window.FilterChips;
  const chips = [
    { key: 'all',      label: ll.filterAll,      count: counts.all },
    { key: 'today',    label: ll.filterToday,    count: counts.today },
    { key: 'featured', label: ll.filterFeatured, count: counts.featured },
    { key: 'finished', label: ll.filterFinished, count: counts.finished },
  ];
  // Defensive fallback: render local chips if canonical isn't loaded yet.
  if (typeof Canonical !== 'function') {
    return (
      <div className="pr-chips" role="tablist">
        {chips.map((it) => (
          <button key={it.key} type="button" role="tab"
                  aria-selected={filter === it.key}
                  className={`pr-chip ${filter === it.key ? 'is-active' : ''}`}
                  onClick={() => onChange && onChange(it.key)}>
            <span className="pr-chip-label">{it.label}</span>
            <span className="pr-chip-count tabular-nums">{_prT(it.count || 0, locale)}</span>
          </button>
        ))}
      </div>
    );
  }
  return <Canonical chips={chips} value={filter} onChange={onChange} locale={locale}/>;
}

// ─── PRTeamCrest ───────────────────────────────────────────────────────────
// v1.3: Proxy to the canonical <TeamCrest/> primitive in homehub-pieces.jsx
// (Design Log §14.1 row 6). Kept as a thin compatibility shim so existing
// call-sites continue to work; new code should use TeamCrest directly.
function PRTeamCrest(props) {
  if (typeof window.TeamCrest === 'function') {
    return React.createElement(window.TeamCrest, props);
  }
  // Defensive inline fallback if homehub-pieces hasn't loaded.
  const { team, size = 36, layout = 'inline', dim = false } = props;
  if (!team) return null;
  const isLight = (team.color || '').toLowerCase() === '#ffffff'
               || (team.color || '').toLowerCase() === '#fff';
  return (
    <div className={`hh-crest hh-crest--${layout} ${dim ? 'is-dim' : ''}`}
         style={{
           width: size, height: size,
           background: `linear-gradient(155deg, ${team.color} 0%, rgba(0,0,0,0.6) 100%)`,
           color: isLight ? '#0E0E16' : 'rgba(255,255,255,0.95)',
         }}
         aria-hidden="true">
      <span className="hh-crest-code" style={{ fontSize: Math.max(10, Math.round(size * 0.32)) }}>
        {team.code}
      </span>
    </div>
  );
}

// ─── PredictionMatchRow ────────────────────────────────────────────────────
// Compact / mid-density row. Renders the right shell depending on `state`:
//   open      → border subtle, "پیش‌بینی کن" CTA
//   partial   → border accent muted, "ادامه" CTA, progress bar shown
//   predicted → border accent, "ذخیره شد" badge, "ادامه" CTA
//   locked    → dashed dim border, lock chip, "منتظر نتیجه"
//   settled   → success border, reward earned chip + "دیدن نتایج"
//
// When `expanded=true`, the row becomes a wrapper that renders a sibling
// PredictionForm beneath it (handled by the parent — keeps row stateless).
function PredictionMatchRow({ locale = 'fa', match, dateMode = 'hybrid',
                              expanded = false, onToggle, onChangeAnswer,
                              variant = 'row' /* 'row' | 'featured' */ }) {
  const ll = PR_I18N[locale];
  const isFeatured = variant === 'featured';
  const home = match.home, away = match.away;
  const answered = Object.keys(match.userAnswers || {}).length;
  const totalQs  = match.questions.length;
  const ratio    = totalQs > 0 ? answered / totalQs : 0;

  // Countdown for non-settled / non-locked matches
  const cd = (match.state === 'locked' || match.state === 'settled')
    ? null
    : `${_prT(String(match.hours || 0).padStart(2,'0'), locale)}:`
    + `${_prT(String(match.minutes || 0).padStart(2,'0'), locale)}:`
    + `${_prT(String(match.seconds || 0).padStart(2,'0'), locale)}`;

  const dPrimary = dateMode === 'gregorian' ? match.dateGregorian
                 : (match.dateJalali ? match.dateJalali[locale] : '');

  // State-specific badge — sits as the row's eyebrow / state tag.
  const stateTag = (() => {
    switch (match.state) {
      case 'open':      return match.featured
                          ? { label: match.tag === 'derby' ? ll.tagDerby
                                  : match.tag === 'classico' ? ll.tagClassico
                                  : ll.tagFeatured,
                              tone: 'featured', icon: 'fire' }
                          : { label: ll.tagNormal, tone: 'normal' };
      case 'partial':   return { label: ll.tagNewN(_prT(totalQs - answered, locale)), tone: 'partial', icon: 'plus' };
      case 'predicted': return { label: ll.qSubmitted, tone: 'predicted', icon: 'check' };
      case 'locked':    return { label: ll.tagLocked, tone: 'locked', icon: 'lock' };
      case 'settled':   return { label: ll.tagSettled, tone: 'settled', icon: 'check' };
      default:          return null;
    }
  })();

  // Footer right-side CTA — varies by state, all routed to onToggle for inline expand.
  const ctaLabel = (() => {
    if (match.state === 'open')      return ll.ctaPredict;
    if (match.state === 'partial')   return ll.ctaContinue;
    if (match.state === 'predicted') return ll.ctaSavedView;
    if (match.state === 'locked')    return ll.ctaSavedView;
    if (match.state === 'settled')   return ll.ctaSettledView;
    return ll.ctaExpand;
  })();

  return (
    <article className={`pr-row pr-row--${variant} pr-row--state-${match.state} ${expanded ? 'is-expanded' : ''} ${match.featured ? 'is-featured' : ''}`}
             aria-label={`${home[locale]} ${ll.vs} ${away[locale]}`}>
      {/* Top: state tag + league + countdown */}
      <header className="pr-row-top">
        {stateTag && (
          <span className={`pr-row-tag pr-row-tag--${stateTag.tone}`}>
            {stateTag.icon && <PRIcon name={stateTag.icon} size={10}/>}
            <span>{stateTag.label}</span>
          </span>
        )}
        <span className="pr-row-league">{match.leagueShort ? match.leagueShort[locale] : match.league[locale]}</span>
        <span className="pr-row-time-meta">
          {match.state === 'locked' && (
            <span className="pr-row-live">
              <span className="pr-row-live-dot" aria-hidden="true"/>
              <span>{ll.locked}</span>
            </span>
          )}
          {match.state === 'settled' && (
            <span className="pr-row-settled-meta">
              <PRIcon name="trophy" size={11}/>
              <span className="tabular-nums">{ll.correctAnswers(_prT(match.settled.correctCount, locale), _prT(match.settled.totalCount, locale))}</span>
            </span>
          )}
          {cd && (
            <span className="pr-row-countdown tabular-nums">
              <PRIcon name="clock" size={11}/>
              <span>{cd}</span>
            </span>
          )}
        </span>
      </header>

      {/* Teams block — featured variant is bigger */}
      <div className={`pr-row-teams ${isFeatured ? 'is-big' : ''}`}>
        <div className="pr-row-team">
          <PRTeamCrest team={home} size={isFeatured ? 56 : 36}/>
          <div className="pr-row-team-meta">
            <div className="pr-row-team-name">{home[locale]}</div>
            <div className="pr-row-team-code">{home.code}</div>
          </div>
          {match.state === 'settled' && (
            <div className="pr-row-final-score tabular-nums">{_prT(match.settled.actualScore.home, locale)}</div>
          )}
        </div>

        <div className="pr-row-center">
          {match.state === 'settled'
            ? <div className="pr-row-vs-score">VS</div>
            : <div className="pr-row-vs">{ll.vs}</div>}
          {match.state !== 'settled' && match.state !== 'locked' && (
            <div className="pr-row-kick tabular-nums">{match.kickoff[locale]}</div>
          )}
          <div className="pr-row-date">{dPrimary}</div>
        </div>

        <div className="pr-row-team pr-row-team--away">
          {match.state === 'settled' && (
            <div className="pr-row-final-score tabular-nums">{_prT(match.settled.actualScore.away, locale)}</div>
          )}
          <div className="pr-row-team-meta pr-row-team-meta--away">
            <div className="pr-row-team-name">{away[locale]}</div>
            <div className="pr-row-team-code">{away.code}</div>
          </div>
          <PRTeamCrest team={away} size={isFeatured ? 56 : 36}/>
        </div>
      </div>

      {/* Progress bar — only when there's something to fill */}
      {(match.state === 'open' || match.state === 'partial' || match.state === 'predicted') && (
        <div className="pr-row-progress">
          <div className="pr-row-progress-meta">
            <span className="pr-row-progress-text">{ll.questionsOf(_prT(answered, locale), _prT(totalQs, locale))}</span>
            <span className="pr-row-progress-reward tabular-nums">
              <PRIcon name="coin" size={11}/>
              <span>{ll.rewardUpTo(_prT(match.rewardMax.toLocaleString('en-US'), locale))}</span>
            </span>
          </div>
          <div className="pr-row-progress-bar" aria-hidden="true">
            <span style={{ width: `${Math.round(ratio * 100)}%` }}/>
          </div>
        </div>
      )}

      {/* Locked state info */}
      {match.state === 'locked' && (
        <div className="pr-row-locked-info">
          <PRIcon name="lock" size={14}/>
          <span>{ll.inCalculation} · {ll.questionsOf(_prT(answered, locale), _prT(totalQs, locale))}</span>
        </div>
      )}

      {/* Settled reward summary */}
      {match.state === 'settled' && (
        <div className="pr-row-settled">
          <div className="pr-row-settled-coin">
            <PRIcon name="coin" size={14}/>
            <span className="tabular-nums">{ll.earnedCoins(_prT(match.settled.coinEarned.toLocaleString('en-US'), locale))}</span>
          </div>
          <div className="pr-row-settled-xp tabular-nums">{ll.earnedXP(_prT(match.settled.xpEarned, locale))}</div>
        </div>
      )}

      {/* Footer CTA — consistent across states, label changes by state */}
      <footer className="pr-row-foot">
        <button type="button"
                className={`pr-row-cta pr-row-cta--${match.state} ${expanded ? 'is-open' : ''}`}
                onClick={() => onToggle && onToggle(match.id)}>
          <span>{ctaLabel}</span>
          <PRIcon name="chevron" size={14} style={{ transform: expanded ? 'rotate(90deg)' : '' }}/>
        </button>
      </footer>

      {/* Inline-expand body — rendered inside the row to keep visual continuity */}
      {expanded && (
        <div className="pr-row-expand">
          <PredictionForm locale={locale} match={match}
                          onChangeAnswer={onChangeAnswer}/>
        </div>
      )}
    </article>
  );
}

// ─── PredictionForm ────────────────────────────────────────────────────────
// Renders the question rows + save bar. Each question gets its own
// `PredictionQuestionRow`. Settled / locked states render answers in read-only.
//
// `hideBar` (v1.3): when true, omit the bottom save/locked bar — used when the
// form is hosted in a bottom sheet whose footer holds the CTA externally.
function PredictionForm({ locale = 'fa', match, onChangeAnswer, hideBar = false }) {
  const ll = PR_I18N[locale];
  const readOnly = match.state === 'locked' || match.state === 'settled';

  return (
    <div className={`pr-form ${readOnly ? 'is-readonly' : ''}`}>
      <div className="pr-form-list">
        {match.questions.map((q) => (
          <PredictionQuestionRow
            key={q.id}
            locale={locale}
            question={q}
            match={match}
            answer={match.userAnswers?.[q.id]}
            actual={match.settled?.[`actual${q.id === 'score' ? 'Score'
                                       : q.id === '3way' ? 'Winner'
                                       : q.id === 'total' ? 'Total'
                                       : q.id === 'btts' ? 'Btts'
                                       : q.id === 'scorer' ? 'Scorer' : ''}`]}
            correctness={match.correctness?.[q.id]}
            readOnly={readOnly}
            onChange={(v) => onChangeAnswer && onChangeAnswer(match.id, q.id, v)}
          />
        ))}
      </div>

      {!hideBar && !readOnly && (
        <div className="pr-form-bar">
          <div className="pr-form-bar-meta">
            <PRIcon name="check" size={13}/>
            <div className="pr-form-bar-text">
              <div className="pr-form-bar-title">{ll.ctaSaved}</div>
              <div className="pr-form-bar-sub">{ll.ctaEditable}</div>
            </div>
          </div>
          <button type="button" className="pr-form-bar-cta">
            <span>{ll.ctaSave}</span>
          </button>
        </div>
      )}

      {!hideBar && match.state === 'locked' && (
        <div className="pr-form-bar pr-form-bar--locked">
          <div className="pr-form-bar-meta">
            <PRIcon name="lock" size={13}/>
            <div className="pr-form-bar-text">
              <div className="pr-form-bar-title">{ll.locked}</div>
              <div className="pr-form-bar-sub">{ll.inCalculation}</div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ─── PredictionMatchSheet ──────────────────────────────────────────────────
// Mobile-first bottom sheet that hosts the full prediction form for one
// match. Built on top of the canonical MyCardsDetailModal (`layout="sheetCustom"`)
// per §13.8 + §14.0 of the Design Log — replaces the previous inline-expand
// pattern, which was ergonomically inadequate on small viewports.
//
//   • Header (sticky): both team crests + countdown + state badge + close
//   • Body (scrollable): <PredictionForm hideBar/>
//   • Footer (sticky): submit CTA (open/partial/predicted) · locked/settled hint
//   • Dismiss: ESC · backdrop tap · close button · swipe-down on handle/header
//   • Height: 90vh per Design Log §14.0
//   • `inline` (default true): renders the sheet absolutely positioned within
//     the nearest positioned ancestor (the .pr-screen phone frame). This is
//     mandatory for design-canvas artboards — the canvas applies pan/zoom
//     transforms, and a portal to document.body would escape that frame.
//     Set `inline={false}` for real production deployment (full-window flow).
function PredictionMatchSheet({ locale = 'fa', match, open, onClose,
                                  dateMode = 'hybrid', onChangeAnswer,
                                  inline = true }) {
  if (!match) return null;
  const ll = PR_I18N[locale];
  const home = match.home, away = match.away;
  const readOnly = match.state === 'locked' || match.state === 'settled';

  // Countdown for non-settled/non-locked matches
  const cd = (match.state === 'locked' || match.state === 'settled')
    ? null
    : `${_prT(String(match.hours || 0).padStart(2,'0'), locale)}:`
    + `${_prT(String(match.minutes || 0).padStart(2,'0'), locale)}:`
    + `${_prT(String(match.seconds || 0).padStart(2,'0'), locale)}`;

  // State tag (mirrors the row's eyebrow logic, abridged)
  const stateTag = (() => {
    switch (match.state) {
      case 'open':      return match.featured
                          ? { label: match.tag === 'derby' ? ll.tagDerby
                                  : match.tag === 'classico' ? ll.tagClassico
                                  : ll.tagFeatured,
                              tone: 'featured', icon: 'fire' }
                          : { label: ll.tagNormal, tone: 'normal' };
      case 'partial':   {
        const answered = Object.keys(match.userAnswers || {}).length;
        const remaining = (match.questions?.length || 0) - answered;
        return { label: ll.tagNewN(_prT(remaining, locale)), tone: 'partial', icon: 'plus' };
      }
      case 'predicted': return { label: ll.qSubmitted, tone: 'predicted', icon: 'check' };
      case 'locked':    return { label: ll.tagLocked, tone: 'locked', icon: 'lock' };
      case 'settled':   return { label: ll.tagSettled, tone: 'settled', icon: 'check' };
      default:          return null;
    }
  })();

  const dPrimary = dateMode === 'gregorian' ? match.dateGregorian
                 : (match.dateJalali ? match.dateJalali[locale] : '');

  const ctaLabel = (() => {
    if (match.state === 'open')      return ll.ctaSave;
    if (match.state === 'partial')   return ll.ctaSave;
    if (match.state === 'predicted') return ll.ctaSave;
    return null;
  })();

  const header = (
    <div className="pr-sheet-head" aria-label={`${home[locale]} ${ll.vs} ${away[locale]}`}>
      <div className="pr-sheet-head-top">
        {stateTag && (
          <span className={`pr-row-tag pr-row-tag--${stateTag.tone}`}>
            {stateTag.icon && <PRIcon name={stateTag.icon} size={10}/>}
            <span>{stateTag.label}</span>
          </span>
        )}
        <span className="pr-sheet-head-league">{match.leagueShort ? match.leagueShort[locale] : match.league[locale]}</span>
        {cd && (
          <span className="pr-row-countdown tabular-nums">
            <PRIcon name="clock" size={11}/>
            <span>{cd}</span>
          </span>
        )}
        {match.state === 'locked' && (
          <span className="pr-row-live">
            <span className="pr-row-live-dot" aria-hidden="true"/>
            <span>{ll.locked}</span>
          </span>
        )}
      </div>
      <div className="pr-sheet-head-teams">
        <div className="pr-sheet-head-team">
          <PRTeamCrest team={home} size={40}/>
          <div className="pr-sheet-head-team-meta">
            <div className="pr-sheet-head-team-name">{home[locale]}</div>
            <div className="pr-sheet-head-team-code">{home.code}</div>
          </div>
        </div>
        <div className="pr-sheet-head-center">
          <div className="pr-row-vs">{ll.vs}</div>
          {match.kickoff && (match.state !== 'locked') && (
            <div className="pr-row-kick tabular-nums">{match.kickoff[locale]}</div>
          )}
          {dPrimary && <div className="pr-row-date">{dPrimary}</div>}
        </div>
        <div className="pr-sheet-head-team pr-sheet-head-team--away">
          <div className="pr-sheet-head-team-meta pr-sheet-head-team-meta--away">
            <div className="pr-sheet-head-team-name">{away[locale]}</div>
            <div className="pr-sheet-head-team-code">{away.code}</div>
          </div>
          <PRTeamCrest team={away} size={40}/>
        </div>
      </div>
    </div>
  );

  const body = (
    <PredictionForm locale={locale} match={match}
                    onChangeAnswer={onChangeAnswer} hideBar/>
  );

  const footer = (() => {
    if (match.state === 'locked') {
      return (
        <div className="pr-sheet-foot pr-sheet-foot--info">
          <PRIcon name="lock" size={13}/>
          <div className="pr-sheet-foot-text">
            <div className="pr-sheet-foot-title">{ll.locked}</div>
            <div className="pr-sheet-foot-sub">{ll.inCalculation}</div>
          </div>
        </div>
      );
    }
    if (match.state === 'settled') {
      return (
        <div className="pr-sheet-foot pr-sheet-foot--info">
          <PRIcon name="trophy" size={13}/>
          <div className="pr-sheet-foot-text">
            <div className="pr-sheet-foot-title">{ll.tagSettled}</div>
            <div className="pr-sheet-foot-sub tabular-nums">
              {ll.correctAnswers(_prT(match.settled.correctCount, locale),
                                  _prT(match.settled.totalCount, locale))}
            </div>
          </div>
        </div>
      );
    }
    const answered = Object.keys(match.userAnswers || {}).length;
    const totalQs  = match.questions.length;
    return (
      <button type="button" className="pr-sheet-cta">
        <span className="pr-sheet-cta-label">{ctaLabel}</span>
        <span className="pr-sheet-cta-meta tabular-nums">
          {ll.questionsOf(_prT(answered, locale), _prT(totalQs, locale))}
        </span>
      </button>
    );
  })();

  // In production the sheet is 90vh of the viewport; inside a design-canvas
  // artboard the same value would be calculated against the browser viewport
  // (not the phone frame), so for inline rendering use 90% of the parent.
  const mobileHeight = inline ? '90%' : '90vh';
  return (
    <MyCardsDetailModal
      open={open}
      onClose={onClose}
      locale={locale}
      layout="sheetCustom"
      mobileHeight={mobileHeight}
      swipeToDismiss={true}
      accent={'var(--accent-primary)'}
      inline={inline}
      header={header}
      body={body}
      footer={footer}
    />
  );
}

// ─── PredictionQuestionRow ─────────────────────────────────────────────────
// One question. Switches body by `question.type`. In read-only states we still
// render the inputs but disabled, with a "your pick vs actual" mini-row
// (settled) or "waiting for result" hint (locked).
function PredictionQuestionRow({ locale = 'fa', question, match, answer, actual,
                                  correctness, readOnly = false, onChange }) {
  const ll = PR_I18N[locale];
  const ttl = (() => {
    switch (question.id) {
      case 'score':  return ll.qScoreTitle;
      case '3way':   return ll.q3wayTitle;
      case 'total':  return ll.qTotalGoalsTitle;
      case 'btts':   return ll.qBttsTitle;
      case 'scorer': return ll.qScorerTitle;
      default:       return '';
    }
  })();

  const help = (() => {
    switch (question.id) {
      case 'score':  return ll.qScoreHelp;
      case 'total':  return ll.qTotalGoalsHelp;
      case 'scorer': return ll.qScorerHelp;
      default:       return null;
    }
  })();

  const answered = answer != null && answer !== '' &&
    !(typeof answer === 'object' && Object.keys(answer).length === 0);

  // Status pill at top-right of question
  const statusPill = (() => {
    if (question.lockedUntilKick && match.state !== 'settled') {
      return { tone: 'locked', label: ll.qLocked, icon: 'lock' };
    }
    if (match.state === 'settled') {
      if (correctness === 'correct') return { tone: 'correct', label: ll.qCorrect, icon: 'check' };
      if (correctness === 'near')    return { tone: 'near',    label: ll.qNear };
      if (correctness === 'wrong')   return { tone: 'wrong',   label: ll.qWrong };
      return null;
    }
    if (match.state === 'locked' && answered) return { tone: 'waiting', label: ll.qWaiting };
    if (answered)                              return { tone: 'answered', label: ll.qSubmitted, icon: 'check' };
    return null;
  })();

  return (
    <article className={`pr-q pr-q--${question.id} pr-q--state-${match.state} ${question.lockedUntilKick && match.state !== 'settled' ? 'is-q-locked' : ''} ${statusPill ? `is-${statusPill.tone}` : ''}`}>
      <header className="pr-q-head">
        <div className="pr-q-title-block">
          <h4 className="pr-q-title">{ttl}</h4>
          {help && <div className="pr-q-help">{help}</div>}
        </div>
        <div className="pr-q-head-meta">
          {statusPill && (
            <span className={`pr-q-status pr-q-status--${statusPill.tone}`}>
              {statusPill.icon && <PRIcon name={statusPill.icon} size={10}/>}
              <span>{statusPill.label}</span>
            </span>
          )}
          <span className="pr-q-reward tabular-nums">
            <PRIcon name="coin" size={11}/>
            <span>{ll.rewardUpTo(_prT(question.rewardMax, locale))}</span>
          </span>
        </div>
      </header>

      {/* Body switches by type */}
      <div className="pr-q-body">
        {question.id === 'score'  && <QScore  locale={locale} match={match} value={answer} readOnly={readOnly} actual={actual} onChange={onChange}/>}
        {question.id === '3way'   && <Q3way   locale={locale} match={match} value={answer} readOnly={readOnly} actual={actual} onChange={onChange}/>}
        {question.id === 'total'  && <QRange  locale={locale} match={match} value={answer} readOnly={readOnly} actual={actual} onChange={onChange}/>}
        {question.id === 'btts'   && <QBool   locale={locale} match={match} value={answer} readOnly={readOnly} actual={actual} onChange={onChange}/>}
        {question.id === 'scorer' && <QScorer locale={locale} match={match} value={answer} readOnly={readOnly} options={question.options} lockedUntilKick={question.lockedUntilKick} actual={actual} onChange={onChange}/>}
      </div>

      {/* Settled extra row: yours vs actual side-by-side */}
      {match.state === 'settled' && (
        <div className="pr-q-settled">
          <div className="pr-q-settled-cell">
            <div className="pr-q-settled-label">{ll.qActual}</div>
            <div className="pr-q-settled-value">{renderSettledValue(question, actual, match, locale)}</div>
          </div>
          <div className="pr-q-settled-cell">
            <div className="pr-q-settled-label">{ll.qYours}</div>
            <div className="pr-q-settled-value">{renderSettledValue(question, answer, match, locale)}</div>
          </div>
        </div>
      )}
    </article>
  );
}

function renderSettledValue(q, v, match, locale) {
  const ll = PR_I18N[locale];
  if (v == null) return '—';
  switch (q.id) {
    case 'score':  return v.home != null
        ? `${_prT(v.home, locale)} - ${_prT(v.away, locale)}`
        : '—';
    case '3way':   return v === 'home' ? match.home[locale]
                       : v === 'away' ? match.away[locale]
                       : v === 'draw' ? ll.q3wayDraw : String(v);
    case 'total':  return v === 'low' ? ll.qTotalLow
                       : v === 'mid' ? ll.qTotalMid
                       : v === 'high' ? ll.qTotalHigh : String(v);
    case 'btts':   return v === true ? ll.qBttsYes
                       : v === false ? ll.qBttsNo : String(v);
    case 'scorer': return (typeof v === 'object' && v?.name)
                       ? (v.name[locale] || v.name.fa)
                       : v === 'other' ? ll.qScorerOther
                       : v === 'none'  ? ll.qScorerNone
                       : String(v);
    default:       return String(v);
  }
}

// ─── Question type bodies ──────────────────────────────────────────────────
// Score: two ScrollNumber wheels (home / away) with a ":" separator.
// Replaces the previous ± / value / ± stepper cluster from v1.3.2 — the
// stepper was felt to be too many taps for goal counts that frequently land
// 0..3, and the wheel reads more like the score lines users see on TV.
// Canonical primitive: <ScrollNumber> from lineup-pickers.jsx (Design Log §۱۶.۳).
// If ScrollNumber isn't loaded yet (script order / older bundle), we fall
// back to the stepper so the form stays usable.
function QScore({ locale = 'fa', match, value, readOnly, onChange }) {
  const ll = PR_I18N[locale];
  const home = value?.home ?? 0;
  const away = value?.away ?? 0;
  const set = (k, n) => {
    if (readOnly) return;
    const nv = Math.max(0, Math.min(9, n));
    onChange && onChange({ home: k === 'home' ? nv : home, away: k === 'away' ? nv : away });
  };
  const SN = window.ScrollNumber;
  if (!SN) {
    // Fallback: legacy stepper (kept verbatim from v1.3.2).
    return (
      <div className="pr-q-score pr-q-score--stepper">
        <div className="pr-q-score-cluster">
          <div className="pr-q-score-team">{match.home[locale]}</div>
          <div className="pr-q-score-stepper">
            <button type="button" disabled={readOnly} aria-label="-"
                    onClick={() => set('home', home - 1)}><PRIcon name="minus" size={14}/></button>
            <div className="pr-q-score-value tabular-nums">{_prT(home, locale)}</div>
            <button type="button" disabled={readOnly} aria-label="+"
                    onClick={() => set('home', home + 1)}><PRIcon name="plus" size={14}/></button>
          </div>
        </div>
        <div className="pr-q-score-sep">:</div>
        <div className="pr-q-score-cluster">
          <div className="pr-q-score-team">{match.away[locale]}</div>
          <div className="pr-q-score-stepper">
            <button type="button" disabled={readOnly} aria-label="-"
                    onClick={() => set('away', away - 1)}><PRIcon name="minus" size={14}/></button>
            <div className="pr-q-score-value tabular-nums">{_prT(away, locale)}</div>
            <button type="button" disabled={readOnly} aria-label="+"
                    onClick={() => set('away', away + 1)}><PRIcon name="plus" size={14}/></button>
          </div>
        </div>
      </div>
    );
  }
  // Stagger the away wheel's intro spin by 120ms so the pair feels alive
  // (not a single sync'd "two slot-machines tied together" gimmick). The
  // home wheel starts immediately. In settled (readOnly) state the intro
  // is disabled by ScrollNumber automatically, so the read-only "you
  // predicted X" view stays static — only the active prediction form spins.
  return (
    <div className="pr-q-score pr-q-score--wheel">
      <div className="pr-q-score-cluster">
        <div className="pr-q-score-team">{match.home[locale]}</div>
        <SN locale={locale}
            min={0} max={9}
            value={home}
            disabled={readOnly}
            onChange={(n) => set('home', n)}
            size="medium"
            introDelay={0}
            ariaLabel={`${ll.qScoreLabel || ''} ${match.home[locale]}`.trim()}/>
      </div>
      <div className="pr-q-score-sep" aria-hidden="true">:</div>
      <div className="pr-q-score-cluster">
        <div className="pr-q-score-team">{match.away[locale]}</div>
        <SN locale={locale}
            min={0} max={9}
            value={away}
            disabled={readOnly}
            onChange={(n) => set('away', n)}
            size="medium"
            introDelay={120}
            ariaLabel={`${ll.qScoreLabel || ''} ${match.away[locale]}`.trim()}/>
      </div>
    </div>
  );
}

// 3-way: home / draw / away
function Q3way({ locale = 'fa', match, value, readOnly, onChange }) {
  const ll = PR_I18N[locale];
  const opts = [
    { id: 'home', label: match.home[locale] },
    { id: 'draw', label: ll.q3wayDraw },
    { id: 'away', label: match.away[locale] },
  ];
  return (
    <div className="pr-q-grid pr-q-grid--3">
      {opts.map((o) => (
        <button key={o.id} type="button" disabled={readOnly}
                className={`pr-q-opt ${value === o.id ? 'is-selected' : ''}`}
                onClick={() => onChange && onChange(o.id)}>{o.label}</button>
      ))}
    </div>
  );
}

// Range: low/mid/high
function QRange({ locale = 'fa', value, readOnly, onChange }) {
  const ll = PR_I18N[locale];
  const opts = [
    { id: 'low',  label: ll.qTotalLow },
    { id: 'mid',  label: ll.qTotalMid },
    { id: 'high', label: ll.qTotalHigh },
  ];
  return (
    <div className="pr-q-grid pr-q-grid--3">
      {opts.map((o) => (
        <button key={o.id} type="button" disabled={readOnly}
                className={`pr-q-opt ${value === o.id ? 'is-selected' : ''}`}
                onClick={() => onChange && onChange(o.id)}>{o.label}</button>
      ))}
    </div>
  );
}

// Bool: yes / no
function QBool({ locale = 'fa', value, readOnly, onChange }) {
  const ll = PR_I18N[locale];
  return (
    <div className="pr-q-grid pr-q-grid--2">
      <button type="button" disabled={readOnly}
              className={`pr-q-opt ${value === true ? 'is-selected' : ''}`}
              onClick={() => onChange && onChange(true)}>{ll.qBttsYes}</button>
      <button type="button" disabled={readOnly}
              className={`pr-q-opt ${value === false ? 'is-selected' : ''}`}
              onClick={() => onChange && onChange(false)}>{ll.qBttsNo}</button>
    </div>
  );
}

// Scorer: list of player options + "another" + "none". Locked-until-kick shows
// a dimmed body with a small unlock hint.
function QScorer({ locale = 'fa', value, readOnly, options = [], lockedUntilKick, onChange, match }) {
  const ll = PR_I18N[locale];
  if (lockedUntilKick && match.state !== 'settled') {
    return (
      <div className="pr-q-locked-body">
        <PRIcon name="lock" size={14}/>
        <span>{ll.qLocked}</span>
      </div>
    );
  }
  const selectedKey = (typeof value === 'object' && value?.code)
    ? `${value.code}:${(value.name?.fa || value.name?.en || '')}`
    : value;
  return (
    <div className="pr-q-scorer">
      {options.map((p, i) => {
        const key = `${p.code}:${p.name.fa}`;
        const selected = selectedKey === key;
        return (
          <button key={key} type="button" disabled={readOnly}
                  className={`pr-q-scorer-row ${selected ? 'is-selected' : ''}`}
                  onClick={() => onChange && onChange({ code: p.code, name: p.name, position: p.position })}>
            <div className="pr-q-scorer-avatar" aria-hidden="true">{p.name[locale].slice(0,1)}</div>
            <div className="pr-q-scorer-info">
              <div className="pr-q-scorer-name">{p.name[locale]}</div>
              <div className="pr-q-scorer-meta">{p.code} · {p.position}</div>
            </div>
          </button>
        );
      })}
      <div className="pr-q-scorer-extras">
        <button type="button" disabled={readOnly}
                className={`pr-q-scorer-extra ${value === 'other' ? 'is-selected' : ''}`}
                onClick={() => onChange && onChange('other')}>{ll.qScorerOther}</button>
        <button type="button" disabled={readOnly}
                className={`pr-q-scorer-extra ${value === 'none' ? 'is-selected' : ''}`}
                onClick={() => onChange && onChange('none')}>{ll.qScorerNone}</button>
      </div>
    </div>
  );
}

// ─── PredictionRewardPreview ───────────────────────────────────────────────
// Sticky-ish summary band shown above the list (or bottom in desktop sidebar).
// Visualises potential max payout + how far the user is.
function PredictionRewardPreview({ locale = 'fa', stats = PR_STATS,
                                    progress, total, variant = 'inline' }) {
  const ll = PR_I18N[locale];
  const have = progress != null ? progress : stats.answered;
  const max  = total    != null ? total    : stats.totalQs;
  const pct  = max > 0 ? have / max : 0;
  return (
    <section className={`pr-reward pr-reward--${variant}`}>
      <div className="pr-reward-bg" aria-hidden="true"/>
      <div className="pr-reward-head">
        <PRIcon name="trophy" size={14}/>
        <div className="pr-reward-titles">
          <div className="pr-reward-title">{ll.rewardPreviewTitle}</div>
          <div className="pr-reward-sub">{ll.rewardPreviewSub}</div>
        </div>
      </div>
      <div className="pr-reward-amount">
        <PRIcon name="coin" size={20}/>
        <span className="pr-reward-num tabular-nums">{_prT(stats.rewardMax.toLocaleString('en-US'), locale)}</span>
        <span className="pr-reward-unit">{ll.coinShort}</span>
      </div>
      <div className="pr-reward-progress">
        <div className="pr-reward-progress-meta">
          <span>{ll.rewardSoFar}</span>
          <span className="tabular-nums">{ll.rewardSoFarOf(_prT(have, locale), _prT(max, locale))}</span>
        </div>
        <div className="pr-reward-progress-bar" aria-hidden="true">
          <span style={{ width: `${Math.round(pct * 100)}%` }}/>
        </div>
      </div>
    </section>
  );
}

// ─── PredictionHistoryCard ─────────────────────────────────────────────────
function PredictionHistoryCard({ locale = 'fa', history = PR_HISTORY, variant = 'compact' }) {
  const ll = PR_I18N[locale];
  return (
    <section className={`pr-history pr-history--${variant}`}>
      <header className="pr-history-head">
        <div className="pr-history-titles">
          <h3 className="pr-history-title">{ll.historyTitle}</h3>
          <div className="pr-history-sub">{ll.historySub}</div>
        </div>
        <button type="button" className="pr-history-all">
          <span>{ll.historyAll}</span>
          <PRIcon name="chevron" size={12}/>
        </button>
      </header>
      <div className="pr-history-list">
        {history.map((w) => (
          <button key={w.id} type="button" className="pr-history-row">
            <div className="pr-history-week">
              <div className="pr-history-week-num tabular-nums">{_prT(w.week, locale)}</div>
              <div className="pr-history-week-label">{ll.weekShort}</div>
            </div>
            <div className="pr-history-meta">
              <div className="pr-history-date">{w.dateRange[locale]}</div>
              <div className="pr-history-stats">
                <span className="pr-history-acc tabular-nums">
                  <PRIcon name="check" size={11}/>
                  <span>{ll.historyOf(_prT(w.correct, locale), _prT(w.totalQs, locale))}</span>
                </span>
                <span className="pr-history-coin tabular-nums">
                  <PRIcon name="coin" size={11}/>
                  <span>{ll.historyRewardEarned(_prT(w.coinEarned.toLocaleString('en-US'), locale))}</span>
                </span>
              </div>
            </div>
            <PRIcon name="chevron" size={14}/>
          </button>
        ))}
      </div>
    </section>
  );
}

// ─── PredictionEmptyState ──────────────────────────────────────────────────
// v1.3: Refactored from a bespoke .pr-empty layout to the canonical
// <HHEmptyState/> primitive from homehub-pieces.jsx (Design Log §14.1 row 2).
// All copy still comes from PR_I18N so the page's voice is unchanged.
function PredictionEmptyState({ locale = 'fa', onCta, onSecondaryCta }) {
  const ll = PR_I18N[locale];
  // Defensive fallback if homehub-pieces hasn't loaded yet — should not happen
  // in any HTML that loads `prediction-pieces.jsx` (homehub-pieces loads first).
  const Empty = window.HHEmptyState;
  if (!Empty) return null;
  return (
    <Empty locale={locale}
           glyphIcon="predict"
           title={ll.emptyTitle}
           body={ll.emptyBody}
           ctaLabel={ll.emptyCta}
           ctaIcon={null}
           onCta={onCta}
           secondaryCtaLabel={ll.emptyCta2}
           secondaryCtaIcon="bell"
           onSecondaryCta={onSecondaryCta}/>
  );
}

// Export all to window for downstream files (prediction-app, Foundation).
Object.assign(window, {
  PredictionHero, PredictionStatsStrip, PredictionFilterChips,
  PredictionMatchRow, PredictionForm, PredictionQuestionRow,
  PredictionRewardPreview, PredictionHistoryCard, PredictionEmptyState,
  PRTeamCrest, PRIcon,
  PredictionMatchSheet,
});

// ─── Sheet-specific styles (header + footer chrome for the bottom sheet) ───
const PR_SHEET_CSS = `
  /* Header inside MyCardsDetailModal sheetCustom (Prediction Hub) */
  .pr-sheet-head {
    display: flex; flex-direction: column;
    gap: 8px;
    padding-inline-end: 36px; /* leave room for close button */
  }
  .pr-sheet-head-top {
    display: flex; align-items: center; gap: 8px;
    flex-wrap: wrap;
    font-size: 11px;
    color: var(--text-tertiary);
  }
  .pr-sheet-head-league {
    color: var(--text-secondary);
    font-weight: 600;
  }
  .pr-sheet-head-teams {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 10px;
  }
  .pr-sheet-head-team {
    display: flex; align-items: center; gap: 10px;
    min-width: 0;
  }
  .pr-sheet-head-team--away {
    justify-content: flex-end;
  }
  .pr-sheet-head-team-meta {
    display: flex; flex-direction: column;
    gap: 1px;
    min-width: 0;
  }
  .pr-sheet-head-team-meta--away { text-align: end; }
  .pr-sheet-head-team-name {
    font-size: 13px; font-weight: 700; color: var(--text-primary);
    line-height: 1.2;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .pr-sheet-head-team-code {
    font-size: 10px; color: var(--text-tertiary);
    letter-spacing: 0.06em;
    font-family: 'JetBrains Mono', ui-monospace, monospace;
  }
  html[lang="fa"] .pr-sheet-head-team-code,
  html[lang="ar"] .pr-sheet-head-team-code {
    font-family: inherit; letter-spacing: 0;
  }
  .pr-sheet-head-center {
    display: flex; flex-direction: column; align-items: center;
    gap: 2px;
    min-width: 60px;
  }

  /* Footer CTA — canonical glass primary (matches .lu-submit-cta).
     v1.3.1: switched from solid accent gradient to translucent glass +
     accent-tinted backplate for parity with the rest of the design system
     (Design Log §13.2). */
  .pr-sheet-cta {
    position: relative;
    display: flex; align-items: center; justify-content: space-between;
    gap: 12px;
    width: 100%;
    padding: 14px 22px;
    border-radius: 999px;
    border: 1px solid rgba(113,99,217,0.55);
    background:
      linear-gradient(180deg,
        rgba(255,255,255,0.12) 0%,
        rgba(255,255,255,0.04) 100%),
      rgba(113,99,217,0.22);
    color: #FFFFFF;
    font-family: inherit;
    font-size: 14px; font-weight: 700;
    cursor: pointer;
    -webkit-backdrop-filter: blur(10px) saturate(140%);
    backdrop-filter: blur(10px) saturate(140%);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,0.20),
      inset 0 -1px 0 rgba(0,0,0,0.20),
      0 8px 22px rgba(0,0,0,0.35);
    text-shadow: 0 1px 2px rgba(0,0,0,0.45);
    transition: transform .12s, box-shadow .15s, background .15s, border-color .15s;
  }
  .pr-sheet-cta:hover:not(:disabled) {
    transform: translateY(-1px);
    border-color: rgba(113,99,217,0.72);
    background:
      linear-gradient(180deg,
        rgba(255,255,255,0.16) 0%,
        rgba(255,255,255,0.06) 100%),
      rgba(113,99,217,0.30);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,0.26),
      inset 0 -1px 0 rgba(0,0,0,0.20),
      0 10px 26px rgba(0,0,0,0.45);
  }
  .pr-sheet-cta:active:not(:disabled) { transform: translateY(0) scale(0.98); }
  .pr-sheet-cta:disabled {
    border-color: rgba(255,255,255,0.08);
    background: rgba(255,255,255,0.03);
    color: var(--text-tertiary);
    cursor: not-allowed;
    box-shadow: none;
    text-shadow: none;
  }
  .pr-sheet-cta-meta {
    font-size: 12px; font-weight: 600;
    opacity: 0.86;
    padding: 4px 10px;
    border-radius: 999px;
    background: rgba(0,0,0,0.28);
    text-shadow: none;
  }

  /* Locked / settled footer — informational (not a button) */
  .pr-sheet-foot--info {
    display: flex; align-items: center; gap: 10px;
    padding: 12px 14px;
    border-radius: 14px;
    background: rgba(255,255,255,0.03);
    border: 1px solid rgba(255,255,255,0.06);
    color: var(--text-secondary);
  }
  .pr-sheet-foot-text { display: flex; flex-direction: column; }
  .pr-sheet-foot-title { font-size: 12.5px; color: var(--text-primary); font-weight: 600; }
  .pr-sheet-foot-sub   { font-size: 11px;  color: var(--text-tertiary); }
`;
const __prSheetStyle = document.createElement('style');
__prSheetStyle.textContent = PR_SHEET_CSS;
document.head.appendChild(__prSheetStyle);
