// progress.jsx — Progress dashboard (exported to window)

function StatTile({ label, value, unit, th }) {
  return (
    <div style={{
      flex: 1, minWidth: 0, padding: '15px 16px', borderRadius: 18,
      background: th.surface, border: '1px solid ' + th.border,
    }}>
      <div style={{ fontSize: 26, fontWeight: 300, lineHeight: 1, color: th.text }}>
        {value}{unit && <span style={{ fontSize: 13, color: th.faint, marginLeft: 4 }}>{unit}</span>}
      </div>
      <div style={{ fontSize: 11, letterSpacing: .6, color: th.faint, marginTop: 8, fontWeight: 300 }}>{label}</div>
    </div>
  );
}

// A premium-gated statistic. Locked: name + lock + PREMIUM, tappable → subscription.
// Unlocked: shows the real value (and an optional mini chart).
function LockStat({ icon, name, value, unit, sub, premium, onLocked, accent, th, wide, chart }) {
  const locked = !premium;
  return (
    <div onClick={locked ? () => onLocked('stats') : undefined} style={{
      gridColumn: wide ? '1 / -1' : 'auto', position: 'relative', overflow: 'hidden',
      padding: '15px 16px', borderRadius: 18, cursor: locked ? 'pointer' : 'default',
      background: locked ? th.surface : `linear-gradient(150deg, ${accent[0]}14, ${th.surface} 70%)`,
      border: '1px solid ' + (locked ? th.border : accent[0] + '3a'),
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
        <span style={{ color: locked ? th.faint : accent[0], display: 'flex' }}>{icon}</span>
        <span style={{ fontSize: 12.5, color: locked ? th.dim : th.text, fontWeight: 300, flex: 1, minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{name}</span>
        {locked && <span style={{ color: th.ghost, display: 'flex', flexShrink: 0 }}><IconLock size={14} sw={1.7} /></span>}
      </div>
      {locked ? (
        <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <span style={{ fontSize: 22, fontWeight: 300, color: th.ghost, letterSpacing: 2 }}>···</span>
          <PremiumPill />
        </div>
      ) : chart ? (
        <div style={{ marginTop: 12, display: 'flex', alignItems: 'flex-end', gap: 6, height: 46 }}>
          {chart.map((h, i) => (
            <div key={i} style={{ flex: 1, height: Math.max(8, h * 46), borderRadius: 4, background: `linear-gradient(180deg, ${accent[0]}, ${accent[1]})`, opacity: .55 + h * 0.45 }} />
          ))}
        </div>
      ) : (
        <div style={{ marginTop: 10 }}>
          <span style={{ fontSize: 26, fontWeight: 300, color: th.text, lineHeight: 1 }}>{value}</span>
          {unit && <span style={{ fontSize: 13, color: th.faint, marginLeft: 4 }}>{unit}</span>}
          {sub && <span style={{ fontSize: 12, color: th.faint, marginLeft: 6 }}>{sub}</span>}
        </div>
      )}
    </div>
  );
}
// module-scope translator shim (set by ProgressScreen render) so LockStat can read 'premium' label
let t_ = (k) => k;

function ProgressScreen({ level, levelIdx, accent, th, totalHours, nextHours, stats, unlocked, isPremium, onLocked, onEditGoals, onShare, onOpenJourney, onOpenAchievements, onUseFreeze, onAutoProtect }) {
  const { t, lang } = useI18n();
  t_ = t;
  const isMax = levelIdx >= LEVELS.length - 1;
  const isEmpty = stats.totalMinutes === 0;
  const prevHours = level.hours;
  const span = Math.max(1, nextHours - prevHours);
  const pct = isMax ? 100 : Math.min(100, Math.round(((totalHours - prevHours) / span) * 100));
  const orbDot = (lv) => `radial-gradient(circle at 38% 30%, ${lv.left}, ${lv.glow} 52%, ${lv.right})`;

  return (
    <div className="scroll-area" style={{ height: '100%', padding: '64px 20px 96px' }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
        <div style={{ fontSize: 30, fontWeight: 300, letterSpacing: .3, color: th.text }}>{t('progress_title')}</div>
        <button onClick={onShare} aria-label={t('share')} style={{ flexShrink: 0, width: 40, height: 40, borderRadius: 13, cursor: 'pointer',
          display: 'flex', alignItems: 'center', justifyContent: 'center', background: th.surface, border: '1px solid ' + th.border, color: accent[0] }}>
          <IconShare size={18} />
        </button>
      </div>

      {/* hero level card */}
      <div style={{
        marginTop: 18, padding: '24px 22px', borderRadius: 26, position: 'relative', overflow: 'hidden',
        background: `linear-gradient(150deg, ${level.glow}26, ${th.surface} 60%)`,
        border: '1px solid ' + level.glow + '40',
      }}>
        <div style={{ position: 'absolute', right: -40, top: -40, width: 160, height: 160, borderRadius: '50%',
          background: `radial-gradient(circle, ${level.glow}55, transparent 70%)`, filter: 'blur(8px)' }} />
        <div style={{ position: 'relative' }}>
          <div style={{ fontSize: 11, letterSpacing: 3, color: level.glow, fontWeight: 500, textTransform: 'uppercase' }}>{t('current_level')}</div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginTop: 8 }}>
            <RankBadge levelIdx={levelIdx} size={40} />
            <div style={{ fontSize: 34, fontWeight: 300, color: th.text }}>{level.name}</div>
          </div>
          <div style={{ fontSize: 14, color: th.dim, fontWeight: 300, marginTop: 6 }}>{t('tag_' + level.key)}</div>

          <div style={{ marginTop: 22, display: 'flex', alignItems: 'baseline', gap: 8 }}>
            <span style={{ fontSize: 40, fontWeight: 200, color: th.text }}>{fmtHours(totalHours)}</span>
            <span style={{ fontSize: 15, color: th.dim }}>{t('total_focus_hours')}</span>
          </div>

          {/* progress bar */}
          <div style={{ marginTop: 16 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, color: th.dim, marginBottom: 8 }}>
              <span>{isMax ? t('max_rank') : t('progress_to', { name: LEVELS[levelIdx + 1].name })}</span>
              <span style={{ color: level.glow }}>{isMax ? '∞' : `${fmtHours(totalHours)} / ${nextHours.toLocaleString()} h`}</span>
            </div>
            <div style={{ height: 10, borderRadius: 99, background: th.track, overflow: 'hidden' }}>
              <div style={{
                width: pct + '%', height: '100%', borderRadius: 99,
                background: `linear-gradient(90deg, ${level.left}, ${level.right})`,
                boxShadow: `0 0 5px ${level.glow}33`, transition: 'width .8s cubic-bezier(.2,.8,.2,1)',
              }} />
            </div>
          </div>
        </div>
      </div>

      {/* Rank Evolution — a compact, tappable preview that opens the full journey screen */}
      <SectionTitle th={th}>{t('evolution')}</SectionTitle>
      {(() => {
        // first ··· (prev · current · next) ··· last — current centred, jumps shown as "···"
        const preview = Array.from(new Set([0, levelIdx - 1, levelIdx, levelIdx + 1, LEVELS.length - 1])).filter((i) => i >= 0 && i < LEVELS.length).sort((a, b) => a - b);
        return (
          <button type="button" onClick={onOpenJourney} data-haptic="medium" className="goal-pill" style={{
            width: '100%', textAlign: 'start', fontFamily: 'inherit', cursor: 'pointer', position: 'relative', overflow: 'hidden',
            padding: '16px 18px 18px', borderRadius: 20, color: th.text,
            background: `linear-gradient(150deg, ${accent[0]}16, ${th.surface} 70%)`, border: '1px solid ' + accent[0] + '33',
            backdropFilter: 'blur(14px) saturate(140%)', WebkitBackdropFilter: 'blur(14px) saturate(140%)',
            transition: 'border-color .2s ease, background .2s ease',
          }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
              <span style={{ fontSize: 13.5, fontWeight: 400, color: th.dim }}>{LEVELS[0].name} → {LEVELS[LEVELS.length - 1].name}</span>
              <span style={{ display: 'flex', alignItems: 'center', gap: 5, color: accent[0], fontSize: 12 }}>
                {t('view_all')}<span style={{ display: 'flex' }}><IconChevron size={16} /></span>
              </span>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 5, marginTop: 16 }}>
              {preview.map((idx, k) => (
                <React.Fragment key={idx}>
                  {k > 0 && (preview[k] - preview[k - 1] > 1
                    ? <span style={{ color: th.ghost, fontSize: 15, letterSpacing: 1.5, padding: '0 7px', flexShrink: 0 }}>···</span>
                    : <span aria-hidden="true" style={{ width: 3, flexShrink: 0 }} />)}
                  <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, minWidth: 0 }}>
                    <RankBadge levelIdx={idx} size={idx === levelIdx ? 33 : 27} reached={idx <= levelIdx} glow={idx === levelIdx} />
                    <span style={{ fontSize: 9.5, color: idx === levelIdx ? accent[0] : th.faint, fontWeight: idx === levelIdx ? 500 : 400, whiteSpace: 'nowrap' }}>{LEVELS[idx].name}</span>
                  </div>
                </React.Fragment>
              ))}
            </div>
          </button>
        );
      })()}

      {/* statistics — advanced stats are Premium */}
      <SectionTitle th={th}>{t('statistics')}</SectionTitle>

      {/* Weekly / Monthly focus, streaks and goals achieved */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
        <LockStat th={th} accent={accent} premium icon={<IconCalendar size={17} />}
          name={t('weekly_focus')} value={`${Math.floor(stats.weekMinutes / 60)}h ${stats.weekMinutes % 60}m`} />
        <LockStat th={th} accent={accent} premium icon={<IconCalendar size={17} />}
          name={t('monthly_focus')} value={`${Math.floor(stats.monthMinutes / 60)}h ${stats.monthMinutes % 60}m`} />
        <LockStat th={th} accent={accent} premium icon={<IconStreak size={17} />}
          name={t('focus_streaks')} value={stats.currentStreak} unit={t('days_unit')} sub={'· ' + stats.longestStreak + ' best'} />
        <LockStat th={th} accent={accent} premium icon={<IconTrophy size={17} />}
          name={t('goals_achieved')} value={stats.goalsAchieved} />
      </div>

      {/* Weekly / Monthly activity with average per day — Premium */}
      {isPremium ? (
        <ActivityCard stats={stats} accent={accent} th={th} t={t} lang={lang} />
      ) : (
        <button type="button" data-haptic="warning" onClick={() => onLocked('stats')} style={{
          width: '100%', marginTop: 12, padding: '16px 16px 14px', borderRadius: 18, cursor: 'pointer', fontFamily: 'inherit',
          textAlign: 'start', background: th.surface, border: '1px solid ' + th.border, position: 'relative', overflow: 'hidden',
        }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
            <span style={{ display: 'flex', alignItems: 'center', gap: 9, color: th.dim, minWidth: 0 }}>
              <IconChart size={17} />
              <span style={{ fontSize: 13, color: th.text, fontWeight: 300, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{t('activity_wk_mo')}</span>
            </span>
            <span style={{ display: 'flex', alignItems: 'center', gap: 9, flexShrink: 0 }}>
              <PremiumPill />
              <span style={{ color: th.ghost, display: 'flex' }}><IconLock size={15} sw={1.7} /></span>
            </span>
          </div>
          <div style={{ marginTop: 14, display: 'flex', alignItems: 'flex-end', gap: 6, height: 46, opacity: .3 }}>
            {[0.5, 0.8, 0.45, 1, 0.7, 0.92, 0.62].map((h, i) => (
              <div key={i} style={{ flex: 1, height: Math.max(8, h * 46), borderRadius: 4, background: `linear-gradient(180deg, ${accent[0]}, ${accent[1]})` }} />
            ))}
          </div>
        </button>
      )}

      {/* ── Focus Heatmap (real session history) ── */}
      <SectionTitle th={th}>{t('focus_heatmap')}</SectionTitle>
      <HeatmapCard stats={stats} isPremium={isPremium} onLocked={onLocked} accent={accent} th={th} t={t} />

      {/* ── By Project (focus minutes per tag) ── */}
      <SectionTitle th={th}>{t('by_project')}</SectionTitle>
      <ProjectBreakdown stats={stats} accent={accent} th={th} t={t} />

      {/* ── Streak Protection ── */}
      <SectionTitle th={th}>{t('streak_protection')}</SectionTitle>
      <StreakProtect stats={stats} isPremium={isPremium} onLocked={onLocked} onAutoProtect={onAutoProtect} accent={accent} th={th} t={t} />

      {/* custom goals — 1 free (daily) · additional require Premium (weekly) */}
      <SectionTitle th={th}>{t('custom_goals')}</SectionTitle>
      {(() => { const GOLD = typeof PREMIUM_GOLD === 'string' ? PREMIUM_GOLD : '#D4A23C'; return (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {/* Daily goal — free: every user gets one custom goal */}
          <GoalBar th={th} accent={accent} icon={<IconTarget size={17} />} label={t('daily_goal')} done={stats.todayMinutes} goal={stats.goals.daily} t={t} onEdit={() => onEditGoals('daily')} />
          {/* Weekly goal — Premium: additional goals */}
          {isPremium ? (
            <GoalBar th={th} accent={accent} icon={<IconCalendar size={17} />} label={t('weekly_goal')} done={stats.weekMinutes} goal={stats.goals.weekly} t={t} onEdit={() => onEditGoals('weekly')} />
          ) : (
            <div onClick={() => onLocked('goals')} style={{
              padding: '16px 18px', borderRadius: 20, cursor: 'pointer',
              background: th.surface, border: '1px solid ' + th.border, display: 'flex', alignItems: 'center', gap: 14,
            }}>
              <span style={{ width: 36, height: 36, borderRadius: 12, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: GOLD + '1c', color: GOLD }}><IconCalendar size={18} /></span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 15, fontWeight: 300, color: th.text }}>{t('weekly_goal')}</div>
                <div style={{ fontSize: 12.5, color: th.faint, marginTop: 2 }}>{t('more_goals_premium')}</div>
              </div>
              <PremiumPill />
            </div>
          )}
        </div>
      ); })()}

      {/* achievements — compact pill; tap to open the full list overlay (like Rank Evolution) */}
      <SectionTitle th={th}>{t('achievements')}</SectionTitle>
      {(() => {
        const unlockedMap = stats.achUnlocked || {};
        const total = ACHIEVEMENTS.length;
        const gotCount = ACHIEVEMENTS.filter((a) => unlockedMap[a.key]).length;
        // preview: most-recently unlocked first, then locked — a glanceable strip of icons
        const unlockedList = ACHIEVEMENTS.filter((a) => unlockedMap[a.key]).sort((a, b) => String(unlockedMap[b.key]).localeCompare(String(unlockedMap[a.key])));
        const lockedList = ACHIEVEMENTS.filter((a) => !unlockedMap[a.key]);
        const preview = unlockedList.concat(lockedList).slice(0, 6);
        return (
          <button type="button" onClick={onOpenAchievements} data-haptic="medium" className="goal-pill" style={{
            width: '100%', textAlign: 'start', fontFamily: 'inherit', cursor: 'pointer', position: 'relative', overflow: 'hidden',
            padding: '16px 18px 18px', borderRadius: 20, color: th.text,
            background: `linear-gradient(150deg, ${accent[0]}16, ${th.surface} 70%)`, border: '1px solid ' + accent[0] + '33',
            backdropFilter: 'blur(14px) saturate(140%)', WebkitBackdropFilter: 'blur(14px) saturate(140%)',
            transition: 'border-color .2s ease, background .2s ease',
          }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
              <span style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13.5, fontWeight: 400, color: th.dim }}><span style={{ color: accent[0], display: 'flex' }}><IconAward size={17} /></span>{t('ach_progress', { n: gotCount, total })}</span>
              <span style={{ display: 'flex', alignItems: 'center', gap: 5, color: accent[0], fontSize: 12 }}>
                {t('view_all')}<span style={{ display: 'flex' }}><IconChevron size={16} /></span>
              </span>
            </div>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 9, marginTop: 16 }}>
              {preview.map((a) => {
                const got = !!unlockedMap[a.key];
                const rar = RARITY[a.rarity] || RARITY.common;
                const Ico = window['Icon' + a.icon] || IconTrophy;
                return (
                  <span key={a.key} style={{ width: 38, height: 38, borderRadius: 11, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
                    background: got ? `linear-gradient(140deg, ${rar.color}33, ${rar.color}12)` : th.surface2,
                    border: '1px solid ' + (got ? rar.color + '55' : th.border), color: got ? rar.color : th.faint,
                    filter: got ? `drop-shadow(0 0 6px ${rar.color}55)` : 'none' }}>
                    {got ? <Ico size={19} /> : <IconLock size={16} />}
                  </span>
                );
              })}
            </div>
          </button>
        );
      })()}
    </div>
  );
}

// Weekly Activity — 7 bars (Mon–Sun) of daily focus minutes. Tap a bar for a
// premium tooltip showing the day and total; smooth fade in / out.
// Generic activity bar chart with a tap-to-reveal tooltip. Used for both the weekly
// (7 day) and monthly (4 week) views inside the ActivityCard.
function ActivityBars({ values, labelFor, tipFor, highlightIdx, accent, th }) {
  const [sel, setSel] = React.useState(null);
  const lastRef = React.useRef(highlightIdx != null ? highlightIdx : 0);
  const n = values.length;
  const max = Math.max(60, ...values);
  const fmt = (m) => { const h = Math.floor(m / 60), mm = m % 60; return h > 0 ? `${h}h ${String(mm).padStart(2, '0')}m` : `${mm}m`; };
  if (sel != null) lastRef.current = sel;
  const tipIdx = sel != null ? sel : lastRef.current;
  return (
    <div style={{ position: 'relative' }}>
      <div style={{
        position: 'absolute', left: `${((tipIdx + 0.5) / n) * 100}%`, top: -8, transform: 'translate(-50%,-100%)',
        opacity: sel != null ? 1 : 0, transition: 'opacity .25s ease', pointerEvents: 'none', zIndex: 5,
        background: th.modal, border: '1px solid ' + th.border2, borderRadius: 12, padding: '8px 13px', whiteSpace: 'nowrap',
        boxShadow: `0 10px 26px rgba(0,0,0,.45), 0 0 0 1px ${accent[0]}22`, textAlign: 'center',
      }}>
        <div style={{ fontSize: 11, letterSpacing: .3, color: th.faint, textTransform: 'capitalize' }}>{tipFor(tipIdx)}</div>
        <div style={{ fontSize: 15, fontWeight: 400, color: th.text, marginTop: 2 }}>{fmt(values[tipIdx])}</div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: `repeat(${n},1fr)`, gap: 8, alignItems: 'end', height: 100 }}>
        {values.map((m, i) => {
          const bh = Math.max(6, Math.round((m / max) * 88));
          const on = sel === i, hi = i === highlightIdx;
          return (
            <button key={i} data-haptic="light" onClick={() => setSel(on ? null : i)} style={{
              display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-end', gap: 8,
              background: 'none', border: 'none', padding: 0, cursor: 'pointer', height: '100%',
              WebkitTapHighlightColor: 'transparent',
            }}>
              <div style={{
                width: '100%', maxWidth: 26, height: bh, borderRadius: 6,
                background: `linear-gradient(180deg, ${accent[0]}, ${accent[1]})`,
                opacity: on ? 1 : (0.4 + (m / max) * 0.45),
                boxShadow: on ? `0 0 14px ${accent[0]}aa` : (hi ? `0 0 8px ${accent[0]}55` : 'none'),
                transition: 'opacity .2s, box-shadow .2s, height .5s cubic-bezier(.2,.8,.2,1)',
              }} />
              <span style={{ fontSize: 11, color: hi ? accent[0] : th.faint, fontWeight: hi ? 600 : 400, textTransform: 'uppercase' }}>{labelFor(i)}</span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

// Free activity card — segmented Weekly / Monthly switch, total + average-per-day,
// and an animated bar chart that swaps smoothly between the two views.
function ActivityCard({ stats, accent, th, t, lang }) {
  const [view, setView] = React.useState('weekly');
  const weekly = view === 'weekly';
  const fmt = (m) => { const h = Math.floor(m / 60), mm = m % 60; return h > 0 ? `${h}h ${String(mm).padStart(2, '0')}m` : `${mm}m`; };
  const week = (stats.weekDaily && stats.weekDaily.length === 7) ? stats.weekDaily : [0, 0, 0, 0, 0, 0, 0];
  const month = (stats.monthWeekly && stats.monthWeekly.length === 4) ? stats.monthWeekly : [0, 0, 0, 0];
  const total = weekly ? stats.weekMinutes : stats.monthMinutes;
  const avg = weekly ? Math.round(stats.weekMinutes / 7) : Math.round(stats.monthMinutes / 30);
  const todayIdx = (new Date().getDay() + 6) % 7;
  const narrow = new Intl.DateTimeFormat(lang || 'en', { weekday: 'narrow' });
  const long = new Intl.DateTimeFormat(lang || 'en', { weekday: 'long' });
  const dayDate = (i) => new Date(2024, 0, 1 + i); // 2024-01-01 was a Monday
  return (
    <div style={{ marginTop: 12, padding: '16px 16px 14px', borderRadius: 18, position: 'relative',
      background: `linear-gradient(150deg, ${accent[0]}14, ${th.surface} 70%)`, border: '1px solid ' + accent[0] + '3a' }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
        <span style={{ display: 'flex', alignItems: 'center', gap: 8, color: accent[0], minWidth: 0 }}>
          <IconChart size={17} />
        </span>
        <div style={{ display: 'inline-flex', padding: 3, borderRadius: 12, background: th.surface2, border: '1px solid ' + th.border, gap: 2 }}>
          {[['weekly', t('weekly_activity')], ['monthly', t('monthly_activity')]].map(([v, label]) => {
            const on = view === v;
            return (
              <button key={v} data-haptic="medium" onClick={() => setView(v)} style={{
                fontFamily: 'inherit', fontSize: 11.5, fontWeight: on ? 500 : 400, cursor: 'pointer', border: 'none', borderRadius: 9, padding: '6px 11px',
                color: on ? '#fff' : th.dim, background: on ? `linear-gradient(135deg,${accent[0]},${accent[1]})` : 'transparent',
                boxShadow: on ? `0 4px 12px ${accent[0]}44` : 'none', transition: 'background .25s ease, color .25s ease',
              }}>{label}</button>
            );
          })}
        </div>
      </div>
      <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 16, marginTop: 14 }}>
        <div>
          <div style={{ fontSize: 10.5, letterSpacing: 1, color: th.faint, textTransform: 'uppercase' }}>{t('act_total')}</div>
          <div style={{ fontSize: 24, fontWeight: 300, color: th.text, marginTop: 3 }}>{fmt(total)}</div>
        </div>
        <div style={{ textAlign: 'end' }}>
          <div style={{ fontSize: 10.5, letterSpacing: 1, color: th.faint, textTransform: 'uppercase' }}>{t('avg_per_day')}</div>
          <div style={{ fontSize: 15, fontWeight: 400, color: accent[0], marginTop: 4 }}>{fmt(avg)}</div>
        </div>
      </div>
      <div key={view} className="act-swap" style={{ marginTop: 18 }}>
        {weekly ? (
          <ActivityBars values={week} accent={accent} th={th} highlightIdx={todayIdx}
            labelFor={(i) => narrow.format(dayDate(i))} tipFor={(i) => long.format(dayDate(i))} />
        ) : (
          <ActivityBars values={month} accent={accent} th={th} highlightIdx={3}
            labelFor={(i) => t('week_abbr') + (i + 1)} tipFor={(i) => t('act_week_n', { n: i + 1 })} />
        )}
      </div>
    </div>
  );
}

function SectionTitle({ children, th }) {
  return <div style={{ fontSize: 12, letterSpacing: 2.4, color: (th ? th.faint : 'rgba(255,255,255,.45)'), fontWeight: 500, margin: '28px 2px 14px', textTransform: 'uppercase' }}>{children}</div>;
}

// Goal progress bar — the entire pill is tappable and opens the goal editor.
function GoalBar({ th, accent, icon, label, done, goal, t, onEdit }) {
  const pct = Math.min(100, Math.round((done / Math.max(1, goal)) * 100));
  const reached = done >= goal;
  return (
    <button type="button" onClick={onEdit} data-haptic="medium" className="goal-pill" style={{
      width: '100%', textAlign: 'start', fontFamily: 'inherit', cursor: 'pointer',
      padding: '15px 18px', borderRadius: 18, background: th.surface, border: '1px solid ' + th.border,
      transition: 'border-color .2s ease, background .2s ease',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <span style={{ color: accent[0], display: 'flex' }}>{icon}</span>
        <span style={{ fontSize: 14, fontWeight: 300, color: th.text, flex: 1 }}>{label}</span>
        <span style={{ fontSize: 13, color: reached ? accent[0] : th.dim, display: 'inline-flex', alignItems: 'center', gap: 6, flexShrink: 0 }}>
          {Math.floor(done / 60)}h {done % 60}m / {Math.floor(goal / 60)}h {goal % 60 ? (goal % 60) + 'm' : ''}
          <span style={{ color: th.ghost, display: 'flex' }}><IconChevron size={14} /></span>
        </span>
      </div>
      <div style={{ marginTop: 12, height: 8, borderRadius: 99, background: th.track, overflow: 'hidden' }}>
        <div style={{ width: pct + '%', height: '100%', borderRadius: 99, background: `linear-gradient(90deg, ${accent[0]}, ${accent[1]})`, boxShadow: `0 0 10px ${accent[0]}aa`, transition: 'width .6s cubic-bezier(.2,.8,.2,1)' }} />
      </div>
    </button>
  );
}

// ── RankJourney — the full 20-level progression as a vertical milestone timeline ──
// Completed ranks are checked, the current rank highlighted with its live progress,
// the next rank flagged as the target, locked ranks dimmed, and Crown made special.
function RankJourney({ levelIdx, totalHours, th, accent, onClose }) {
  const { t, dir } = useI18n();
  return (
    <div className="prem-overlay" dir={dir}>
      <div className="prem-backdrop" onClick={onClose} />
      <div className="prem-sheet" style={{ background: th.sheet, borderColor: th.border2 }}>
        <div style={{ position: 'relative', flexShrink: 0, padding: '14px 18px 8px', display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12 }}>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: 19, fontWeight: 400, color: th.text }}>{t('evolution')}</div>
            <div style={{ fontSize: 12.5, color: th.faint, marginTop: 2 }}>{t('journey_sub')}</div>
          </div>
          <button onClick={onClose} aria-label={t('np_close')} style={{ width: 34, height: 34, borderRadius: '50%', background: th.surface2, border: '1px solid ' + th.border, color: th.text, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}><IconClose size={16} /></button>
        </div>
        <div className="scroll-area" style={{ flex: 1, padding: '8px 18px 28px' }}>
          {LEVELS.map((lv, i) => {
            const reached = i <= levelIdx, current = i === levelIdx, next = i === levelIdx + 1, isCrown = i === LEVELS.length - 1;
            const nextLv = LEVELS[i + 1];
            const curPct = nextLv ? Math.max(3, Math.min(100, Math.round((totalHours / nextLv.hours) * 100))) : 100;
            return (
              <div key={lv.key} style={{ display: 'flex', alignItems: 'stretch', gap: 14, marginBottom: 10 }}>
                {/* rail — vivid badges only, vertically centred against their card */}
                <div style={{ width: 46, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                  <RankBadge levelIdx={i} size={current ? 44 : 36} reached={reached} glow={current} vivid={reached} />
                </div>
                {/* content card */}
                <div style={{ flex: 1, padding: '13px 15px', borderRadius: 16,
                  background: current ? `linear-gradient(120deg, ${lv.glow}26, ${th.surface})` : (isCrown && reached ? `linear-gradient(120deg, ${lv.glow}1f, ${th.surface})` : th.surface),
                  border: '1px solid ' + (current ? lv.glow + '88' : isCrown ? lv.glow + '55' : th.border),
                  opacity: (reached || next) ? 1 : 0.5,
                  boxShadow: current ? `0 8px 26px ${lv.glow}26` : 'none' }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <span style={{ fontSize: 11, color: th.faint, fontWeight: 500 }}>{i + 1}</span>
                    <span style={{ fontSize: 15.5, fontWeight: 400, color: th.text }}>{lv.name}</span>
                    {isCrown && <span style={{ color: lv.glow, display: 'flex', filter: `drop-shadow(0 0 6px ${lv.glow}aa)` }}><IconCrown size={15} /></span>}
                    <span style={{ marginInlineStart: 'auto', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
                      {current && <span style={{ fontSize: 9.5, letterSpacing: 1.2, color: lv.glow, fontWeight: 600, padding: '3px 9px', borderRadius: 99, border: '1px solid ' + lv.glow + '88', textTransform: 'uppercase' }}>{t('current_badge')}</span>}
                      {next && <span style={{ fontSize: 9.5, letterSpacing: 1.2, color: accent[0], fontWeight: 600, padding: '3px 9px', borderRadius: 99, border: '1px solid ' + accent[0] + '66', textTransform: 'uppercase' }}>{t('next_target')}</span>}
                      {reached && !current && <span style={{ color: lv.glow, display: 'flex' }}><IconCheck size={18} /></span>}
                      {!reached && !next && <span style={{ color: th.ghost, display: 'flex' }}><IconLock size={14} sw={1.8} /></span>}
                    </span>
                  </div>
                  <div style={{ fontSize: 12, color: th.faint, marginTop: 4 }}>{lv.hours === 0 ? t('starting_tier') : t('hours_count', { n: lv.hours.toLocaleString() })}</div>
                  {current && (
                    <div style={{ marginTop: 11 }}>
                      <div style={{ height: 4, borderRadius: 4, background: th.track, overflow: 'hidden' }}>
                        <div style={{ width: curPct + '%', height: '100%', borderRadius: 4, background: `linear-gradient(90deg, ${lv.left}, ${lv.right})`, boxShadow: `0 0 4px ${lv.glow}33` }} />
                      </div>
                      <div style={{ fontSize: 11, color: th.dim, marginTop: 6 }}>
                        {nextLv ? `${Math.floor(totalHours)} / ${nextLv.hours} h · ${nextLv.name}` : t('max_rank')}
                      </div>
                    </div>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

// ── AchievementsJourney — the full achievement list as a grouped overlay (mirrors the
// Rank Evolution journey): opened from the compact Achievements pill on the Progress tab.
function AchievementsJourney({ stats, th, accent, onClose }) {
  const { t, dir } = useI18n();
  const groupLabels = { mastery: t('ach_g_mastery'), consistency: t('ach_g_consistency'), sessions: t('ach_g_sessions'), challenge: t('ach_g_challenge'), premium: t('ach_g_premium'), sharing: t('ach_g_sharing') };
  const unlockedMap = stats.achUnlocked || {};
  const total = ACHIEVEMENTS.length;
  const gotCount = ACHIEVEMENTS.filter((a) => unlockedMap[a.key]).length;
  const fmtDate = (d) => { try { return new Date(d + 'T00:00:00').toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); } catch (e) { return d; } };
  return (
    <div className="prem-overlay" dir={dir}>
      <div className="prem-backdrop" onClick={onClose} />
      <div className="prem-sheet" style={{ background: th.sheet, borderColor: th.border2 }}>
        <div style={{ position: 'relative', flexShrink: 0, padding: '14px 18px 8px', display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12 }}>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: 19, fontWeight: 400, color: th.text }}>{t('achievements')}</div>
            <div style={{ fontSize: 12.5, color: th.faint, marginTop: 2 }}>{t('ach_progress', { n: gotCount, total })}</div>
          </div>
          <button onClick={onClose} aria-label={t('np_close')} style={{ width: 34, height: 34, borderRadius: '50%', background: th.surface2, border: '1px solid ' + th.border, color: th.text, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}><IconClose size={16} /></button>
        </div>
        <div className="scroll-area" style={{ flex: 1, padding: '8px 18px 28px' }}>
          {ACH_GROUPS.map((g) => {
            const items = ACHIEVEMENTS.filter((a) => a.group === g);
            if (!items.length) return null;
            return (
              <div key={g} style={{ marginBottom: 18 }}>
                <div style={{ fontSize: 11, letterSpacing: 1.6, textTransform: 'uppercase', color: th.faint, fontWeight: 600, margin: '0 2px 10px' }}>{groupLabels[g]}</div>
                <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2,1fr)', gap: 10 }}>
                  {items.map((a) => {
                    const date = unlockedMap[a.key];
                    const got = !!date;
                    const rar = RARITY[a.rarity] || RARITY.common;
                    const Ico = window['Icon' + a.icon] || IconTrophy;
                    return (
                      <div key={a.key} style={{
                        position: 'relative', padding: '13px 13px 12px', borderRadius: 16, overflow: 'hidden',
                        background: got ? `linear-gradient(155deg, ${rar.color}1f, ${th.surface})` : th.surface,
                        border: '1px solid ' + (got ? rar.color + '55' : th.border),
                        opacity: got ? 1 : 0.6,
                      }}>
                        <span style={{ position: 'absolute', top: 11, right: 11, fontSize: 8, letterSpacing: .6, fontWeight: 700, textTransform: 'uppercase',
                          color: got ? rar.color : th.faint, border: '1px solid ' + (got ? rar.color + '66' : th.border), borderRadius: 99, padding: '2px 6px' }}>{t('rar_' + a.rarity)}</span>
                        <span style={{ width: 38, height: 38, borderRadius: 11, display: 'flex', alignItems: 'center', justifyContent: 'center',
                          background: got ? `linear-gradient(140deg, ${rar.color}33, ${rar.color}12)` : th.surface2,
                          border: '1px solid ' + (got ? rar.color + '55' : th.border), color: got ? rar.color : th.faint,
                          filter: got ? `drop-shadow(0 0 7px ${rar.color}55)` : 'none' }}>
                          {got ? <Ico size={20} /> : <IconLock size={17} />}
                        </span>
                        <div style={{ fontSize: 13, fontWeight: 500, color: got ? th.text : th.dim, marginTop: 10, lineHeight: 1.2 }}>{a.title}</div>
                        <div style={{ fontSize: 10.5, color: th.faint, marginTop: 4, lineHeight: 1.35, minHeight: 28 }}>{a.desc}</div>
                        <div style={{ fontSize: 9.5, letterSpacing: .3, marginTop: 9, color: got ? rar.color : th.ghost, fontWeight: 500, display: 'flex', alignItems: 'center', gap: 5 }}>
                          <span style={{ display: 'flex' }}>{got ? <IconCheck size={11} sw={2.4} /> : <IconLock size={10} sw={1.8} />}</span>
                          {got ? t('ach_unlocked_on', { date: fmtDate(date) }) : t('ach_locked')}
                        </div>
                      </div>
                    );
                  })}
                </div>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

// ── V3: Focus Heatmap — GitHub-style grid built from real session history ──
function histByDay(history) {
  const m = {};
  (history || []).forEach((h) => { if (h && h.date) m[h.date] = (m[h.date] || 0) + (h.dur || 0); });
  return m;
}
function dayKeyOffset(daysAgo) {
  const d = new Date(); d.setHours(0, 0, 0, 0); d.setDate(d.getDate() - daysAgo);
  const p = (n) => String(n).padStart(2, '0');
  return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate());
}
function HeatmapCard({ stats, isPremium, onLocked, accent, th, t }) {
  const byDay = histByDay(stats.history);
  const days = isPremium ? 84 : 7; // Premium: 12 weeks · Free: last 7 days
  const cells = [];
  for (let i = days - 1; i >= 0; i--) { const k = dayKeyOffset(i); cells.push({ k, min: byDay[k] || 0 }); }
  const peak = Math.max(60, ...cells.map((c) => c.min));
  const cellColor = (min) => {
    if (!min) return th.surface2 || 'rgba(255,255,255,.05)';
    const a = 0.2 + Math.min(1, min / peak) * 0.8;
    return `rgba(184,123,255,${a.toFixed(2)})`;
  };
  return (
    <div style={{ padding: '16px', borderRadius: 20, background: th.surface, border: '1px solid ' + th.border }}>
      <div style={{ display: 'grid', gridTemplateColumns: `repeat(${isPremium ? 12 : 7}, 1fr)`, gap: 5 }}>
        {cells.map((c) => (
          <div key={c.k} title={`${c.k} · ${c.min}m`} style={{ aspectRatio: '1', borderRadius: 5, background: cellColor(c.min) }} />
        ))}
      </div>
      <div style={{ marginTop: 12, fontSize: 11.5, color: th.faint }}>
        {isPremium ? t('focus_heatmap') : t('last_7_days')}
      </div>
      {!isPremium && (
        <button type="button" onClick={() => onLocked('heatmap')} data-haptic="warning" style={{
          marginTop: 10, width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8,
          padding: '11px 13px', borderRadius: 12, cursor: 'pointer', fontFamily: 'inherit', textAlign: 'start',
          background: 'transparent', border: '1px solid ' + th.border,
        }}>
          <span style={{ display: 'flex', alignItems: 'center', gap: 8, color: th.dim }}>
            <IconLock size={13} sw={1.7} /><span style={{ fontSize: 12.5, color: th.text }}>{t('heatmap_full_hint')}</span>
          </span>
          <PremiumPill />
        </button>
      )}
    </div>
  );
}

// ── V3: By Project — focus minutes per tag (real history) ──
function ProjectBreakdown({ stats, accent, th, t }) {
  const tags = stats.tags || [];
  const byTag = {};
  (stats.history || []).forEach((h) => { if (h && h.tag) byTag[h.tag] = (byTag[h.tag] || 0) + (h.dur || 0); });
  const rows = tags.map((g) => ({ g, min: byTag[g.id] || 0 })).filter((r) => r.min > 0).sort((a, b) => b.min - a.min);
  const max = rows.length ? rows[0].min : 1;
  return (
    <div style={{ padding: '16px 18px', borderRadius: 20, background: th.surface, border: '1px solid ' + th.border }}>
      {rows.length === 0 ? (
        <div style={{ fontSize: 13, color: th.faint, lineHeight: 1.5 }}>{t('no_project_data')}</div>
      ) : rows.map(({ g, min }) => (
        <div key={g.id} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '7px 0' }}>
          <span style={{ width: 64, fontSize: 13, color: th.text, fontWeight: 300, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{g.label}</span>
          <div style={{ flex: 1, height: 8, borderRadius: 99, background: th.surface2 || 'rgba(255,255,255,.06)', overflow: 'hidden' }}>
            <div style={{ width: Math.round((min / max) * 100) + '%', height: '100%', borderRadius: 99, background: g.color || accent[0] }} />
          </div>
          <span style={{ width: 56, textAlign: 'end', fontSize: 12.5, color: th.dim }}>{Math.floor(min / 60)}h {min % 60}m</span>
        </div>
      ))}
    </div>
  );
}

// ── V3: Streak Protection — freeze tokens + auto-protect (Premium) ──
function StreakProtect({ stats, isPremium, onLocked, onAutoProtect, accent, th, t }) {
  const f = stats.streakFreeze || { tokens: 0, autoProtect: false };
  return (
    <div style={{ padding: '16px', borderRadius: 20, background: th.surface, border: '1px solid ' + th.border, display: 'flex', flexDirection: 'column', gap: 12 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
        <span style={{ width: 38, height: 38, borderRadius: 12, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', background: `linear-gradient(140deg, ${accent[0]}33, ${accent[1]}22)`, color: accent[0] }}><IconFlame size={20} /></span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 15, fontWeight: 300, color: th.text }}>{t('freeze_available', { n: f.tokens || 0 })}</div>
          <div style={{ fontSize: 12, color: th.faint, marginTop: 2 }}>{f.autoProtect && isPremium ? t('auto_protect') : t('streak_protection')}</div>
        </div>
      </div>
      <div onClick={() => { if (!isPremium) { onLocked('auto_protect'); return; } onAutoProtect(!f.autoProtect); }}
        style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px', borderRadius: 14, cursor: 'pointer', border: '1px solid ' + th.border, background: th.surface2 || 'transparent' }}>
        <span style={{ flex: 1, fontSize: 13.5, color: th.text }}>{t('auto_protect')}</span>
        {!isPremium ? (
          <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <PremiumPill />
            <IconLock size={14} sw={1.7} />
          </span>
        ) : (
          <span style={{ width: 46, height: 27, borderRadius: 99, background: f.autoProtect ? `linear-gradient(135deg,${accent[0]},${accent[1]})` : (th.surface2 || 'rgba(140,140,160,.3)'), position: 'relative', flexShrink: 0 }}>
            <span style={{ position: 'absolute', top: 3, left: f.autoProtect ? 22 : 3, width: 21, height: 21, borderRadius: '50%', background: '#fff', transition: 'left .2s' }} />
          </span>
        )}
      </div>
    </div>
  );
}

Object.assign(window, { ProgressScreen, SectionTitle, StatTile, GoalBar, LockStat, RankJourney, AchievementsJourney, HeatmapCard, ProjectBreakdown, StreakProtect });

  