// Tempo — Bilan hebdomadaire (design system Tempo)
// Une carte par semaine ISO (lundi → dimanche), de la plus récente à la plus ancienne.
// Pour chaque semaine : P&L net, nombre de trades, gagnants, perdants, taux de
// réussite, profit factor, moyenne par jour et 7 mini-barres jour (vert = gain,
// rouge = perte, gris = pas de trade).
// Réutilise les globaux : computeStats, localDayKey (trades-api.jsx), fmtMoney
// (app.jsx) et les formateurs de format-prefs.jsx. Tous les jours sont calculés
// dans le fuseau du profil, sans jamais passer par l'heure du navigateur.

// Titre de la page : table NAV_LABELS (core.jsx), seule source des noms d'écrans.
// Lu au rendu (et non au chargement du fichier) ; repli sur le libellé de la charte.
function weeklyTitle() {
  const n = (typeof NAV_LABELS !== 'undefined' && NAV_LABELS && NAV_LABELS.weekly) || null;
  return (n && n.titre) || 'Bilan hebdomadaire';
}

// ── Jours civils 'YYYY-MM-DD' (aucune projection dans le fuseau du navigateur) ──

// Jour civil d'un trade dans le fuseau du profil — même regroupement que le
// calendrier (groupTradesByDay) et le Journal quotidien.
function weeklyDayOf(t, tz) {
  if (!t || !t.executed_at) return null;
  const day = (typeof localDayKey === 'function')
    ? localDayKey(t.executed_at, tz)
    : localDayInTz(t.executed_at, tz);
  return /^\d{4}-\d{2}-\d{2}$/.test(day || '') ? day : null;
}

// Rang du jour dans la semaine : 0 = lundi … 6 = dimanche.
function weeklyDowOf(day) {
  return (new Date(day + 'T12:00:00Z').getUTCDay() + 6) % 7;
}

// Jour civil décalé de n jours (calcul à midi UTC, sans effet d'heure d'été).
function weeklyAddDays(day, n) {
  const dt = new Date(day + 'T12:00:00Z');
  dt.setUTCDate(dt.getUTCDate() + n);
  return dt.toISOString().slice(0, 10);
}

// « Mer. 16 sept. » : même format que l'en-tête du journal (« Lun. 21 sept. »).
function weeklyDayLabel(day) {
  const wd = fmtWeekdayShort(day);
  return wd.charAt(0).toUpperCase() + wd.slice(1) + ' ' + fmtDayShort(day);
}

// Regroupe les trades par semaine ISO. Retourne un tableau trié du plus récent au plus ancien.
// Chaque élément : { key (lundi 'YYYY-MM-DD'), weekLabel, rangeLabel, trades, stats,
// days: [7 × { iso, pnl, count }], tradedDays, lastTradedISO, avgPerDay }
function weeklyBuildWeeks(trades, tz) {
  const buckets = {};
  for (const t of (trades || [])) {
    const day = weeklyDayOf(t, tz);
    if (!day) continue;
    const key = weeklyAddDays(day, -weeklyDowOf(day));
    if (!buckets[key]) buckets[key] = { key, items: [] };
    buckets[key].items.push({ t, day });
  }

  const weeks = Object.values(buckets).map(b => {
    // 7 cases jour (lundi → dimanche), chacune avec son jour civil.
    const days = [];
    for (let i = 0; i < 7; i++) days.push({ iso: weeklyAddDays(b.key, i), pnl: 0, count: 0 });
    for (const item of b.items) {
      const slot = days[weeklyDowOf(item.day)];
      if (slot) { slot.pnl += Number(item.t.pnl) || 0; slot.count += 1; }
    }
    const rawTrades = b.items.map(x => x.t);
    const traded = days.filter(d => d.count > 0);
    const stats = computeStats(rawTrades);
    return {
      key: b.key,
      weekLabel: fmtWeekLabel(b.key),
      rangeLabel: fmtWeekRange(b.key),
      trades: rawTrades,
      stats,
      days,
      tradedDays: traded.length,
      // Une semaine n'existe que si elle a au moins un trade.
      lastTradedISO: (traded[traded.length - 1] || days[0]).iso,
      avgPerDay: traded.length > 0 ? stats.netPnl / traded.length : 0,
    };
  });

  weeks.sort((a, b) => (a.key < b.key ? 1 : a.key > b.key ? -1 : 0));
  return weeks;
}

// Signe d'un montant TEL QU'IL S'AFFICHE : −1, 0 ou 1. Un montant qui s'écrit
// « 0,00 € » une fois arrondi aux décimales de la devise vaut 0, même si la
// somme flottante laisse un résidu (10,10 + 20,20 − 30,30 ≠ 0 en machine).
// Même règle que les pages Stratégies et Statistiques (charte §2).
function weeklySign(v) {
  return moneySign(v);
}

// Couleur d'un montant signé : vert pour un gain, rouge pour une perte, neutre pour zéro.
function weeklyTone(v) {
  const sign = weeklySign(v);
  return sign > 0 ? 'var(--green)' : sign < 0 ? 'var(--red)' : 'var(--fg)';
}

// « 1 200 trades » : nombre au format français et accord du pluriel.
function weeklyTradesLabel(count) {
  return fmtCount(count, 'trade');
}

// ── Mini-barres jour (7) ──────────────────────────────────────────────
// onDayClick(iso) : un jour avec trades devient un bouton qui ouvre son journal.
function WeeklyDayBars({ days, height = 34, compact = false, onDayClick }) {
  // Échelle relative à la plus grosse amplitude de la semaine, plancher pour rester lisible.
  const maxAbs = Math.max(1, ...days.map(d => Math.abs(d.pnl)));
  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', gap: compact ? 4 : 6 }}>
      {days.map((d, i) => {
        const has = d.count > 0;
        // Jour tradé à « 0,00 € » : barre grise, ni verte ni rouge.
        const sign = weeklySign(d.pnl);
        const tone = !has ? 'var(--bg-elev)' : (sign > 0 ? 'var(--green)' : sign < 0 ? 'var(--red)' : 'var(--fg-4)');
        const ratio = has ? Math.max(0.18, Math.abs(d.pnl) / maxAbs) : 0.14;
        const dayLabel = weeklyDayLabel(d.iso);
        const tip = has
          ? `${dayLabel} · ${fmtMoney(d.pnl, true)} · ${weeklyTradesLabel(d.count)}`
          : `${dayLabel} · aucun trade`;
        const column = { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 5, flex: 1, minWidth: 0 };
        const content = (
          <React.Fragment>
            <div style={{ height, width: '100%', display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }}>
              <div style={{
                width: '100%', maxWidth: compact ? 14 : 18,
                height: `${Math.round(ratio * height)}px`,
                minHeight: 4, borderRadius: 4,
                background: tone, opacity: has ? 1 : 0.7,
              }} />
            </div>
            <span aria-hidden="true" style={{ fontSize: compact ? 9.5 : 10, color: 'var(--fg-3)', fontWeight: 500 }}>{FR_WEEKDAYS_2[i]}</span>
          </React.Fragment>
        );
        if (has && onDayClick) {
          return (
            <button
              key={d.iso}
              type="button"
              className="tap"
              onClick={() => onDayClick(d.iso)}
              title={tip}
              aria-label={`Ouvrir le journal du ${fmtWeekdayShort(d.iso)} ${fmtDayShort(d.iso)} (${fmtMoney(d.pnl, true)}, ${weeklyTradesLabel(d.count)})`}
              style={{ ...column, background: 'transparent', border: 'none', padding: 0, margin: 0, cursor: 'pointer', color: 'var(--fg)', font: 'inherit' }}>
              {content}
            </button>
          );
        }
        return (
          <div key={d.iso} role="img" title={tip} aria-label={tip} style={column}>
            {content}
          </div>
        );
      })}
    </div>
  );
}

// ── Une stat de la grille ─────────────────────────────────────────────
// Ne crée pas de boîte (display: contents) : son libellé, sa valeur et son
// sous-texte se placent chacun dans une rangée de la grille du groupe
// (WeeklyStatsGrid). Les valeurs d'un même groupe restent ainsi sur une seule
// ligne, même quand un libellé voisin passe sur deux lignes (320 px).
// L'ordre libellé → valeur → sous-texte est aussi l'ordre de lecture.
// wide : colonne du montant ; le libellé ne doit pas fixer la largeur minimale
// de la colonne (c'est le montant qui la fixe), il se replie donc dedans. Le
// montant réserve la largeur du plus long montant de la liste
// (--weekly-avg-w, voir useWeeklyAvgWidth) : mêmes colonnes dans toutes les cartes.
function WeeklyStat({ label, value, tone, sub, wide = false }) {
  return (
    <div style={{ display: 'contents' }}>
      <div style={{
        alignSelf: 'end', marginBottom: 4, fontSize: 11, color: 'var(--fg-3)', fontWeight: 500,
        ...(wide ? { width: 0, minWidth: '100%' } : { minWidth: 0 }),
      }}>{label}</div>
      <div className="num" style={{ minWidth: wide ? 'var(--weekly-avg-w, 0px)' : 0, fontSize: 15, fontWeight: 600, color: tone || 'var(--fg)', letterSpacing: '-.01em', whiteSpace: 'nowrap' }}>
        {wide ? <span data-weekly-avg="" style={{ display: 'inline-block' }}>{value}</span> : value}
      </div>
      {sub
        ? <div style={{ minWidth: 0, fontSize: 11, color: 'var(--fg-3)', marginTop: 2, whiteSpace: 'nowrap' }}>{sub}</div>
        : <div aria-hidden="true" />}
    </div>
  );
}

// Largeur commune de la colonne « Moyenne par jour ». Chaque carte a sa propre
// grille : sans cela, la colonne suivrait le montant de SA semaine et les
// colonnes se décaleraient de quelques pixels d'une carte à l'autre dès qu'un
// montant dépasse un tiers de la largeur. On mesure donc le plus long montant
// de la liste et on le publie dans --weekly-avg-w sur le conteneur. Mesuré
// après chaque rendu (les montants changent avec les trades et la devise) et
// quand les polices ont fini de charger. Sans la variable, chaque colonne
// garde au moins la largeur de son propre montant : rien n'est jamais coupé.
function useWeeklyAvgWidth() {
  const ref = React.useRef(null);
  React.useLayoutEffect(() => {
    const root = ref.current;
    if (!root) return undefined;
    let alive = true;
    const measure = () => {
      if (!alive) return;
      let max = 0;
      root.querySelectorAll('[data-weekly-avg]').forEach(el => { if (el.offsetWidth > max) max = el.offsetWidth; });
      // offsetWidth est arrondi à l'entier : + 1 px pour ne jamais être en dessous.
      const next = max > 0 ? (max + 1) + 'px' : '';
      if (root.style.getPropertyValue('--weekly-avg-w') !== next) {
        if (next) root.style.setProperty('--weekly-avg-w', next); else root.style.removeProperty('--weekly-avg-w');
      }
    };
    measure();
    if (typeof document !== 'undefined' && document.fonts && document.fonts.ready && typeof document.fonts.ready.then === 'function') {
      document.fonts.ready.then(measure, () => {});
    }
    return () => { alive = false; };
  });
  return ref;
}

// ── Grille de stats d'une semaine (mêmes libellés et formats partout) ──
// Deux groupes de trois : côte à côte quand la place le permet (6 colonnes),
// sinon l'un sous l'autre (3 × 2). Chaque groupe est une grille de 3 colonnes
// et 3 rangées (libellés, valeurs, sous-textes), remplie colonne par colonne.
// Aucun montant n'est jamais coupé : la colonne « Moyenne par jour » prend au
// moins la largeur du plus long montant de la liste (les trois colonnes restent
// égales tant qu'il tient dans un tiers).
// compact (mobile) : la moyenne par jour passe au format compact au-delà de 10 000.
function WeeklyStatsGrid({ week, compact = false }) {
  const s = week.stats;
  const nuls = s.count - s.wins - s.losses;
  const group = {
    flex: compact ? '1 1 100%' : '1 1 330px', minWidth: 0,
    display: 'grid', gridTemplateRows: 'auto auto auto', gridAutoFlow: 'column',
    gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', columnGap: compact ? 10 : 18, rowGap: 0,
  };
  const moneyGroup = { ...group, gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1fr) minmax(max-content, 1fr)' };
  const avg = compact ? fmtMoneyCompact(week.avgPerDay, true) : fmtMoney(week.avgPerDay, true);
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', rowGap: compact ? 10 : 18, columnGap: 18 }}>
      <div style={group}>
        <WeeklyStat label="Trades" value={fmtNum(s.count, 0)} sub={nuls > 0 ? `${fmtNum(nuls, 0)} nul${nuls > 1 ? 's' : ''}` : null} />
        <WeeklyStat label="Gagnants" value={fmtNum(s.wins, 0)} tone={s.wins > 0 ? 'var(--green)' : 'var(--fg)'} />
        <WeeklyStat label="Perdants" value={fmtNum(s.losses, 0)} tone={s.losses > 0 ? 'var(--red)' : 'var(--fg)'} />
      </div>
      <div style={moneyGroup}>
        <WeeklyStat label="Taux de réussite" value={fmtPct(s.winRate, 1)} />
        <WeeklyStat label="Profit factor" value={fmtPF(s.profitFactor, s.wins + s.losses)} />
        <WeeklyStat label="Moyenne par jour" value={avg} tone={weeklyTone(week.avgPerDay)} wide />
      </div>
    </div>
  );
}

// ── Bouton « Journal du 25 sept. » : ouvre le dernier jour tradé de la semaine ──
function WeeklyJournalButton({ week, openJournal, style }) {
  if (!openJournal) return null;
  const day = fmtDayShort(week.lastTradedISO);
  return (
    <button
      type="button"
      onClick={() => openJournal(week.lastTradedISO)}
      className="btn btn-outline-blue tap"
      aria-label={`Ouvrir le journal du ${day}`}
      style={{ whiteSpace: 'nowrap', ...style }}>
      {Ico.book} Journal du {day}
    </button>
  );
}

// ── Carte semaine (desktop) ───────────────────────────────────────────
function WeeklyCard({ week, openJournal }) {
  const s = week.stats;
  return (
    <div className="card lift" style={{ padding: '20px 22px' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 20, flexWrap: 'wrap' }}>
        {/* Gauche : semaine + plage + mini-barres */}
        <div style={{ minWidth: 230, flex: '1 1 280px' }}>
          <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' }}>
            <CardTitle style={{ fontSize: 16, fontWeight: 700, letterSpacing: '-.015em', color: 'var(--fg)' }}>{week.weekLabel}</CardTitle>
            <span style={{ fontSize: 12.5, color: 'var(--fg-3)' }}>{week.rangeLabel}</span>
          </div>
          <div style={{ marginTop: 16, maxWidth: 320 }}>
            <WeeklyDayBars days={week.days} onDayClick={openJournal ? (iso => openJournal(iso)) : undefined} />
          </div>
        </div>

        {/* Droite : P&L + bouton Journal */}
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 10 }}>
          <WeeklyJournalButton week={week} openJournal={openJournal} />
          <div className="num" style={{ fontSize: 30, fontWeight: 700, letterSpacing: '-.022em', color: weeklyTone(s.netPnl), lineHeight: 1, whiteSpace: 'nowrap' }}>
            {fmtMoney(s.netPnl, true)}
          </div>
          <div style={{ fontSize: 11.5, color: 'var(--fg-3)' }}>P&amp;L de la semaine</div>
        </div>
      </div>

      {/* Grille de stats */}
      <div style={{ marginTop: 20, paddingTop: 18, borderTop: '1px solid var(--line)' }}>
        <WeeklyStatsGrid week={week} />
      </div>
    </div>
  );
}

// ── État vide : une phrase et une action ──────────────────────────────
function WeeklyEmpty({ compact = false, openImport }) {
  return (
    <div className="card">
      <EmptyTrades openImport={openImport} compact={compact}
        text="Ajoute ou importe des trades : ton bilan apparaîtra ici, semaine par semaine, du lundi au dimanche."/>
    </div>
  );
}

// ── Chargement : squelette, uniquement tant qu'aucune donnée n'est chargée ──
function WeeklySkeleton({ compact = false }) {
  const bar = (w, h) => <div style={{ width: w, height: h, borderRadius: 6, background: 'var(--bg-elev)' }} />;
  return (
    <div role="status" aria-busy="true" aria-label="Chargement de ton bilan hebdomadaire" style={{ display: 'flex', flexDirection: 'column', gap: compact ? 12 : 14 }}>
      <div className="card" style={{ padding: '14px 18px', display: 'flex', gap: 28 }}>
        {[0, 1, 2].map(i => <div key={i} style={{ display: 'flex', flexDirection: 'column', gap: 8, width: 90 }}>{bar('70%', 9)}{bar('100%', 18)}</div>)}
      </div>
      {[0, 1].map(i => (
        <div key={i} className="card" style={{ padding: compact ? '14px 16px' : '20px 22px', display: 'flex', flexDirection: 'column', gap: 14 }}>
          {bar('35%', 14)}{bar('100%', compact ? 28 : 34)}{bar('100%', 30)}
        </div>
      ))}
    </div>
  );
}

// ── Erreur de chargement (≠ compte vide) : les semaines déjà chargées restent ──
function WeeklyLoadError({ state }) {
  const err = state && state.tradesError;
  if (!err) return null;
  const retry = typeof state.refreshTrades === 'function' ? () => state.refreshTrades() : null;
  return (
    <div role="alert" className="card" style={{
      display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 12,
      padding: '12px 16px', marginBottom: 16, background: 'var(--red-soft)', borderColor: 'var(--red)',
    }}>
      <span style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--red-text)' }}>Impossible de charger tes trades. Vérifie ta connexion.</span>
      {retry && <button type="button" onClick={retry} className="btn tap" style={{ color: 'var(--fg)' }}>Réessayer</button>}
    </div>
  );
}

// ── En-tête de stats globales (résumé toutes semaines) ───────────────
// compact (mobile) : grille 2 × 2 alignée ; la colonne du P&L net prend au moins
// la largeur du montant (deux colonnes égales tant qu'il tient dans une moitié).
// Une semaine affichée « 0,00 € » n'est pas une semaine gagnante.
function WeeklySummary({ weeks, compact = false }) {
  const totalPnl = weeks.reduce((a, w) => a + w.stats.netPnl, 0);
  const totalTrades = weeks.reduce((a, w) => a + w.stats.count, 0);
  const greenWeeks = weeks.filter(w => weeklySign(w.stats.netPnl) > 0).length;
  const label = { fontSize: 11, color: 'var(--fg-3)', fontWeight: 500 };
  const value = { fontSize: 18, fontWeight: 700, letterSpacing: '-.015em', color: 'var(--fg)', whiteSpace: 'nowrap' };
  return (
    <div className="card" style={compact
      ? { padding: '14px 16px', display: 'grid', gridTemplateColumns: 'minmax(max-content, 1fr) minmax(0, 1fr)', columnGap: 16, rowGap: 12 }
      : { padding: '14px 18px', display: 'flex', columnGap: 28, rowGap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
      <div>
        <div style={label}>P&amp;L net</div>
        <div className="num" style={{ ...value, color: weeklyTone(totalPnl) }}>{fmtMoney(totalPnl, true)}</div>
      </div>
      <div>
        <div style={label}>Semaines</div>
        <div className="num" style={value}>{fmtNum(weeks.length, 0)}</div>
      </div>
      <div>
        <div style={label}>Semaines gagnantes</div>
        <div className="num" style={value}>{fmtNum(greenWeeks, 0)}<span style={{ fontSize: 12, color: 'var(--fg-3)', fontWeight: 500 }}> / {fmtNum(weeks.length, 0)}</span></div>
      </div>
      <div>
        <div style={label}>Trades</div>
        <div className="num" style={value}>{fmtNum(totalTrades, 0)}</div>
      </div>
    </div>
  );
}

// Contenu commun (desktop et mobile) : erreur, chargement, vide ou semaines.
function useWeeklyWeeks(state) {
  const trades = (state && state.trades) || [];
  const tz = (state && state.timezone) || (typeof window !== 'undefined' && window.__appTz) || 'Europe/Paris';
  const weeks = React.useMemo(() => weeklyBuildWeeks(trades, tz), [trades, tz]);
  const loading = !!(state && state.tradesLoading) && weeks.length === 0;
  const failed = !!(state && state.tradesError) && weeks.length === 0;
  return { weeks, loading, failed };
}

// ── Page Bilan hebdomadaire (desktop) ─────────────────────────────────
function WeeklyPage({ state, openJournal, openImport }) {
  const { weeks, loading, failed } = useWeeklyWeeks(state);
  const listRef = useWeeklyAvgWidth();

  return (
    <div style={{ width: '100%', height: '100%', overflow: 'auto', display: 'flex', flexDirection: 'column' }} className="scroll">
      <PageHeader title={weeklyTitle()} />
      <div style={{ ...PAGE_BODY, padding: '20px 24px 32px', flex: 1 }}>
        <WeeklyLoadError state={state} />
        {loading ? (
          <WeeklySkeleton />
        ) : failed ? null : weeks.length === 0 ? (
          <WeeklyEmpty openImport={openImport} />
        ) : (
          <React.Fragment>
            <div style={{ marginBottom: 16 }}>
              <WeeklySummary weeks={weeks} />
            </div>
            <div ref={listRef} className="stagger" style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
              {weeks.map(w => (
                <WeeklyCard key={w.key} week={w} openJournal={openJournal} />
              ))}
            </div>
          </React.Fragment>
        )}
      </div>
    </div>
  );
}

// ── Carte semaine (mobile, compacte) ──────────────────────────────────
function WeeklyMobileCard({ week, openJournal }) {
  const s = week.stats;
  return (
    <div className="card" style={{ padding: '14px 16px' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }}>
        <div style={{ minWidth: 0 }}>
          <CardTitle style={{ fontSize: 14, fontWeight: 700, color: 'var(--fg)' }}>{week.weekLabel}</CardTitle>
          <div style={{ fontSize: 11.5, color: 'var(--fg-3)', marginTop: 1 }}>{week.rangeLabel}</div>
        </div>
        <div className="num" style={{ fontSize: 20, fontWeight: 700, color: weeklyTone(s.netPnl), letterSpacing: '-.018em', whiteSpace: 'nowrap' }}>
          {fmtMoney(s.netPnl, true)}
        </div>
      </div>

      <div style={{ marginTop: 12 }}>
        <WeeklyDayBars days={week.days} height={28} compact onDayClick={openJournal ? (iso => openJournal(iso)) : undefined} />
      </div>

      <div style={{ marginTop: 12, paddingTop: 11, borderTop: '1px solid var(--line)' }}>
        <WeeklyStatsGrid week={week} compact />
      </div>

      <WeeklyJournalButton week={week} openJournal={openJournal}
        style={{ marginTop: 12, width: '100%', justifyContent: 'center' }} />
    </div>
  );
}

// ── Page Bilan hebdomadaire (mobile) ──────────────────────────────────
function WeeklyMobile({ state, openJournal, openImport }) {
  const { weeks, loading, failed } = useWeeklyWeeks(state);
  const listRef = useWeeklyAvgWidth();

  return (
    <div style={{ width: '100%', height: '100%', overflow: 'auto' }} className="scroll">
      <div style={{ padding: '16px var(--m-gutter, 16px) calc(28px + env(safe-area-inset-bottom))' }}>
        <h1 style={{ fontSize: 24, fontWeight: 700, letterSpacing: '-.025em', margin: '0 0 14px', color: 'var(--fg)' }}>{weeklyTitle()}</h1>

        <WeeklyLoadError state={state} />
        {loading ? (
          <WeeklySkeleton compact />
        ) : failed ? null : weeks.length === 0 ? (
          <WeeklyEmpty compact openImport={openImport} />
        ) : (
          <React.Fragment>
            {/* Résumé global — parité avec la version desktop */}
            <div style={{ marginBottom: 12 }}>
              <WeeklySummary weeks={weeks} compact />
            </div>
            <div ref={listRef} className="stagger" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
              {weeks.map(w => (
                <WeeklyMobileCard key={w.key} week={w} openJournal={openJournal} />
              ))}
            </div>
          </React.Fragment>
        )}
      </div>
    </div>
  );
}

Object.assign(window, { WeeklyPage, WeeklyMobile });
