// pantry-pages.jsx — Inventory, Recipes, Grocery, Profile pages + Onboarding
// All pages take ({ palette, lang, iconStyle, web, inventory, setInventory, openRecipe, openAdd, grocery, setGrocery, onToggleLang, ...etc })

const { useState: useStateP, useMemo: useMemoP, useRef: useRefP, useEffect: useEffectP } = React;

// ─────────────────────────────────────────────────────────────
// Page header — used on all secondary pages
// ─────────────────────────────────────────────────────────────
function PageHeader({ palette, title, subtitle, action, lang, onToggleLang, onOpenProfile, onBack, authUser }) {
  return (
    <div style={{ padding: '20px 20px 12px', display: 'flex', alignItems: 'flex-start', gap: 10 }}>
      {onBack && (
        <div onClick={onBack} style={{
          width: 38, height: 38, borderRadius: 12,
          background: palette.cardSub, color: palette.ink,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', border: `1px solid ${palette.line}`, flexShrink: 0,
        }} aria-label="back">
          <Icon name="chevL" size={17} color={palette.ink} />
        </div>
      )}
      <div style={{ flex: 1 }}>
        <div style={{ fontSize: 11.5, color: palette.ink3, fontFamily: 'Geist Mono, monospace', letterSpacing: 0.04, textTransform: 'uppercase', marginBottom: 4 }}>
          {subtitle}
        </div>
        <div style={{ fontSize: 26, fontWeight: 600, letterSpacing: -0.6, color: palette.ink, lineHeight: 1.1 }}>
          {title}
        </div>
      </div>
      {onToggleLang && (
        <div onClick={onToggleLang} style={{
          height: 30, padding: '0 10px', borderRadius: 10,
          background: palette.cardSub, color: palette.ink2,
          display: 'flex', alignItems: 'center', cursor: 'pointer',
          fontSize: 11, fontFamily: 'Geist Mono, monospace', fontWeight: 500, letterSpacing: 0.06,
          border: `1px solid ${palette.line}`,
        }}>{lang.toUpperCase()}</div>
      )}
      {action}
      {onOpenProfile && (
        <UserAvatar palette={palette} size={38} gradient onClick={onOpenProfile}
          name={authUser?.name} userId={authUser?.id} />
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// Filter chip (used by inventory + recipes)
// ─────────────────────────────────────────────────────────────
function Chip({ active, label, palette, onClick, count }) {
  return (
    <div onClick={onClick} style={{
      padding: '7px 12px', borderRadius: 999,
      background: active ? palette.ink : palette.card,
      color: active ? palette.bg : palette.ink2,
      border: `1px solid ${active ? palette.ink : palette.line}`,
      fontSize: 12.5, fontWeight: 500, cursor: 'pointer', whiteSpace: 'nowrap',
      display: 'inline-flex', alignItems: 'center', gap: 6,
    }}>
      {label}
      {count != null && (
        <span style={{
          fontSize: 10.5, fontFamily: 'Geist Mono, monospace',
          opacity: 0.75, fontWeight: 500,
        }}>{count}</span>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// INVENTORY PAGE
// ─────────────────────────────────────────────────────────────
function InventoryPage({ palette, lang, iconStyle, inventory, setInventory, openAdd, onToggleLang, onOpenProfile, onOpenItemMenu, onBack, web, authUser }) {
  const [filter, setFilter] = useStateP('all');
  const [sort, setSort] = useStateP('urgency');
  const [q, setQ] = useStateP('');
  const [selected, setSelected] = useStateP(new Set());
  const selectMode = selected.size > 0;

  const counts = useMemoP(() => ({
    all: inventory.length,
    fridge: inventory.filter(i => i.location === 'fridge').length,
    freezer: inventory.filter(i => i.location === 'freezer').length,
    pantry: inventory.filter(i => i.location === 'pantry').length,
  }), [inventory]);

  const filtered = useMemoP(() => {
    let list = inventory.filter(i => filter === 'all' || i.location === filter);
    if (q.trim()) list = list.filter(i => localized(lang, i.name).toLowerCase().includes(q.toLowerCase()));
    if (sort === 'urgency') list.sort((a, b) => a.daysLeft - b.daysLeft);
    else if (sort === 'alpha') list.sort((a, b) => localized(lang, a.name).localeCompare(localized(lang, b.name)));
    else if (sort === 'added') list.sort((a, b) => (b.addedAt || 0) - (a.addedAt || 0));
    return list;
  }, [inventory, filter, q, sort, lang]);

  const atRiskCount = inventory.filter(i => i.daysLeft <= 3).length;
  const toggleSelect = (id) => {
    setSelected(s => {
      const next = new Set(s);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  };
  const deleteSelected = () => {
    const n = selected.size;
    const msg = lang === 'fr'
      ? `Supprimer ${n} aliment${n > 1 ? 's' : ''} de l'inventaire ?`
      : `Delete ${n} item${n > 1 ? 's' : ''} from inventory?`;
    if (typeof window !== 'undefined' && !window.confirm(msg)) return;
    setInventory(prev => prev.filter(i => !selected.has(i.id)));
    setSelected(new Set());
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', position: 'relative' }}>
      <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>
        <PageHeader
          palette={palette}
          title={t(lang, 'invTitle')}
          subtitle={`${inventory.length} ${t(lang, 'invSub')} · ${atRiskCount} ${t(lang, 'expSoon')}`}
          lang={lang}
          onBack={onBack}
          onToggleLang={onToggleLang}
          onOpenProfile={onOpenProfile}
          authUser={authUser}
        />

        {/* Search */}
        <div style={{ padding: '0 20px 12px' }}>
          <div style={{
            background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
            padding: '10px 14px', display: 'flex', alignItems: 'center', gap: 10,
          }}>
            <Icon name="search" size={17} color={palette.ink3} />
            <input
              value={q}
              onChange={e => setQ(e.target.value)}
              placeholder={t(lang, 'searchPh')}
              style={{
                flex: 1, border: 'none', outline: 'none', background: 'transparent',
                fontSize: 14.5, color: palette.ink, fontFamily: 'inherit',
              }}
            />
            {q && (
              <div onClick={() => setQ('')} style={{ cursor: 'pointer', color: palette.ink3 }}>
                <Icon name="close" size={14} color={palette.ink3} />
              </div>
            )}
          </div>
        </div>

        {/* Filter chips */}
        <div style={{ display: 'flex', gap: 6, padding: '4px 20px 8px', overflowX: 'auto', scrollbarWidth: 'none' }}>
          <Chip active={filter === 'all'} label={t(lang, 'filterAll')} count={counts.all} palette={palette} onClick={() => setFilter('all')} />
          <Chip active={filter === 'fridge'} label={t(lang, 'filterFridge')} count={counts.fridge} palette={palette} onClick={() => setFilter('fridge')} />
          <Chip active={filter === 'freezer'} label={t(lang, 'filterFreezer')} count={counts.freezer} palette={palette} onClick={() => setFilter('freezer')} />
          <Chip active={filter === 'pantry'} label={t(lang, 'filterPantry')} count={counts.pantry} palette={palette} onClick={() => setFilter('pantry')} />
        </div>

        {/* Sort row */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 20px 12px' }}>
          <span style={{ fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase' }}>
            {t(lang, 'sortBy')}
          </span>
          {[{ k: 'urgency', l: t(lang, 'sortUrgency') }, { k: 'alpha', l: t(lang, 'sortAlpha') }, { k: 'added', l: t(lang, 'sortAdded') }].map(o => (
            <span key={o.k} onClick={() => setSort(o.k)} style={{
              fontSize: 12, color: sort === o.k ? palette.primary : palette.ink3,
              fontWeight: sort === o.k ? 500 : 400, cursor: 'pointer',
              borderBottom: sort === o.k ? `1px solid ${palette.primary}` : '1px solid transparent',
              paddingBottom: 1,
            }}>{o.l}</span>
          ))}
        </div>

        {/* List */}
        {filtered.length === 0 ? (
          <div style={{ padding: '60px 32px', textAlign: 'center' }}>
            <div style={{ fontSize: 48, marginBottom: 12, opacity: 0.6 }}>📦</div>
            <div style={{ fontSize: 15, color: palette.ink2, marginBottom: 16 }}>{t(lang, 'empty')}</div>
            <div onClick={openAdd} style={{
              display: 'inline-flex', alignItems: 'center', gap: 8,
              padding: '10px 16px', borderRadius: 12, background: palette.primary, color: palette.primaryInk,
              fontSize: 14, fontWeight: 500, cursor: 'pointer',
            }}>
              <Icon name="plus" size={16} color={palette.primaryInk} />
              {t(lang, 'emptyCTA')}
            </div>
          </div>
        ) : (
          <div style={{ padding: '0 20px 100px' }}>
            <div style={{ background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 16, overflow: 'hidden' }}>
              {filtered.map((item, i) => {
                const st = statusOf(item);
                const stColor = st === 'urgent' || st === 'expired' ? palette.red : st === 'soon' ? palette.amber : palette.primary;
                const isSel = selected.has(item.id);
                return (
                  <div
                    key={item.id}
                    onClick={() => selectMode ? toggleSelect(item.id) : null}
                    style={{
                      display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px',
                      borderBottom: i < filtered.length - 1 ? `1px solid ${palette.line2}` : 'none',
                      cursor: 'pointer', minWidth: 0,
                      background: isSel ? palette.primarySoft : 'transparent',
                      transition: 'background 140ms',
                    }}
                  >
                    {selectMode && (
                      <div style={{
                        width: 20, height: 20, borderRadius: 10,
                        background: isSel ? palette.primary : 'transparent',
                        border: `1.5px solid ${isSel ? palette.primary : palette.line}`,
                        display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
                      }}>
                        {isSel && <Icon name="check" size={12} color={palette.primaryInk} />}
                      </div>
                    )}
                    <FoodChip item={item} size={36} style={iconStyle} palette={palette} />
                    <div style={{ flex: 1, minWidth: 0, overflow: 'hidden' }}>
                      <div style={{ fontSize: 14.5, fontWeight: 500, color: palette.ink, letterSpacing: -0.1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                        {localized(lang, item.name)}
                      </div>
                      <div style={{ fontSize: 11.5, color: palette.ink3, marginTop: 2, fontFamily: 'Geist Mono, monospace', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                        {item.qty} {localized(lang, item.unit)} · {t(lang, item.location)}
                      </div>
                    </div>
                    {!selectMode && (
                      <>
                        <div style={{ display: 'flex', alignItems: 'center', gap: 6, color: stColor, fontSize: 11.5, fontWeight: 500, fontFamily: 'Geist Mono, monospace', flexShrink: 0 }}>
                          <span style={{ width: 5, height: 5, borderRadius: 3, background: stColor }} />
                          {item.daysLeft < 0 ? `−${-item.daysLeft}${t(lang, 'days')}` : item.daysLeft === 0 ? t(lang, 'today') : `${item.daysLeft}${t(lang, 'days')}`}
                        </div>
                        <div
                          onClick={(e) => { e.stopPropagation(); if (onOpenItemMenu) onOpenItemMenu(item); else toggleSelect(item.id); }}
                          style={{ padding: 6, marginRight: -6, color: palette.ink3, cursor: 'pointer' }}
                        >
                          <Icon name="dots" size={16} color={palette.ink3} />
                        </div>
                      </>
                    )}
                  </div>
                );
              })}
            </div>
            <div style={{ fontSize: 11, color: palette.ink3, marginTop: 12, textAlign: 'center', fontFamily: 'Geist Mono, monospace' }}>
              {lang === 'fr' ? 'Maintiens un élément pour sélectionner' : 'Tap dots to select'}
            </div>
          </div>
        )}
      </div>

      {/* Bulk action bar */}
      {selectMode && (
        <div style={{
          position: 'absolute', left: 16, right: 16, bottom: web ? 16 : 84, zIndex: 60,
          background: palette.ink, color: palette.bg,
          borderRadius: 14, padding: '10px 14px',
          display: 'flex', alignItems: 'center', gap: 10,
          boxShadow: '0 12px 32px rgba(0,0,0,0.25)',
        }}>
          <div onClick={() => setSelected(new Set())} style={{ cursor: 'pointer', padding: 4 }}>
            <Icon name="close" size={16} color={palette.bg} />
          </div>
          <div style={{ flex: 1, fontSize: 13.5, fontWeight: 500 }}>
            {selected.size} {t(lang, 'selected')}
          </div>
          <div onClick={deleteSelected} style={{
            display: 'flex', alignItems: 'center', gap: 6,
            padding: '6px 12px', borderRadius: 10, background: palette.red, color: '#fff',
            fontSize: 13, fontWeight: 500, cursor: 'pointer',
          }}>
            <Icon name="trash" size={14} color="#fff" />
            {t(lang, 'deleteN')}
          </div>
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// RECIPES PAGE — feed sorted by match %
// ─────────────────────────────────────────────────────────────
function RecipesPage({ palette, lang, inventory, favorites, setFavorites, customRecipes, openRecipe, onAddRecipe, onToggleLang, onOpenProfile, onBack, web, authUser }) {
  const [timeFilter, setTimeFilter] = useStateP('all'); // all|15|30
  const [mealFilter, setMealFilter] = useStateP('all'); // all|breakfast|lunch|dinner
  const [view, setView] = useStateP('all'); // all|fav|mine
  const [q, setQ] = useStateP('');
  const [source, setSource] = useStateP('local'); // local | online
  const [dietFilter, setDietFilter] = useStateP('all');
  const [allergyFilters, setAllergyFilters] = useStateP(() => new Set());
  const [online, setOnline] = useStateP([]);
  const [onlineState, setOnlineState] = useStateP('idle'); // idle|loading|ok|offline

  const FAV = favorites || new Set();
  const toggleFav = (id) => {
    if (!setFavorites) return;
    const next = new Set(FAV);
    if (next.has(id)) next.delete(id); else next.add(id);
    setFavorites(next);
  };

  const EXTRA = (typeof window !== 'undefined' && window.EXTRA_RECIPES) || [];
  const allRecipes = useMemoP(() => [...(customRecipes || []), ...RECIPES, ...EXTRA], [customRecipes, EXTRA]);

  const DIET_KEYS = (typeof window !== 'undefined' && window.RECIPE_DIETS) || [];
  const ALLERGEN_KEYS = (typeof window !== 'undefined' && window.RECIPE_ALLERGENS) || [];
  const toggleAllergy = (a) => {
    setAllergyFilters(prev => { const n = new Set(prev); if (n.has(a)) n.delete(a); else n.add(a); return n; });
  };

  // Online (TheMealDB) — fetch on demand when the Explore tab is opened or the
  // query changes. Debounced lightly via the effect dependency.
  useEffectP(() => {
    if (source !== 'online' || !window.MealDB) return;
    let alive = true;
    setOnlineState('loading');
    const run = window.MealDB.search(q.trim());
    run.then(list => {
      if (!alive) return;
      setOnline(Array.isArray(list) ? list : []);
      setOnlineState(list && list.length ? 'ok' : 'offline');
    }).catch(() => { if (alive) setOnlineState('offline'); });
    return () => { alive = false; };
  }, [source, q]);

  const matchOf = (typeof window !== 'undefined' && window.matchRecipe)
    ? window.matchRecipe
    : (r) => ({ matched: 0, total: Math.max(1, (r.uses || []).length), expiringUsed: 0, missing: [] });

  // Apply diet / allergy / meal / time / view filters to any recipe list.
  const passesFilters = (r, useView) => {
    if (useView && view === 'fav' && !FAV.has(r.id)) return false;
    if (useView && view === 'mine' && !r.custom) return false;
    if (timeFilter === '15' && r.minutes > 15) return false;
    if (timeFilter === '30' && r.minutes > 30) return false;
    if (mealFilter !== 'all' && r.mealType !== mealFilter) return false;
    if (dietFilter !== 'all' && !(r.diet || []).includes(dietFilter)) return false;
    if (allergyFilters.size > 0 && (r.allergens || []).some(a => allergyFilters.has(a))) return false;
    if (q.trim() && !localized(lang, r.name).toLowerCase().includes(q.toLowerCase())) return false;
    return true;
  };

  const scored = useMemoP(() => {
    return allRecipes
      .map(r => {
        const mm = matchOf(r, inventory);
        return { ...r, match: mm.matched / Math.max(1, mm.total), matchedIng: mm.matched, totalIng: mm.total, expiringUsed: mm.expiringUsed };
      })
      .filter(r => passesFilters(r, true))
      // Priority: recipes that use what's about to expire, then best inventory
      // match, then quicker recipes.
      .sort((a, b) => b.expiringUsed - a.expiringUsed || b.match - a.match || a.minutes - b.minutes);
  }, [allRecipes, timeFilter, mealFilter, view, q, lang, FAV, inventory, dietFilter, allergyFilters]);

  // Online results enriched with the same match + filtered the same way.
  const onlineScored = useMemoP(() => {
    return (online || [])
      .map(r => {
        const mm = matchOf(r, inventory);
        return { ...r, match: mm.matched / Math.max(1, mm.total), matchedIng: mm.matched, totalIng: mm.total, expiringUsed: mm.expiringUsed };
      })
      .filter(r => passesFilters(r, false))
      .sort((a, b) => b.expiringUsed - a.expiringUsed || b.match - a.match);
  }, [online, timeFilter, mealFilter, q, lang, inventory, dietFilter, allergyFilters]);

  // Build a simple category catalog by main ingredient/group
  const categories = useMemoP(() => {
    const cats = [
      { id: 'chicken', label: lang === 'fr' ? 'Poulet' : 'Chicken', emoji: '🍗', match: r => (r.uses || []).some(u => u.startsWith('i5') || u === 's13') },
      { id: 'pasta', label: lang === 'fr' ? 'Pâtes' : 'Pasta', emoji: '🍝', match: r => (r.uses || []).some(u => u === 'i6' || u === 's14') },
      { id: 'eggs', label: lang === 'fr' ? 'Œufs' : 'Eggs', emoji: '🥚', match: r => (r.uses || []).some(u => u === 'i8' || u === 's17') },
      { id: 'veg', label: lang === 'fr' ? 'Légumes' : 'Veggies', emoji: '🥬', match: r => (r.uses || []).some(u => ['i1', 'i7', 's25', 's29'].includes(u)) },
      { id: 'fish', label: lang === 'fr' ? 'Poisson' : 'Fish', emoji: '🐟', match: r => (r.uses || []).some(u => u === 's4') },
      { id: 'fast', label: lang === 'fr' ? 'Rapide' : 'Quick', emoji: '⚡', match: r => r.minutes <= 12 },
    ];
    return cats.map(c => ({ ...c, count: allRecipes.filter(c.match).length })).filter(c => c.count > 0);
  }, [allRecipes, lang]);
  const [activeCat, setActiveCat] = useStateP(null);
  const catFiltered = activeCat ? scored.filter(categories.find(c => c.id === activeCat).match) : scored;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>
        <PageHeader
          palette={palette}
          title={t(lang, 'recTitle')}
          subtitle={`${scored.length} · ${t(lang, 'recSub')}`}
          lang={lang}
          onBack={onBack}
          onToggleLang={onToggleLang}
          onOpenProfile={onOpenProfile}
          authUser={authUser}
          action={onAddRecipe ? (
            <div onClick={onAddRecipe} style={{
              padding: '0 12px', height: 38, borderRadius: 12,
              background: palette.primary, color: palette.primaryInk,
              display: 'inline-flex', alignItems: 'center', gap: 6,
              fontSize: 12.5, fontWeight: 500, cursor: 'pointer', flexShrink: 0,
            }}>
              <Icon name="plus" size={14} color={palette.primaryInk} />
              {lang === 'fr' ? 'Nouvelle' : 'New'}
            </div>
          ) : null}
        />

        {/* Search */}
        <div style={{ padding: '0 20px 12px' }}>
          <div style={{
            background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
            padding: '10px 14px', display: 'flex', alignItems: 'center', gap: 10,
          }}>
            <Icon name="search" size={17} color={palette.ink3} />
            <input value={q} onChange={e => setQ(e.target.value)}
              placeholder={lang === 'fr' ? 'Rechercher une recette…' : 'Search recipes…'}
              style={{
                flex: 1, border: 'none', outline: 'none', background: 'transparent',
                fontSize: 14, color: palette.ink, fontFamily: 'inherit',
              }} />
            {q && <div onClick={() => setQ('')} style={{ cursor: 'pointer' }}>
              <Icon name="close" size={14} color={palette.ink3} />
            </div>}
          </div>
        </div>

        {/* Source toggle: local catalog vs online (TheMealDB w/ photos) */}
        <div style={{ display: 'flex', gap: 6, padding: '0 20px 10px' }}>
          {[
            { k: 'local', l: t(lang, 'recLocal') },
            { k: 'online', l: t(lang, 'recOnline') },
          ].map(o => (
            <div key={o.k} onClick={() => setSource(o.k)} style={{
              flex: 1, padding: '8px 0', textAlign: 'center', borderRadius: 10,
              background: source === o.k ? palette.primary : palette.card,
              color: source === o.k ? palette.primaryInk : palette.ink2,
              border: `1px solid ${source === o.k ? palette.primary : palette.line}`,
              fontSize: 12.5, fontWeight: 500, cursor: 'pointer',
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 6,
            }}>
              {o.k === 'online' && <Icon name="sparkle" size={13} color={source === o.k ? palette.primaryInk : palette.ink2} />}
              {o.l}
            </div>
          ))}
        </div>

        {/* Diet + allergy filters — apply to BOTH local catalog and online */}
        {(
          <div style={{ padding: '0 20px 8px' }}>
            <div style={{ display: 'flex', gap: 6, overflowX: 'auto', scrollbarWidth: 'none', paddingBottom: 6 }}>
              <Chip active={dietFilter === 'all'} label={t(lang, 'recNoneFilter')} palette={palette} onClick={() => setDietFilter('all')} />
              {DIET_KEYS.map(d => (
                <Chip key={d} active={dietFilter === d}
                  label={t(lang, 'diet' + d.charAt(0).toUpperCase() + d.slice(1))}
                  palette={palette} onClick={() => setDietFilter(dietFilter === d ? 'all' : d)} />
              ))}
            </div>
            <div style={{ display: 'flex', gap: 6, overflowX: 'auto', scrollbarWidth: 'none', alignItems: 'center' }}>
              <span style={{ fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.ink3, flexShrink: 0 }}>{t(lang, 'recAllergy')}:</span>
              {ALLERGEN_KEYS.map(a => (
                <Chip key={a} active={allergyFilters.has(a)}
                  label={t(lang, 'alg' + a.charAt(0).toUpperCase() + a.slice(1))}
                  palette={palette} onClick={() => toggleAllergy(a)} />
              ))}
            </div>
          </div>
        )}

        {/* View tabs */}
        {source === 'local' && (
        <div style={{ display: 'flex', gap: 6, padding: '0 20px 10px' }}>
          {[
            { k: 'all', l: t(lang, 'filterAll') },
            { k: 'fav', l: lang === 'fr' ? '★ Favoris' : '★ Favorites' },
            { k: 'mine', l: lang === 'fr' ? 'Mes recettes' : 'My recipes' },
          ].map(o => (
            <div key={o.k} onClick={() => setView(o.k)} style={{
              flex: 1, padding: '8px 0', textAlign: 'center', borderRadius: 10,
              background: view === o.k ? palette.ink : palette.card,
              color: view === o.k ? palette.bg : palette.ink2,
              border: `1px solid ${view === o.k ? palette.ink : palette.line}`,
              fontSize: 12, fontWeight: 500, cursor: 'pointer',
            }}>{o.l}</div>
          ))}
        </div>
        )}

        {/* Time + meal filters — both sources */}
        {(
        <div style={{ display: 'flex', gap: 6, padding: '4px 20px 8px', overflowX: 'auto', scrollbarWidth: 'none' }}>
          <Chip active={timeFilter === 'all'} label={t(lang, 'filterAll')} palette={palette} onClick={() => setTimeFilter('all')} />
          <Chip active={timeFilter === '15'} label={t(lang, 'filterFast')} palette={palette} onClick={() => setTimeFilter('15')} />
          <Chip active={timeFilter === '30'} label={t(lang, 'filterMid')} palette={palette} onClick={() => setTimeFilter('30')} />
          <div style={{ width: 8 }} />
          <Chip active={mealFilter === 'breakfast'} label={t(lang, 'filterBreakfast')} palette={palette} onClick={() => setMealFilter(mealFilter === 'breakfast' ? 'all' : 'breakfast')} />
          <Chip active={mealFilter === 'lunch'} label={t(lang, 'filterLunch')} palette={palette} onClick={() => setMealFilter(mealFilter === 'lunch' ? 'all' : 'lunch')} />
          <Chip active={mealFilter === 'dinner'} label={t(lang, 'filterDinner')} palette={palette} onClick={() => setMealFilter(mealFilter === 'dinner' ? 'all' : 'dinner')} />
        </div>
        )}

        {/* Category catalog */}
        {source === 'local' && view === 'all' && !q && (
          <div style={{ padding: '4px 20px 10px' }}>
            <div style={{ fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase', marginBottom: 8 }}>
              {lang === 'fr' ? 'Catalogue' : 'Catalog'}
            </div>
            <div style={{ display: 'flex', gap: 8, overflowX: 'auto', scrollbarWidth: 'none', paddingBottom: 4 }}>
              {categories.map(c => (
                <div key={c.id} onClick={() => setActiveCat(activeCat === c.id ? null : c.id)} style={{
                  flexShrink: 0, padding: '8px 12px', borderRadius: 12,
                  background: activeCat === c.id ? palette.ink : palette.card,
                  color: activeCat === c.id ? palette.bg : palette.ink2,
                  border: `1px solid ${activeCat === c.id ? palette.ink : palette.line}`,
                  fontSize: 12, fontWeight: 500, cursor: 'pointer',
                  display: 'inline-flex', alignItems: 'center', gap: 6,
                }}>
                  <span style={{ fontSize: 16 }}>{c.emoji}</span>
                  {c.label}
                  <span style={{ fontSize: 10.5, opacity: 0.7, fontFamily: 'Geist Mono, monospace' }}>{c.count}</span>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* ONLINE source (TheMealDB, real photos) */}
        {source === 'online' && (
          onlineState === 'loading' ? (
            <div style={{ padding: '50px 32px', textAlign: 'center', color: palette.ink3, fontSize: 14 }}>
              {t(lang, 'recLoadingOnline')}
            </div>
          ) : onlineState === 'offline' ? (
            <div style={{ padding: '50px 32px', textAlign: 'center' }}>
              <div style={{ fontSize: 40, marginBottom: 10, opacity: 0.6 }}>📡</div>
              <div style={{ fontSize: 14, color: palette.ink2 }}>{t(lang, 'recOfflineMsg')}</div>
            </div>
          ) : onlineScored.length === 0 ? (
            <div style={{ padding: '50px 32px', textAlign: 'center' }}>
              <div style={{ fontSize: 40, marginBottom: 10, opacity: 0.6 }}>🔍</div>
              <div style={{ fontSize: 14, color: palette.ink2 }}>{lang === 'fr' ? 'Rien trouvé avec ces filtres' : 'Nothing found with these filters'}</div>
            </div>
          ) : (
            <div style={{ padding: '8px 20px 120px', display: 'grid', gridTemplateColumns: web ? 'repeat(2, 1fr)' : '1fr', gap: 12 }}>
              {onlineScored.map((r, idx) => (
                <RecipeFeedItem key={r.id} recipe={r} inventory={inventory} palette={palette} lang={lang}
                  onOpen={() => openRecipe(r)} featured={idx === 0 && !web}
                  isFav={FAV.has(r.id)} onToggleFav={() => toggleFav(r.id)} />
              ))}
            </div>
          )
        )}

        {/* LOCAL source (in-app catalog + custom) */}
        {source === 'local' && (catFiltered.length === 0 ? (
          <div style={{ padding: '60px 32px', textAlign: 'center' }}>
            <div style={{ fontSize: 48, marginBottom: 12, opacity: 0.6 }}>{view === 'fav' ? '★' : view === 'mine' ? '👨‍🍳' : '🍽'}</div>
            <div style={{ fontSize: 14, color: palette.ink2, marginBottom: 16 }}>
              {view === 'fav' ? (lang === 'fr' ? 'Aucun favori' : 'No favorites yet')
                : view === 'mine' ? (lang === 'fr' ? 'Aucune recette perso' : 'No personal recipes yet')
                : (lang === 'fr' ? 'Rien trouvé' : 'Nothing found')}
            </div>
            {view === 'mine' && onAddRecipe && (
              <div onClick={onAddRecipe} style={{
                display: 'inline-flex', alignItems: 'center', gap: 8,
                padding: '10px 16px', borderRadius: 12, background: palette.primary, color: palette.primaryInk,
                fontSize: 14, fontWeight: 500, cursor: 'pointer',
              }}>
                <Icon name="plus" size={16} color={palette.primaryInk} />
                {lang === 'fr' ? 'Ajouter une recette' : 'Add a recipe'}
              </div>
            )}
          </div>
        ) : (
          <div style={{ padding: '8px 20px 120px', display: 'grid', gridTemplateColumns: web ? 'repeat(2, 1fr)' : '1fr', gap: 12 }}>
            {catFiltered.map((r, idx) => (
              <RecipeFeedItem key={r.id} recipe={r} inventory={inventory} palette={palette} lang={lang}
                onOpen={() => openRecipe(r)} featured={idx === 0 && !web && view === 'all' && !activeCat}
                isFav={FAV.has(r.id)} onToggleFav={() => toggleFav(r.id)} />
            ))}
          </div>
        ))}
      </div>
    </div>
  );
}

function RecipeFeedItem({ recipe, inventory, palette, lang, onOpen, featured, isFav, onToggleFav }) {
  // Unified matcher works for every source (seed ids, QC names, online names).
  const m = (typeof window !== 'undefined' && window.matchRecipe)
    ? window.matchRecipe(recipe, inventory)
    : { matched: 0, total: Math.max(1, (recipe.uses || []).length), expiringUsed: 0, missing: [] };
  const matched = m.matched;
  const total = Math.max(1, m.total);
  const hasMatch = total > 0 && (recipe.ingredients || []).length + (recipe.uses || []).length > 0;
  const pct = Math.round(matched / total * 100);
  const allHave = hasMatch && matched >= total;

  const FavBtn = onToggleFav ? (
    <div onClick={(e) => { e.stopPropagation(); onToggleFav(); }} style={{
      position: 'absolute', top: 10, right: 60,
      width: 32, height: 32, borderRadius: 16,
      background: 'rgba(255,255,255,0.92)', backdropFilter: 'blur(8px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      cursor: 'pointer', fontSize: 16,
      color: isFav ? '#D94B4B' : '#8A7468',
    }}>{isFav ? '♥' : '♡'}</div>
  ) : null;

  if (featured) {
    return (
      <div onClick={onOpen} style={{
        borderRadius: 22, overflow: 'hidden', cursor: 'pointer', position: 'relative',
        background: palette.card, border: `1px solid ${palette.line}`,
      }}>
        <div style={{
          height: 140, background: `linear-gradient(135deg, ${recipe.bg1 || palette.primary}, ${recipe.bg2 || palette.amber})`,
          position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center',
        }}>
          {recipe.image
            ? <img src={recipe.image} alt="" loading="lazy" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }} />
            : <div style={{ fontSize: 76, filter: 'drop-shadow(0 6px 14px rgba(0,0,0,0.22))' }}>{recipe.hero}</div>}
          {hasMatch && (
          <div style={{
            position: 'absolute', top: 12, left: 12,
            padding: '4px 9px', borderRadius: 999,
            background: 'rgba(255,255,255,0.92)', backdropFilter: 'blur(8px)',
            fontSize: 10.5, fontFamily: 'Geist Mono, monospace', fontWeight: 500,
            color: '#2A3531', display: 'flex', alignItems: 'center', gap: 5,
          }}>
            <Icon name="sparkle" size={11} color="#2A3531" />
            {allHave ? t(lang, 'haveAll') : `${pct}% ${t(lang, 'haveIt').toLowerCase()}`}
          </div>
          )}
          <div style={{
            position: 'absolute', top: 12, right: 12,
            padding: '4px 9px', borderRadius: 999,
            background: 'rgba(255,255,255,0.92)', backdropFilter: 'blur(8px)',
            fontSize: 10.5, fontFamily: 'Geist Mono, monospace', fontWeight: 500, color: '#2A3531',
          }}>{recipe.minutes} {t(lang, 'minutes')}</div>
          {onToggleFav && (
            <div onClick={(e) => { e.stopPropagation(); onToggleFav(); }} style={{
              position: 'absolute', bottom: 12, right: 12,
              width: 36, height: 36, borderRadius: 18,
              background: 'rgba(255,255,255,0.92)', backdropFilter: 'blur(8px)',
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              cursor: 'pointer', fontSize: 17,
              color: isFav ? '#D94B4B' : '#5A4830',
            }}>{isFav ? '♥' : '♡'}</div>
          )}
        </div>
        <div style={{ padding: '14px 16px 16px' }}>
          <div style={{ fontSize: 17, fontWeight: 600, color: palette.ink, letterSpacing: -0.3, lineHeight: 1.2 }}>
            {localized(lang, recipe.name)}
            {recipe.custom && (
              <span style={{
                fontSize: 9.5, fontFamily: 'Geist Mono, monospace', marginLeft: 8,
                padding: '2px 6px', borderRadius: 5, background: palette.amberSoft, color: palette.amberSoftInk,
                fontWeight: 500, verticalAlign: 'middle',
              }}>{lang === 'fr' ? 'PERSO' : 'MINE'}</span>
            )}
          </div>
          <div style={{ fontSize: 13, color: palette.ink3, marginTop: 4 }}>{localized(lang, recipe.tagline)}</div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 12, opacity: hasMatch ? 1 : 0.35 }}>
            <div style={{ flex: 1, height: 5, borderRadius: 4, background: palette.line2, overflow: 'hidden' }}>
              <div style={{ width: `${hasMatch ? pct : 0}%`, height: '100%', background: palette.primary, borderRadius: 4 }} />
            </div>
            <span style={{ fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.primary, fontWeight: 500 }}>
              {hasMatch ? `${matched}/${total}` : '—'}
            </span>
          </div>
        </div>
      </div>
    );
  }

  // compact card
  return (
    <div onClick={onOpen} style={{
      background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 16,
      padding: 12, display: 'flex', gap: 12, alignItems: 'center', cursor: 'pointer',
      position: 'relative',
    }}>
      <div style={{
        width: 64, height: 64, borderRadius: 14, flexShrink: 0, overflow: 'hidden',
        background: `linear-gradient(135deg, ${recipe.bg1 || palette.primary}, ${recipe.bg2 || palette.amber})`,
        display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 32,
      }}>{recipe.image
        ? <img src={recipe.image} alt="" loading="lazy" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
        : recipe.hero}</div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 15, fontWeight: 600, color: palette.ink, letterSpacing: -0.2, lineHeight: 1.2, display: 'flex', alignItems: 'center', gap: 6 }}>
          {localized(lang, recipe.name)}
          {recipe.custom && (
            <span style={{
              fontSize: 9, fontFamily: 'Geist Mono, monospace',
              padding: '1px 5px', borderRadius: 4, background: palette.amberSoft, color: palette.amberSoftInk,
              fontWeight: 500,
            }}>{lang === 'fr' ? 'PERSO' : 'MINE'}</span>
          )}
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 6, fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.ink2 }}>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
            <Icon name="clock" size={12} color={palette.ink3} /> {recipe.minutes}m
          </span>
          {hasMatch && <span style={{ color: palette.ink3 }}>·</span>}
          {hasMatch && (
          <span style={{ color: palette.primary, fontWeight: 500 }}>
            {allHave ? t(lang, 'haveAll') : `${matched}/${total} ${t(lang, 'haveIt').toLowerCase()}`}
          </span>
          )}
          {recipe.area && <><span style={{ color: palette.ink3 }}>·</span><span>{recipe.area}</span></>}
        </div>
        {m.expiringUsed > 0 && (
          <div style={{ marginTop: 6, display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 10, fontFamily: 'Geist Mono, monospace', color: palette.amberSoftInk, background: palette.amberSoft, padding: '2px 7px', borderRadius: 6 }}>
            <Icon name="clock" size={10} color={palette.amberSoftInk} />
            {lang === 'fr' ? `utilise ${m.expiringUsed} qui expire` : `uses ${m.expiringUsed} expiring`}
          </div>
        )}
        {hasMatch && (
        <div style={{ marginTop: 8, height: 4, borderRadius: 4, background: palette.line2, overflow: 'hidden' }}>
          <div style={{ width: `${pct}%`, height: '100%', background: pct === 100 ? palette.primary : pct >= 60 ? palette.primary : palette.amber, borderRadius: 4 }} />
        </div>
        )}
      </div>
      {onToggleFav && (
        <div onClick={(e) => { e.stopPropagation(); onToggleFav(); }} style={{
          width: 32, height: 32, borderRadius: 16,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', fontSize: 17, flexShrink: 0,
          color: isFav ? '#D94B4B' : palette.ink3,
        }}>{isFav ? '♥' : '♡'}</div>
      )}
      <Icon name="chevR" size={16} color={palette.ink3} />
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// GROCERY PAGE — checkable list + smart suggestions
// ─────────────────────────────────────────────────────────────
function GroceryPage({ palette, lang, inventory, grocery, setGrocery, onToggleLang, onOpenProfile, onBuyConfirm, onAddDetailed, onBack, web, authUser }) {
  const [input, setInput] = useStateP('');
  const [grouped, setGrouped] = useStateP(true);
  const [focused, setFocused] = useStateP(false);
  // Region drives the price-estimate multiplier; persisted across sessions.
  const REGIONS = (typeof window !== 'undefined' && window.PRICE_REGIONS) || [];
  const [region, setRegion] = useStateP(() => {
    try { return localStorage.getItem('pw_region') || 'mtl'; } catch (e) { return 'mtl'; }
  });
  const setRegionPersist = (id) => { setRegion(id); try { localStorage.setItem('pw_region', id); } catch (e) {} };
  const priceOf = (g) => (typeof window !== 'undefined' && window.estimatePrice)
    ? window.estimatePrice(g.name, g.qty, region, lang) : null;
  const estTotal = (typeof window !== 'undefined' && window.estimateListTotal)
    ? window.estimateListTotal(grocery, region, lang, true) : null;

  // build autocomplete suggestions from inventory + ITEM_SUGGESTIONS
  const knownNames = useMemoP(() => {
    const map = new Map();
    inventory.forEach(i => {
      const n = localized(lang, i.name);
      map.set(n.toLowerCase(), { name: n, emoji: i.emoji });
    });
    ITEM_SUGGESTIONS.forEach(s => {
      const n = localized(lang, s.name);
      map.set(n.toLowerCase(), { name: n, emoji: s.emoji });
    });
    return [...map.values()];
  }, [inventory, lang]);
  const autocomplete = input.trim()
    ? knownNames.filter(k => k.name.toLowerCase().includes(input.toLowerCase())).slice(0, 4)
    : [];

  const expiringSoon = inventory.filter(i => i.daysLeft <= 5 && i.daysLeft >= 0).slice(0, 3);
  const checkedCount = grocery.filter(g => g.checked).length;

  const aisleLabels = {
    fresh: t(lang, 'aisleFresh'),
    dry: t(lang, 'aisleDry'),
    frozen: t(lang, 'aisleFrozen'),
    other: t(lang, 'aisleOther'),
  };

  const guessAisle = (name) => {
    const n = (name || '').toLowerCase();
    if (/(lait|fromage|yogourt|oeuf|poulet|saumon|viande|salade|tomate|avocat|fruit|legume|fresh|cheese|milk|egg|meat|chicken|fish)/.test(n)) return 'fresh';
    if (/(pates|pasta|riz|rice|huile|oil|sel|sucre|farine|cereale|cereal|sauce|conserve|can|pain|bread|biscuit|cookie)/.test(n)) return 'dry';
    if (/(surgele|frozen|glace|ice)/.test(n)) return 'frozen';
    return 'other';
  };

  const addItem = () => {
    const v = input.trim();
    if (!v) return;
    setGrocery(prev => [...prev, {
      id: 'gn_' + Date.now(),
      name: v,
      emoji: '🛒',
      aisle: guessAisle(v),
      qty: '',
      checked: false,
      source: 'manual',
    }]);
    setInput('');
  };
  const toggle = (g) => {
    // When checking off (going from unchecked → checked), offer to add to inventory
    if (!g.checked && onBuyConfirm) {
      onBuyConfirm(g);
      return;
    }
    setGrocery(prev => prev.map(x => x.id === g.id ? { ...x, checked: !x.checked } : x));
  };
  const remove = (id) => setGrocery(prev => prev.filter(g => g.id !== id));
  // 1-tap restock: add the everyday staples you're NOT currently stocking.
  const restockBasics = () => {
    const staples = ((typeof window !== 'undefined' && window.FOODS) || []).filter(f => f.staple);
    const haveInv = new Set(inventory.map(i => localized(lang, i.name).toLowerCase()));
    const onList = new Set(grocery.map(g => (typeof g.name === 'string' ? g.name : localized(lang, g.name)).toLowerCase()));
    const toAdd = staples
      .filter(s => { const n = localized(lang, s.name).toLowerCase(); return !haveInv.has(n) && !onList.has(n); })
      .map((s, i) => ({ id: 'gs_' + Date.now() + '_' + i, name: s.name, emoji: s.emoji, aisle: guessAisle(localized(lang, s.name)), qty: '', checked: false, source: 'restock' }));
    if (toAdd.length) setGrocery(prev => [...prev, ...toAdd]);
    else alert(lang === 'fr' ? 'Tu as déjà tous les essentiels !' : 'You already have all the basics!');
  };
  const clearChecked = () => setGrocery(prev => prev.filter(g => !g.checked));
  const clearAll = () => {
    if (!grocery.length) return;
    const msg = lang === 'fr' ? 'Vider toute la liste d\'épicerie ?' : 'Clear the entire grocery list?';
    if (typeof window !== 'undefined' && !window.confirm(msg)) return;
    setGrocery([]);
  };
  const shareList = () => {
    const lines = grocery.filter(g => !g.checked)
      .map(g => `• ${typeof g.name === 'string' ? g.name : localized(lang, g.name)}${g.qty ? ' (' + g.qty + ')' : ''}`);
    const text = (lang === 'fr' ? 'Liste d\'épicerie — PantryWise\n' : 'Grocery list — PantryWise\n') + lines.join('\n');
    if (navigator.share) { navigator.share({ title: 'PantryWise', text }).catch(() => {}); }
    else if (navigator.clipboard) { navigator.clipboard.writeText(text).then(() => alert(lang === 'fr' ? 'Liste copiée !' : 'List copied!')).catch(() => {}); }
    else { alert(text); }
  };

  const renderItem = (g) => (
    <div key={g.id} onClick={() => toggle(g)} style={{
      display: 'flex', alignItems: 'center', gap: 12, padding: '10px 14px',
      borderBottom: `1px solid ${palette.line2}`,
      cursor: 'pointer', opacity: g.checked ? 0.55 : 1,
    }}>
      <div style={{
        width: 22, height: 22, borderRadius: 11, flexShrink: 0,
        background: g.checked ? palette.primary : 'transparent',
        border: `1.5px solid ${g.checked ? palette.primary : palette.line}`,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        {g.checked && <Icon name="check" size={13} color={palette.primaryInk} />}
      </div>
      <div style={{ fontSize: 20, flexShrink: 0 }}>{g.emoji || '🛒'}</div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{
          fontSize: 14.5, fontWeight: 500, color: palette.ink, letterSpacing: -0.1,
          textDecoration: g.checked ? 'line-through' : 'none',
          textDecorationColor: palette.ink3,
        }}>
          {typeof g.name === 'string' ? g.name : localized(lang, g.name)}
          {g.brand && (
            <span style={{
              marginLeft: 6, fontSize: 11, fontFamily: 'Geist Mono, monospace',
              color: palette.ink3, fontWeight: 400,
            }}>· {g.brand}</span>
          )}
        </div>
        {(g.qty || g.store || g.notes) && (
          <div style={{ fontSize: 11.5, color: palette.ink3, marginTop: 1, fontFamily: 'Geist Mono, monospace', display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {g.qty && <span>{g.qty}</span>}
            {!g.checked && priceOf(g) != null && (
              <span style={{ color: palette.primary, fontWeight: 500 }}>· ~{priceOf(g).toFixed(2)} $</span>
            )}
            {g.store && (
              <span style={{ color: palette.amber }}>
                · 📍 {g.store}
              </span>
            )}
            {g.source === 'recipe' && <span style={{ opacity: 0.75 }}>· {lang === 'fr' ? 'depuis recette' : 'from recipe'}</span>}
            {g.notes && <span style={{ opacity: 0.85 }}>· {g.notes}</span>}
          </div>
        )}
      </div>
      <div onClick={(e) => { e.stopPropagation(); remove(g.id); }} style={{ padding: 6, color: palette.ink3, cursor: 'pointer' }}>
        <Icon name="close" size={14} color={palette.ink3} />
      </div>
    </div>
  );

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>
        <PageHeader
          palette={palette}
          title={t(lang, 'grocTitle')}
          subtitle={`${grocery.length - checkedCount} ${t(lang, 'grocSub')} · ${checkedCount} ${t(lang, 'checkedCount')}`}
          lang={lang}
          onBack={onBack}
          onToggleLang={onToggleLang}
          onOpenProfile={onOpenProfile}
          authUser={authUser}
        />

        {/* Estimated cost of what's left to buy (regional estimate) */}
        {estTotal != null && grocery.length - checkedCount > 0 && (
          <div style={{ padding: '0 20px 12px' }}>
            <div style={{ background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 16, padding: '13px 15px' }}>
              <div style={{ display: 'flex', alignItems: 'flex-end', gap: 10 }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.ink3, letterSpacing: 0.04, textTransform: 'uppercase' }}>
                    {t(lang, 'priceEstimate')} · {grocery.length - checkedCount} {t(lang, 'priceRemaining')}
                  </div>
                  <div style={{ fontSize: 26, fontWeight: 700, color: palette.ink, letterSpacing: -0.6, marginTop: 2 }}>
                    {estTotal.toFixed(2)} $
                  </div>
                </div>
                {/* Region selector */}
                <select value={region} onChange={e => setRegionPersist(e.target.value)}
                  onClick={e => e.stopPropagation()} style={{
                    border: `1px solid ${palette.line}`, background: palette.cardSub, color: palette.ink2,
                    borderRadius: 10, padding: '7px 8px', fontSize: 12, fontFamily: 'inherit', cursor: 'pointer', maxWidth: 130,
                  }}>
                  {REGIONS.map(r => <option key={r.id} value={r.id}>{localized(lang, r.label)}</option>)}
                </select>
              </div>
              {/* Illustrative bar vs a ~150$ weekly reference */}
              <div style={{ marginTop: 10, height: 7, borderRadius: 5, background: palette.line2, overflow: 'hidden' }}>
                <div style={{
                  width: `${Math.min(100, estTotal / 150 * 100)}%`, height: '100%', borderRadius: 5,
                  background: estTotal > 150 ? palette.amber : palette.primary,
                }} />
              </div>
              <div style={{ fontSize: 10, color: palette.ink3, marginTop: 7, fontFamily: 'Geist Mono, monospace', display: 'flex', alignItems: 'center', gap: 5 }}>
                <Icon name="sparkle" size={10} color={palette.ink3} />
                {t(lang, 'priceNote')}
              </div>
            </div>
          </div>
        )}

        {/* Smart hint */}
        {expiringSoon.length > 0 && (
          <div style={{ padding: '0 20px 12px' }}>
            <div style={{
              background: palette.amberSoft, border: `1px solid ${palette.amber}30`,
              borderRadius: 16, padding: '12px 14px',
            }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <Icon name="sparkle" size={14} color={palette.amberSoftInk} />
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 12.5, fontWeight: 600, color: palette.amberSoftInk }}>{t(lang, 'smartHint')}</div>
                  <div style={{ fontSize: 11, color: palette.amberSoftInk, opacity: 0.8 }}>{t(lang, 'smartHintSub')}</div>
                </div>
              </div>
              <div style={{ display: 'flex', gap: 6, marginTop: 10, flexWrap: 'wrap' }}>
                {expiringSoon.map(item => {
                  const inList = grocery.some(g => {
                    const gn = typeof g.name === 'string' ? g.name : localized(lang, g.name);
                    return gn.toLowerCase().includes(localized(lang, item.name).toLowerCase());
                  });
                  return (
                    <div key={item.id} onClick={() => {
                      if (inList) return;
                      setGrocery(prev => [...prev, {
                        id: 'gh_' + item.id + '_' + Date.now(),
                        name: item.name,
                        emoji: item.emoji,
                        aisle: 'fresh',
                        qty: item.qty + ' ' + localized(lang, item.unit),
                        checked: false,
                        source: 'smart',
                      }]);
                    }} style={{
                      padding: '6px 10px', borderRadius: 999,
                      background: inList ? 'transparent' : palette.amber,
                      color: inList ? palette.amberSoftInk : '#fff',
                      border: inList ? `1px solid ${palette.amber}` : 'none',
                      fontSize: 12, fontWeight: 500, cursor: inList ? 'default' : 'pointer',
                      display: 'inline-flex', alignItems: 'center', gap: 6,
                      opacity: inList ? 0.6 : 1,
                    }}>
                      <span style={{ fontSize: 13 }}>{item.emoji}</span>
                      {localized(lang, item.name)}
                      {!inList && <Icon name="plus" size={11} color="#fff" />}
                    </div>
                  );
                })}
              </div>
            </div>
          </div>
        )}

        {/* Add bar */}
        <div style={{ padding: '0 20px 12px' }}>
          <div style={{
            background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 14,
            padding: '10px 14px', display: 'flex', alignItems: 'center', gap: 10,
            position: 'relative',
          }}>
            <Icon name="plus" size={17} color={palette.ink3} />
            <input
              value={input}
              onChange={e => setInput(e.target.value)}
              onFocus={() => setFocused(true)}
              onBlur={() => setTimeout(() => setFocused(false), 200)}
              onKeyDown={e => e.key === 'Enter' && addItem()}
              placeholder={t(lang, 'grocPh')}
              style={{
                flex: 1, border: 'none', outline: 'none', background: 'transparent',
                fontSize: 14.5, color: palette.ink, fontFamily: 'inherit',
              }}
            />
            {onAddDetailed && (
              <div onClick={onAddDetailed} style={{
                padding: '5px 9px', borderRadius: 8,
                background: palette.cardSub, color: palette.ink2, border: `1px solid ${palette.line}`,
                fontSize: 11, fontWeight: 500, cursor: 'pointer',
                fontFamily: 'Geist Mono, monospace', letterSpacing: 0.04,
                display: 'inline-flex', alignItems: 'center', gap: 4,
              }}>
                <Icon name="pen" size={11} color={palette.ink2} />
                {lang === 'fr' ? 'Détails' : 'Details'}
              </div>
            )}
            {input && (
              <div onClick={addItem} style={{
                padding: '5px 12px', borderRadius: 8,
                background: palette.primary, color: palette.primaryInk,
                fontSize: 12, fontWeight: 500, cursor: 'pointer',
              }}>{t(lang, 'add')}</div>
            )}
            {/* Autocomplete dropdown */}
            {focused && autocomplete.length > 0 && (
              <div style={{
                position: 'absolute', top: 'calc(100% + 4px)', left: 0, right: 0, zIndex: 5,
                background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 12,
                boxShadow: '0 8px 24px rgba(0,0,0,0.10)', overflow: 'hidden',
              }}>
                {autocomplete.map((s, i) => (
                  <div key={i} onClick={() => { setInput(s.name); }} style={{
                    display: 'flex', alignItems: 'center', gap: 10, padding: '9px 14px',
                    borderBottom: i < autocomplete.length - 1 ? `1px solid ${palette.line2}` : 'none',
                    cursor: 'pointer',
                  }}>
                    <div style={{ fontSize: 17 }}>{s.emoji}</div>
                    <div style={{ flex: 1, fontSize: 13.5, color: palette.ink }}>{s.name}</div>
                    <Icon name="plus" size={12} color={palette.ink3} />
                  </div>
                ))}
              </div>
            )}
          </div>
        </div>

        {/* Toggle grouped */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '0 20px 12px' }}>
          <div onClick={() => setGrouped(!grouped)} style={{
            display: 'inline-flex', alignItems: 'center', gap: 6,
            fontSize: 12, color: palette.ink3, cursor: 'pointer',
            fontFamily: 'Geist Mono, monospace', letterSpacing: 0.02,
          }}>
            <div style={{
              width: 26, height: 16, borderRadius: 9, padding: 2,
              background: grouped ? palette.primary : palette.line, display: 'flex',
              transition: 'background 180ms',
            }}>
              <div style={{
                width: 12, height: 12, borderRadius: 6, background: '#fff',
                transform: grouped ? 'translateX(10px)' : 'translateX(0)',
                transition: 'transform 180ms',
              }} />
            </div>
            {lang === 'fr' ? 'Grouper par rayon' : 'Group by aisle'}
          </div>
          <div onClick={restockBasics} style={{ fontSize: 12, color: palette.primary, cursor: 'pointer', fontWeight: 500, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
            <Icon name="plus" size={12} color={palette.primary} />{lang === 'fr' ? 'Essentiels' : 'Basics'}
          </div>
          <div style={{ flex: 1 }} />
          {grocery.length > 0 && (
            <div onClick={shareList} style={{ fontSize: 12, color: palette.ink2, cursor: 'pointer', fontWeight: 500, display: 'inline-flex', alignItems: 'center', gap: 4 }}>
              <Icon name="users" size={12} color={palette.ink2} />{lang === 'fr' ? 'Partager' : 'Share'}
            </div>
          )}
          {checkedCount > 0 && (
            <div onClick={clearChecked} style={{ fontSize: 12, color: palette.ink2, cursor: 'pointer', fontWeight: 500 }}>
              {t(lang, 'clearChecked')}
            </div>
          )}
          {grocery.length > 0 && (
            <div onClick={clearAll} style={{ fontSize: 12, color: palette.red, cursor: 'pointer', fontWeight: 500 }}>
              {lang === 'fr' ? 'Tout vider' : 'Clear all'}
            </div>
          )}
        </div>

        {/* List */}
        <div style={{ padding: '0 20px 100px' }}>
          {grocery.length === 0 ? (
            <div style={{ padding: '40px 16px', textAlign: 'center', color: palette.ink3 }}>
              <div style={{ fontSize: 40, marginBottom: 8, opacity: 0.55 }}>🛒</div>
              <div style={{ fontSize: 13.5 }}>{lang === 'fr' ? 'Liste vide' : 'Empty list'}</div>
            </div>
          ) : grouped ? (
            ['fresh', 'dry', 'frozen', 'other'].map(aisle => {
              const items = grocery.filter(g => g.aisle === aisle);
              if (items.length === 0) return null;
              return (
                <div key={aisle} style={{ marginBottom: 14 }}>
                  <div style={{
                    fontSize: 11, fontFamily: 'Geist Mono, monospace',
                    color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase',
                    padding: '6px 4px', display: 'flex', alignItems: 'center', gap: 8,
                  }}>
                    {aisleLabels[aisle]} <span style={{ opacity: 0.5 }}>· {items.length}</span>
                  </div>
                  <div style={{ background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 14, overflow: 'hidden' }}>
                    {items.map(renderItem)}
                  </div>
                </div>
              );
            })
          ) : (
            <div style={{ background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 14, overflow: 'hidden' }}>
              {grocery.map(renderItem)}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// PROFILE PAGE
// ─────────────────────────────────────────────────────────────
function ProfilePage({ palette, lang, inventory, leftovers, cookLog, onOpenAdmin, household, setHousehold, privacyPrefs, setPrivacyPrefs, onInviteFamily, onToggleLang, setTweak, tweaks, onOpenSavedExplainer, onBack, web, authUser, onSignOut, onShowLogin, authStatus, syncStatus }) {
  // Real household members from the server (so the count matches reality once a
  // family is formed). Falls back to local `household` when not signed in.
  const [liveMembers, setLiveMembers] = useStateP(null);
  useEffectP(() => {
    let alive = true;
    if (!(window.PWAuth && window.PWAuth.api && authUser)) return;
    window.PWAuth.api.members().then((res) => {
      if (!alive) return;
      const srv = (res.members || []).map((m, i) => ({
        id: m.id, name: m.name, email: m.email,
        tone: ['#4F8267', '#B85149', '#C68A2E', '#4A6FA5', '#7A5BB5', '#8B6B4A'][i % 6],
        initial: (m.name || '?').charAt(0).toUpperCase(),
      }));
      if (srv.length) setLiveMembers(srv);
    }).catch(() => {});
    return () => { alive = false; };
  }, [authUser?.id]);
  const HH = liveMembers || household || HOUSEHOLD;
  const [notifs, setNotifs] = useStateP(NOTIF_PREFS_DEFAULT);
  const [diet, setDiet] = useStateP('none');
  const [allergies, setAllergies] = useStateP([{ id: 'a1', label: lang === 'fr' ? 'Arachides' : 'Peanuts' }]);
  const [allergyInput, setAllergyInput] = useStateP('');
  const [expandNotif, setExpandNotif] = useStateP(false);
  const [deductOnCook, setDeductOnCook] = useStateP(() => { try { return localStorage.getItem('pw_deductOnCook') === '1'; } catch (e) { return false; } });
  const toggleDeduct = () => { const v = !deductOnCook; setDeductOnCook(v); try { localStorage.setItem('pw_deductOnCook', v ? '1' : '0'); } catch (e) {} };
  // Admin dashboard appears only if the server says this account is the owner.
  const [isAdmin, setIsAdmin] = useStateP(false);
  useEffectP(() => {
    if (!authUser || !(window.PWAuth && window.PWAuth.api && window.PWAuth.api.adminStats)) return;
    window.PWAuth.api.adminStats().then(() => setIsAdmin(true)).catch(() => setIsAdmin(false));
  }, [authUser && authUser.id]);
  const [expandFood, setExpandFood] = useStateP(false);
  const [expandFamily, setExpandFamily] = useStateP(false);
  const [expandPrivacy, setExpandPrivacy] = useStateP(false);

  // Profile photo (stored locally per user id; initial is the fallback)
  const photoInputRef = useRefP(null);
  const [photo, setPhoto] = useStateP(() => (window.getProfilePhoto ? window.getProfilePhoto(authUser?.id) : null));
  const onPickPhoto = (e) => {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      // Downscale to keep localStorage small (~max 256px square jpeg)
      const img = new Image();
      img.onload = () => {
        const size = 256;
        const c = document.createElement('canvas');
        c.width = size; c.height = size;
        const ctx = c.getContext('2d');
        const min = Math.min(img.width, img.height);
        const sx = (img.width - min) / 2, sy = (img.height - min) / 2;
        ctx.drawImage(img, sx, sy, min, min, 0, 0, size, size);
        let out;
        try { out = c.toDataURL('image/jpeg', 0.82); } catch (err) { out = reader.result; }
        if (window.setProfilePhoto) window.setProfilePhoto(authUser?.id, out);
        setPhoto(out);
      };
      img.onerror = () => {
        if (window.setProfilePhoto) window.setProfilePhoto(authUser?.id, reader.result);
        setPhoto(reader.result);
      };
      img.src = reader.result;
    };
    reader.readAsDataURL(file);
  };
  const onRemovePhoto = () => {
    if (window.setProfilePhoto) window.setProfilePhoto(authUser?.id, null);
    setPhoto(null);
  };

  // Real stats from the cooking log (no hardcoded numbers).
  const _log = cookLog || [];
  const _now = Date.now();
  const weekLog = _log.filter(e => _now - e.ts < 7 * 86400000);
  const monthLog = _log.filter(e => _now - e.ts < 30 * 86400000);
  const savedThisMonth = Math.round(monthLog.reduce((s, e) => s + (e.savedCents || 0), 0) / 100);
  const avoidedThisWeek = weekLog.reduce((s, e) => s + (e.rescued || 0), 0);
  const cookedThisWeek = weekLog.length;
  const stats = [
    { label: t(lang, 'statsSaved'), value: `${savedThisMonth}$`, sub: lang === 'fr' ? 'ce mois-ci' : 'this month', tone: palette.primary, onTap: onOpenSavedExplainer },
    { label: t(lang, 'statsAvoid'), value: `${avoidedThisWeek}`, sub: lang === 'fr' ? 'cette semaine' : 'this week', tone: palette.amber },
    { label: t(lang, 'statsCooked'), value: `${cookedThisWeek}`, sub: lang === 'fr' ? 'cette semaine' : 'this week', tone: palette.blue },
  ];

  const Row = ({ icon, label, value, accent, last, onClick, right, danger }) => (
    <div onClick={onClick} style={{
      display: 'flex', alignItems: 'center', gap: 12, padding: '13px 14px',
      borderBottom: last ? 'none' : `1px solid ${palette.line2}`,
      cursor: onClick ? 'pointer' : 'default',
    }}>
      <div style={{
        width: 30, height: 30, borderRadius: 9,
        background: danger ? palette.redSoft : palette.cardSub, color: danger ? palette.redSoftInk : palette.ink2,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        border: `1px solid ${danger ? 'transparent' : palette.line}`,
      }}>
        <Icon name={icon} size={15} color={danger ? palette.redSoftInk : palette.ink2} />
      </div>
      <div style={{ flex: 1, fontSize: 14, color: danger ? palette.red : palette.ink, fontWeight: 500 }}>{label}</div>
      {right || (
        <>
          {value && <div style={{ fontSize: 12.5, color: accent || palette.ink3, fontFamily: 'Geist Mono, monospace' }}>{value}</div>}
          {onClick && <Icon name="chevR" size={15} color={palette.ink3} />}
        </>
      )}
    </div>
  );

  const Section = ({ title, children }) => (
    <div style={{ marginBottom: 18 }}>
      <div style={{
        fontSize: 11, fontFamily: 'Geist Mono, monospace',
        color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase',
        padding: '8px 4px',
      }}>{title}</div>
      <div style={{ background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 14, overflow: 'hidden' }}>
        {children}
      </div>
    </div>
  );

  const ToggleSwitch = ({ on, onToggle }) => (
    <div onClick={(e) => { e.stopPropagation(); onToggle(); }} style={{
      width: 36, height: 22, borderRadius: 12, padding: 2,
      background: on ? palette.primary : palette.line, display: 'flex',
      cursor: 'pointer', transition: 'background 180ms',
    }}>
      <div style={{
        width: 18, height: 18, borderRadius: 9, background: '#fff',
        transform: on ? 'translateX(14px)' : 'translateX(0)',
        transition: 'transform 180ms',
      }} />
    </div>
  );

  const Chipy = ({ active, label, onClick, accent }) => (
    <div onClick={onClick} style={{
      padding: '6px 11px', borderRadius: 999,
      background: active ? (accent || palette.primary) : palette.card,
      color: active ? '#fff' : palette.ink2,
      border: `1px solid ${active ? (accent || palette.primary) : palette.line}`,
      fontSize: 12.5, fontWeight: 500, cursor: 'pointer',
    }}>{label}</div>
  );

  const addAllergy = () => {
    const v = allergyInput.trim();
    if (!v) return;
    setAllergies(prev => [...prev, { id: 'a_' + Date.now(), label: v }]);
    setAllergyInput('');
  };
  const removeAllergy = (id) => setAllergies(prev => prev.filter(a => a.id !== id));

  return (
    <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>
      {/* Header w/ avatar */}
      <div style={{ padding: '24px 20px 8px', display: 'flex', alignItems: 'center', gap: 14 }}>
        {onBack && (
          <div onClick={onBack} style={{
            width: 38, height: 38, borderRadius: 12,
            background: palette.cardSub, color: palette.ink,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            cursor: 'pointer', border: `1px solid ${palette.line}`, flexShrink: 0,
          }} aria-label="back">
            <Icon name="chevL" size={17} color={palette.ink} />
          </div>
        )}
        <div style={{ position: 'relative', flexShrink: 0 }}
          onClick={() => photoInputRef.current && photoInputRef.current.click()}>
          <UserAvatar palette={palette} size={64} gradient
            name={authUser?.name} userId={authUser?.id} photo={photo} />
          <div style={{
            position: 'absolute', right: -2, bottom: -2,
            width: 24, height: 24, borderRadius: 12, background: palette.primary,
            border: `2px solid ${palette.bg}`, color: palette.primaryInk,
            display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
          }}>
            <Icon name="camera" size={12} color={palette.primaryInk} />
          </div>
          <input ref={photoInputRef} type="file" accept="image/*" onChange={onPickPhoto}
            style={{ display: 'none' }} />
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          {authUser && (
            <>
              <div style={{ fontSize: 18, fontWeight: 600, color: palette.ink, letterSpacing: -0.3, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                {authUser.name}
              </div>
              <div style={{ fontSize: 12.5, color: palette.ink3, marginTop: 2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                {authUser.email}
              </div>
              {photo && (
                <div onClick={onRemovePhoto} style={{ fontSize: 11.5, color: palette.ink3, marginTop: 4, cursor: 'pointer' }}>
                  {t(lang, 'removePhoto')}
                </div>
              )}
            </>
          )}
        </div>
        <div onClick={onToggleLang} style={{
          padding: '6px 10px', borderRadius: 8,
          background: palette.cardSub, color: palette.ink2, border: `1px solid ${palette.line}`,
          fontSize: 11, fontFamily: 'Geist Mono, monospace', fontWeight: 500, letterSpacing: 0.06,
          cursor: 'pointer',
        }}>{lang.toUpperCase()}</div>
      </div>

      <div style={{ padding: '16px 20px 100px' }}>
        {/* Stats */}
        <div style={{
          fontSize: 11, fontFamily: 'Geist Mono, monospace',
          color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase',
          padding: '8px 4px',
        }}>{t(lang, 'profStats')}</div>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, marginBottom: 18 }}>
          {stats.map((s, i) => (
            <div key={i} onClick={s.onTap} style={{
              background: palette.card, border: `1px solid ${palette.line}`,
              borderRadius: 14, padding: '14px 12px',
              cursor: s.onTap ? 'pointer' : 'default',
              position: 'relative',
            }}>
              <div style={{ width: 6, height: 6, borderRadius: 3, background: s.tone, marginBottom: 8 }} />
              <div style={{ fontSize: 22, fontWeight: 600, letterSpacing: -0.8, color: palette.ink, lineHeight: 1, fontFamily: 'Geist, sans-serif' }}>{s.value}</div>
              <div style={{ fontSize: 11, color: palette.ink2, marginTop: 4, fontWeight: 500 }}>{s.label}</div>
              <div style={{ fontSize: 10, color: palette.ink3, marginTop: 1, fontFamily: 'Geist Mono, monospace' }}>{s.sub}</div>
              {s.onTap && (
                <div style={{ position: 'absolute', top: 10, right: 10, color: palette.ink3 }}>
                  <Icon name="sparkle" size={11} color={palette.ink3} />
                </div>
              )}
            </div>
          ))}
        </div>
        {onOpenSavedExplainer && (
          <div onClick={onOpenSavedExplainer} style={{
            margin: '0 0 18px',
            fontSize: 11.5, color: palette.primary, fontFamily: 'Geist Mono, monospace',
            cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 6,
          }}>
            <Icon name="sparkle" size={11} color={palette.primary} />
            {t(lang, 'savedHow')}
          </div>
        )}

        <Section title={t(lang, 'sectionAccount')}>
          <Row icon="user" label={t(lang, 'email')} value={authUser?.email || (lang === 'fr' ? 'Invité (local)' : 'Guest (local)')} onClick={() => {}} />
          {authUser && (() => {
            const st = syncStatus?.status;
            const ok = st === 'ok';
            const busy = st === 'pulling' || st === 'pushing';
            const label = lang === 'fr' ? 'Sauvegarde' : 'Sync';
            const value = busy
              ? (lang === 'fr' ? '… en cours' : '… working')
              : ok
                ? (lang === 'fr' ? '● synchronisé' : '● synced')
                : (lang === 'fr' ? '⚠ hors-ligne' : '⚠ offline');
            return (
              <Row icon="leaf" label={label} value={value}
                onClick={() => { if (syncStatus?.error) alert((lang === 'fr' ? 'Détail sync : ' : 'Sync detail: ') + syncStatus.error); }} />
            );
          })()}
          <Row icon="leaf" label={t(lang, 'language')} value={lang === 'fr' ? 'Français' : 'English'} onClick={onToggleLang} last />
        </Section>

        {/* Notif preferences — expandable */}
        <Section title={t(lang, 'sectionPrefs')}>
          <Row icon="bell" label={t(lang, 'notifs')} onClick={() => setExpandNotif(!expandNotif)}
            right={
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <span style={{ fontSize: 12, color: palette.primary, fontFamily: 'Geist Mono, monospace' }}>
                  {Object.values(notifs).filter(Boolean).length}/{Object.keys(notifs).length}
                </span>
                <Icon name={expandNotif ? 'chevD' : 'chevR'} size={15} color={palette.ink3} />
              </div>
            } />
          {expandNotif && (
            <div style={{ padding: '4px 14px 12px', borderBottom: `1px solid ${palette.line2}` }}>
              {[
                ['expiry', t(lang, 'notifExpiry')],
                ['low', t(lang, 'notifLow')],
                ['specials', t(lang, 'notifSpecials')],
                ['noMeal', t(lang, 'notifNoMeal')],
                ['ready', t(lang, 'notifReady')],
              ].map(([k, label]) => (
                <div key={k} style={{
                  display: 'flex', alignItems: 'center', gap: 10, padding: '9px 0',
                }}>
                  <div style={{ flex: 1, fontSize: 13, color: palette.ink2 }}>{label}</div>
                  <ToggleSwitch on={notifs[k]} onToggle={() => setNotifs({ ...notifs, [k]: !notifs[k] })} />
                </div>
              ))}
            </div>
          )}
          <Row icon="chef" label={lang === 'fr' ? 'Retirer l\'inventaire en cuisinant' : 'Deduct inventory when cooking'}
            right={<ToggleSwitch on={deductOnCook} onToggle={toggleDeduct} />} last />
        </Section>

        {/* Food / diet / allergies — expandable */}
        <Section title={t(lang, 'sectionFood')}>
          <Row icon="chef" label={t(lang, 'diet')} onClick={() => setExpandFood(!expandFood)}
            right={
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <span style={{ fontSize: 12, color: palette.ink3, fontFamily: 'Geist Mono, monospace' }}>{dietLabel(lang, diet)}</span>
                <Icon name={expandFood ? 'chevD' : 'chevR'} size={15} color={palette.ink3} />
              </div>
            } />
          {expandFood && (
            <div style={{ padding: '6px 14px 14px', borderBottom: `1px solid ${palette.line2}` }}>
              <div style={{ fontSize: 10.5, fontFamily: 'Geist Mono, monospace', color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase', marginBottom: 8 }}>
                {t(lang, 'dietsHeading')}
              </div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 14 }}>
                {DIETS_LIST.map(d => (
                  <Chipy key={d} active={diet === d} label={dietLabel(lang, d)} onClick={() => setDiet(d)} />
                ))}
              </div>
              <div style={{ fontSize: 10.5, fontFamily: 'Geist Mono, monospace', color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase', marginBottom: 8 }}>
                {t(lang, 'allergies')} · {t(lang, 'avoid')}
              </div>
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                {allergies.map(a => (
                  <div key={a.id} style={{
                    padding: '5px 10px', borderRadius: 999,
                    background: palette.redSoft, color: palette.redSoftInk,
                    fontSize: 12, fontWeight: 500,
                    display: 'inline-flex', alignItems: 'center', gap: 6,
                  }}>
                    {a.label}
                    <div onClick={() => removeAllergy(a.id)} style={{ cursor: 'pointer', display: 'flex' }}>
                      <Icon name="close" size={11} color={palette.redSoftInk} />
                    </div>
                  </div>
                ))}
                <div style={{
                  display: 'inline-flex', alignItems: 'center', gap: 4,
                  padding: '4px 8px', borderRadius: 999,
                  background: palette.cardSub, border: `1px dashed ${palette.line}`,
                }}>
                  <input
                    value={allergyInput}
                    onChange={e => setAllergyInput(e.target.value)}
                    onKeyDown={e => e.key === 'Enter' && addAllergy()}
                    placeholder={t(lang, 'allergyTypeholder')}
                    style={{
                      border: 'none', outline: 'none', background: 'transparent',
                      fontSize: 12, color: palette.ink, fontFamily: 'inherit', width: 130,
                    }}
                  />
                  {allergyInput && (
                    <div onClick={addAllergy} style={{ cursor: 'pointer', color: palette.primary, display: 'flex' }}>
                      <Icon name="plus" size={12} color={palette.primary} />
                    </div>
                  )}
                </div>
              </div>
            </div>
          )}
          <Row icon="user" label={t(lang, 'family')} onClick={() => setExpandFamily(!expandFamily)}
            right={
              <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <div style={{ display: 'flex' }}>
                  {HH.slice(0, 3).map((m, i) => (
                    <div key={m.id} style={{
                      width: 22, height: 22, borderRadius: 11,
                      background: m.tone, color: '#fff',
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      fontSize: 10.5, fontWeight: 600, fontFamily: 'Geist Mono, monospace',
                      marginLeft: i === 0 ? 0 : -6,
                      border: `1.5px solid ${palette.card}`,
                    }}>{m.initial}</div>
                  ))}
                </div>
                <Icon name={expandFamily ? 'chevD' : 'chevR'} size={15} color={palette.ink3} />
              </div>
            } />
          {expandFamily && (
            <div style={{ padding: '4px 14px 12px' }}>
              {HH.map(m => (
                <div key={m.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 0' }}>
                  <div style={{
                    width: 28, height: 28, borderRadius: 14,
                    background: m.tone, color: '#fff',
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                    fontSize: 11.5, fontWeight: 600, fontFamily: 'Geist Mono, monospace',
                  }}>{m.initial}</div>
                  <div style={{ flex: 1, fontSize: 13.5, color: palette.ink, fontWeight: 500 }}>
                    {m.name}
                    {m.email && <div style={{ fontSize: 11, color: palette.ink3, fontFamily: 'Geist Mono, monospace', marginTop: 1 }}>{m.email}</div>}
                  </div>
                  {m.id !== 'u1' && setHousehold && (
                    <div onClick={() => setHousehold(prev => prev.filter(x => x.id !== m.id))} style={{
                      padding: 6, color: palette.ink3, cursor: 'pointer',
                    }}>
                      <Icon name="close" size={13} color={palette.ink3} />
                    </div>
                  )}
                  <div style={{ fontSize: 10.5, fontFamily: 'Geist Mono, monospace', color: palette.primary, background: palette.primarySoft, padding: '3px 7px', borderRadius: 6 }}>
                    {t(lang, 'shared')}
                  </div>
                </div>
              ))}
              <div onClick={() => onInviteFamily && onInviteFamily()} style={{
                marginTop: 6, padding: '11px 12px', borderRadius: 10,
                border: `1px dashed ${palette.line}`,
                fontSize: 13, color: palette.primary, cursor: 'pointer',
                display: 'flex', alignItems: 'center', gap: 8, fontWeight: 500,
              }}>
                <Icon name="plus" size={14} color={palette.primary} />
                {t(lang, 'inviteFamily')}
              </div>
            </div>
          )}
          {/* Privacy: which meal slots to share with family */}
          {setPrivacyPrefs && privacyPrefs && (
            <>
              <Row icon="leaf" label={lang === 'fr' ? 'Confidentialité repas' : 'Meal privacy'} onClick={() => setExpandPrivacy(!expandPrivacy)}
                right={
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <span style={{ fontSize: 11, color: palette.ink3, fontFamily: 'Geist Mono, monospace' }}>
                      {[privacyPrefs.shareBreakfast, privacyPrefs.shareLunch, privacyPrefs.shareDinner].filter(Boolean).length}/3
                    </span>
                    <Icon name={expandPrivacy ? 'chevD' : 'chevR'} size={15} color={palette.ink3} />
                  </div>
                } last />
              {expandPrivacy && (
                <div style={{ padding: '4px 14px 14px', borderTop: `1px solid ${palette.line2}` }}>
                  <div style={{ fontSize: 11, color: palette.ink3, marginBottom: 8, lineHeight: 1.45 }}>
                    {lang === 'fr'
                      ? 'Choisis ce que les autres voient. Le souper est partagé par défaut. L\'inventaire reste toujours partagé.'
                      : 'Pick what others see. Dinner is shared by default. Inventory is always shared.'}
                  </div>
                  {[
                    ['shareBreakfast', t(lang, 'planBreakfast')],
                    ['shareLunch', t(lang, 'planLunch')],
                    ['shareDinner', t(lang, 'planDinner')],
                  ].map(([k, label]) => (
                    <div key={k} style={{
                      display: 'flex', alignItems: 'center', gap: 10, padding: '9px 0',
                    }}>
                      <div style={{ flex: 1, fontSize: 13, color: palette.ink2 }}>{label}</div>
                      <ToggleSwitch on={privacyPrefs[k]} onToggle={() => setPrivacyPrefs({ ...privacyPrefs, [k]: !privacyPrefs[k] })} />
                    </div>
                  ))}
                </div>
              )}
            </>
          )}
        </Section>

        {isAdmin && (
          <Section title={lang === 'fr' ? 'Administration' : 'Admin'}>
            <Row icon="users" label={lang === 'fr' ? 'Tableau de bord' : 'Dashboard'}
              value={lang === 'fr' ? 'comptes · activité' : 'accounts · activity'}
              onClick={onOpenAdmin} last />
          </Section>
        )}

        {/* Data: export everything as a JSON backup */}
        <Section title={lang === 'fr' ? 'Mes données' : 'My data'}>
          <Row icon="box" label={lang === 'fr' ? 'Exporter mes données' : 'Export my data'}
            onClick={() => {
              try {
                const dump = { exportedAt: new Date().toISOString(),
                  inventory, leftovers, household, privacyPrefs,
                  app: JSON.parse(localStorage.getItem('pantrywise.v1') || '{}') };
                const blob = new Blob([JSON.stringify(dump, null, 2)], { type: 'application/json' });
                const a = document.createElement('a');
                a.href = URL.createObjectURL(blob);
                a.download = 'pantrywise-export-' + new Date().toISOString().slice(0, 10) + '.json';
                a.click(); URL.revokeObjectURL(a.href);
              } catch (e) { alert(lang === 'fr' ? 'Export impossible' : 'Export failed'); }
            }} />
          {authUser && (
            <Row icon="trash" label={lang === 'fr' ? 'Supprimer mon compte' : 'Delete my account'} danger
              onClick={() => {
                const msg = lang === 'fr'
                  ? 'Supprimer définitivement ton compte et tes données ? Cette action est irréversible.'
                  : 'Permanently delete your account and data? This cannot be undone.';
                if (!window.confirm(msg)) return;
                const run = (typeof api !== 'undefined' && api.deleteAccount) ? api.deleteAccount() : Promise.reject();
                run.then(() => { if (onSignOut) onSignOut(); })
                  .catch(() => alert(lang === 'fr' ? 'Suppression impossible (réessaie en ligne).' : 'Delete failed (try again online).'));
              }} last />
          )}
        </Section>

        <Section title={t(lang, 'sectionAbout')}>
          <Row icon="bell" label={lang === 'fr' ? 'Aide & commentaires' : 'Help & feedback'}
            onClick={() => { window.location.href = 'mailto:support@pantry-wise.com?subject=PantryWise'; }} />
          <Row icon="leaf" label={lang === 'fr' ? 'Confidentialité' : 'Privacy'}
            onClick={() => window.open('/privacy.html', '_blank')} />
          <Row icon="leaf" label={lang === 'fr' ? 'Conditions' : 'Terms'}
            onClick={() => window.open('/terms.html', '_blank')} />
          <Row icon="leaf" label={t(lang, 'version')} value="1.0 · pantry-wise.com" last />
        </Section>

        <div onClick={() => {
          if (authUser) {
            if (typeof window !== 'undefined' && !window.confirm(lang === 'fr' ? 'Te déconnecter ?' : 'Sign out?')) return;
            if (onSignOut) onSignOut();
          } else if (onShowLogin) {
            onShowLogin();
          }
        }} style={{
          marginTop: 8, padding: '13px 14px', textAlign: 'center',
          fontSize: 14, color: authUser ? palette.red : palette.primary,
          fontWeight: 600, cursor: 'pointer',
        }}>
          {authUser
            ? t(lang, 'logout')
            : (lang === 'fr' ? 'Se connecter / Créer un compte' : 'Sign in / Create account')}
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// ONBOARDING
// ─────────────────────────────────────────────────────────────
function Onboarding({ palette, lang, onComplete, onToggleLang, web }) {
  const [step, setStep] = useStateP(0); // 0,1,2 = slides, 3 = setup
  const [selected, setSelected] = useStateP(new Set());

  const slides = [
    {
      title: t(lang, 'onboardTitle1'),
      desc: t(lang, 'onboardDesc1'),
      hero: (
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 16, padding: '24px 0' }}>
          {['🥑', '🍅', '🥬'].map((e, i) => (
            <div key={i} style={{
              width: 80, height: 80, borderRadius: 22,
              background: palette.card, border: `1px solid ${palette.line}`,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              fontSize: 40,
              transform: i === 1 ? 'translateY(-12px) scale(1.08)' : `translateY(${i === 0 ? 8 : 4}px) scale(0.94)`,
              boxShadow: i === 1 ? `0 12px 28px ${palette.primary}40` : '0 4px 14px rgba(0,0,0,0.06)',
            }}>{e}</div>
          ))}
        </div>
      ),
      accent: palette.primary,
    },
    {
      title: t(lang, 'onboardTitle2'),
      desc: t(lang, 'onboardDesc2'),
      hero: (
        <div style={{ padding: '24px 16px', display: 'flex', justifyContent: 'center' }}>
          <div style={{
            width: 240, background: palette.card, border: `1px solid ${palette.line}`,
            borderRadius: 20, padding: 16, position: 'relative', overflow: 'hidden',
            boxShadow: '0 8px 24px rgba(0,0,0,0.06)',
          }}>
            <div style={{ position: 'absolute', top: -16, right: -16, opacity: 0.18, fontSize: 80 }}>🥑</div>
            <div style={{ position: 'relative', fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.amberSoftInk, background: palette.amberSoft, padding: '2px 8px', borderRadius: 6, display: 'inline-flex', alignItems: 'center', gap: 5 }}>
              <span style={{ width: 4, height: 4, borderRadius: 2, background: palette.amber }} />
              {lang === 'fr' ? 'Expire demain' : 'Expires tomorrow'}
            </div>
            <div style={{ marginTop: 10, fontSize: 17, fontWeight: 600, color: palette.ink, letterSpacing: -0.3 }}>
              {lang === 'fr' ? 'Avocat' : 'Avocado'}
            </div>
            <div style={{ marginTop: 4, fontSize: 12, color: palette.ink3 }}>
              {lang === 'fr' ? '3 recettes l\'utilisent' : '3 recipes use it'}
            </div>
            <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', gap: 6, color: palette.primary, fontSize: 12, fontWeight: 500 }}>
              <Icon name="sparkle" size={12} color={palette.primary} />
              {lang === 'fr' ? 'Toast à l\'avocat' : 'Avocado toast'}
              <Icon name="chevR" size={11} color={palette.primary} />
            </div>
          </div>
        </div>
      ),
      accent: palette.amber,
    },
    {
      title: t(lang, 'onboardTitle3'),
      desc: t(lang, 'onboardDesc3'),
      hero: (
        <div style={{ padding: '20px 16px', display: 'flex', justifyContent: 'center' }}>
          <div style={{
            width: 240, background: palette.card, border: `1px solid ${palette.line}`,
            borderRadius: 18, overflow: 'hidden', boxShadow: '0 8px 24px rgba(0,0,0,0.06)',
          }}>
            {[
              { e: '🥑', n: lang === 'fr' ? 'Avocats' : 'Avocados', q: '3', check: false },
              { e: '🍋', n: lang === 'fr' ? 'Citron' : 'Lemon', q: '2', check: false },
              { e: '🥛', n: lang === 'fr' ? 'Lait' : 'Milk', q: '1 L', check: true },
              { e: '🍝', n: lang === 'fr' ? 'Pâtes' : 'Pasta', q: '500g', check: false },
            ].map((it, i, arr) => (
              <div key={i} style={{
                display: 'flex', alignItems: 'center', gap: 10, padding: '9px 12px',
                borderBottom: i < arr.length - 1 ? `1px solid ${palette.line2}` : 'none',
                opacity: it.check ? 0.5 : 1,
              }}>
                <div style={{
                  width: 16, height: 16, borderRadius: 8,
                  background: it.check ? palette.primary : 'transparent',
                  border: `1.5px solid ${it.check ? palette.primary : palette.line}`,
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                }}>{it.check && <Icon name="check" size={10} color="#fff" />}</div>
                <div style={{ fontSize: 16 }}>{it.e}</div>
                <div style={{ flex: 1, fontSize: 12.5, color: palette.ink, fontWeight: 500, textDecoration: it.check ? 'line-through' : 'none' }}>{it.n}</div>
                <div style={{ fontSize: 10.5, color: palette.ink3, fontFamily: 'Geist Mono, monospace' }}>{it.q}</div>
              </div>
            ))}
          </div>
        </div>
      ),
      accent: palette.blue,
    },
  ];

  // Slides screen
  if (step < 3) {
    const s = slides[step];
    return (
      <div style={{
        position: 'absolute', inset: 0, zIndex: 200,
        background: palette.bg, color: palette.ink,
        display: 'flex', flexDirection: 'column',
        fontFamily: 'Geist, ui-sans-serif, system-ui, sans-serif',
      }}>
        {/* Top bar */}
        <div style={{ display: 'flex', alignItems: 'center', padding: '24px 20px 0' }}>
          <div style={{ flex: 1, display: 'flex', gap: 6 }}>
            {slides.map((_, i) => (
              <div key={i} style={{
                flex: 1, height: 3, borderRadius: 2,
                background: i <= step ? palette.primary : palette.line,
                transition: 'background 220ms',
              }} />
            ))}
          </div>
          <div onClick={onComplete} style={{
            marginLeft: 12, fontSize: 12.5, color: palette.ink3, cursor: 'pointer',
            fontFamily: 'Geist Mono, monospace',
          }}>{t(lang, 'onboardSkip')}</div>
        </div>

        {/* Hero */}
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'center', padding: '0 24px' }}>
          {s.hero}
          <div style={{ textAlign: 'center', marginTop: 24 }}>
            <div style={{
              display: 'inline-block', fontSize: 10.5, fontFamily: 'Geist Mono, monospace',
              color: s.accent, letterSpacing: 0.1, textTransform: 'uppercase',
              padding: '3px 9px', borderRadius: 6, background: hexToSoft(s.accent),
              marginBottom: 14,
            }}>{`0${step + 1} · 03`}</div>
            <div style={{ fontSize: 26, fontWeight: 600, letterSpacing: -0.8, lineHeight: 1.15, color: palette.ink, maxWidth: 360, margin: '0 auto' }}>
              {s.title}
            </div>
            <div style={{ fontSize: 14.5, color: palette.ink2, marginTop: 12, lineHeight: 1.5, maxWidth: 320, margin: '12px auto 0' }}>
              {s.desc}
            </div>
          </div>
        </div>

        {/* Bottom CTA */}
        <div style={{ padding: '24px 24px 32px', display: 'flex', alignItems: 'center', gap: 10 }}>
          {step > 0 && (
            <div onClick={() => setStep(step - 1)} style={{
              width: 50, height: 50, borderRadius: 14,
              background: palette.card, border: `1px solid ${palette.line}`, color: palette.ink2,
              display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
            }}>
              <Icon name="chevL" size={18} color={palette.ink2} />
            </div>
          )}
          <div onClick={() => step < 2 ? setStep(step + 1) : setStep(3)} style={{
            flex: 1, height: 50, borderRadius: 14,
            background: palette.ink, color: palette.bg,
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
            fontSize: 15, fontWeight: 500, cursor: 'pointer',
          }}>
            {step < 2 ? t(lang, 'onboardNext') : t(lang, 'onboardStart')}
            <Icon name="chevR" size={16} color={palette.bg} />
          </div>
        </div>
      </div>
    );
  }

  // Setup screen — pick 5 items
  return (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 200,
      background: palette.bg, color: palette.ink,
      display: 'flex', flexDirection: 'column',
      fontFamily: 'Geist, ui-sans-serif, system-ui, sans-serif',
    }}>
      <div style={{ padding: '24px 20px 16px' }}>
        <div onClick={() => setStep(2)} style={{
          width: 32, height: 32, borderRadius: 10, background: palette.cardSub,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          cursor: 'pointer', border: `1px solid ${palette.line}`, color: palette.ink2,
          marginBottom: 18,
        }}>
          <Icon name="chevL" size={15} color={palette.ink2} />
        </div>
        <div style={{ fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.primary, letterSpacing: 0.1, textTransform: 'uppercase', marginBottom: 6 }}>
          {lang === 'fr' ? 'Étape 4 · 4' : 'Step 4 · 4'}
        </div>
        <div style={{ fontSize: 24, fontWeight: 600, letterSpacing: -0.6, lineHeight: 1.2, color: palette.ink }}>
          {t(lang, 'onboardSetupTitle')}
        </div>
        <div style={{ fontSize: 14, color: palette.ink2, marginTop: 6, lineHeight: 1.5 }}>
          {t(lang, 'onboardSetupSub')}
        </div>
      </div>

      <div style={{ flex: 1, overflowY: 'auto', padding: '0 20px 16px' }}>
        <div style={{
          display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8,
        }}>
          {ONBOARD_PICKS.map(item => {
            const sel = selected.has(item.id);
            return (
              <div key={item.id} onClick={() => {
                setSelected(prev => {
                  const next = new Set(prev);
                  if (next.has(item.id)) next.delete(item.id);
                  else next.add(item.id);
                  return next;
                });
              }} style={{
                aspectRatio: '1 / 1', borderRadius: 16,
                background: sel ? palette.primarySoft : palette.card,
                border: `1.5px solid ${sel ? palette.primary : palette.line}`,
                display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
                cursor: 'pointer', position: 'relative',
                transition: 'background 140ms, border-color 140ms',
              }}>
                {sel && (
                  <div style={{
                    position: 'absolute', top: 8, right: 8,
                    width: 20, height: 20, borderRadius: 10, background: palette.primary,
                    display: 'flex', alignItems: 'center', justifyContent: 'center',
                  }}>
                    <Icon name="check" size={12} color={palette.primaryInk} />
                  </div>
                )}
                <div style={{ fontSize: 32 }}>{item.emoji}</div>
                <div style={{ fontSize: 11.5, color: sel ? palette.primarySoftInk : palette.ink, marginTop: 6, fontWeight: 500, textAlign: 'center', padding: '0 4px' }}>
                  {localized(lang, item.name)}
                </div>
              </div>
            );
          })}
        </div>
      </div>

      <div style={{ padding: '12px 24px 32px', borderTop: `1px solid ${palette.line}`, background: palette.bg }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
          <div style={{ flex: 1, fontSize: 12, color: palette.ink2 }}>
            <span style={{ fontFamily: 'Geist Mono, monospace', color: palette.primary, fontWeight: 600 }}>{selected.size}</span> {t(lang, 'selectedN')}
          </div>
          <div onClick={() => onComplete([])} style={{ fontSize: 12.5, color: palette.ink3, cursor: 'pointer', fontFamily: 'Geist Mono, monospace' }}>
            {t(lang, 'onboardSkip')}
          </div>
        </div>
        <div onClick={() => {
          const picks = ONBOARD_PICKS.filter(p => selected.has(p.id)).map(p => ({
            id: p.id + '_' + Date.now(),
            name: p.name, emoji: p.emoji, mono: p.id[0].toUpperCase(),
            qty: p.defaultQty, unit: p.defaultUnit,
            location: p.defaultLoc, daysLeft: p.defaultDays,
          }));
          onComplete(picks);
        }} style={{
          height: 50, borderRadius: 14,
          background: palette.primary,
          color: palette.primaryInk,
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
          fontSize: 15, fontWeight: 500, cursor: 'pointer',
          transition: 'background 180ms',
        }}>
          {t(lang, 'onboardSetupCTA')}
          <Icon name="chevR" size={16} color={palette.primaryInk} />
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// FAMILY HUB — the shared "espace famille": members + invite,
// shortcuts to the meal plan and shared grocery list, sharing prefs.
// ─────────────────────────────────────────────────────────────
function FamilyHubPage({ palette, lang, household, setHousehold, authUser, onInviteFamily,
  onOpenPlan, onOpenList, onToggleLang, onOpenProfile, onBack, mealPlan, grocery,
  privacyPrefs, setPrivacyPrefs }) {
  const tt = (fr, en) => (lang === 'fr' ? fr : en);
  const [members, setMembers] = useStateP(household || []);
  const [pending, setPending] = useStateP([]);
  const isAuthed = !!(window.PWAuth && window.PWAuth.api && authUser);

  // Pull the real members + pending invites from the server.
  useEffectP(() => {
    let alive = true;
    if (!isAuthed) { setMembers(household || []); return; }
    window.PWAuth.api.members().then((res) => {
      if (!alive) return;
      const srv = (res.members || []).map((m, i) => ({
        id: m.id, name: m.name, email: m.email,
        tone: ['#4F8267', '#B85149', '#C68A2E', '#4A6FA5', '#7A5BB5', '#8B6B4A'][i % 6],
        initial: (m.name || '?').charAt(0).toUpperCase(),
        self: m.id === authUser?.id,
      }));
      setMembers(srv);
      setPending(res.pendingInvites || []);
    }).catch(() => { setMembers(household || []); });
    return () => { alive = false; };
  }, [isAuthed, authUser?.id]);

  const planCount = (mealPlan || []).length;
  const listCount = (grocery || []).length;

  const Tile = ({ icon, title, sub, onClick, tone }) => (
    <div onClick={onClick} style={{
      flex: 1, padding: '16px 14px', borderRadius: 16,
      background: palette.card, border: `1px solid ${palette.line}`, cursor: 'pointer',
      display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0,
    }}>
      <div style={{
        width: 38, height: 38, borderRadius: 11, background: tone || palette.primarySoft,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
      }}>
        <Icon name={icon} size={20} color="#fff" style="filled" />
      </div>
      <div>
        <div style={{ fontSize: 14.5, fontWeight: 600, color: palette.ink, letterSpacing: -0.2 }}>{title}</div>
        <div style={{ fontSize: 12, color: palette.ink3, marginTop: 2 }}>{sub}</div>
      </div>
    </div>
  );

  return (
    <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>
      <PageHeader palette={palette} title={t(lang, 'familySpace')} subtitle={t(lang, 'familyHub')}
        lang={lang} onBack={onBack} onToggleLang={onToggleLang}
        onOpenProfile={onOpenProfile} authUser={authUser} />

      <div style={{ padding: '4px 20px 100px' }}>
        {/* Shared shortcuts */}
        <div style={{ display: 'flex', gap: 12, marginBottom: 18 }}>
          <Tile icon="calendar" title={t(lang, 'sharedPlan')} tone={palette.blue}
            sub={`${planCount} ${tt('repas planifiés', 'meals planned')}`} onClick={onOpenPlan} />
          <Tile icon="cart" title={t(lang, 'sharedList')} tone={palette.amber}
            sub={`${listCount} ${tt('articles', 'items')}`} onClick={onOpenList} />
        </div>

        {/* Members */}
        <div style={{
          fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.ink3,
          letterSpacing: 0.06, textTransform: 'uppercase', padding: '4px 4px 8px',
        }}>{t(lang, 'familyMembers')}</div>

        <div style={{ background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 16, overflow: 'hidden' }}>
          {members.length === 0 && (
            <div style={{ padding: '16px', fontSize: 13, color: palette.ink3 }}>{t(lang, 'noMembersYet')}</div>
          )}
          {members.map((m, i) => (
            <div key={m.id || i} style={{
              display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px',
              borderTop: i === 0 ? 'none' : `1px solid ${palette.line2}`,
            }}>
              <UserAvatar palette={palette} size={36} name={m.name} userId={m.id} tone={m.tone} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 14, fontWeight: 500, color: palette.ink }}>
                  {m.name} {m.self && <span style={{ fontSize: 11, color: palette.ink3 }}>({tt('toi', 'you')})</span>}
                </div>
                {m.email && <div style={{ fontSize: 11.5, color: palette.ink3, fontFamily: 'Geist Mono, monospace', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{m.email}</div>}
              </div>
              <div style={{ fontSize: 10.5, fontFamily: 'Geist Mono, monospace', color: palette.primary, background: palette.primarySoft, padding: '3px 7px', borderRadius: 6 }}>
                {t(lang, 'shared')}
              </div>
            </div>
          ))}
        </div>

        {/* Pending invites */}
        {pending.length > 0 && (
          <>
            <div style={{
              fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.ink3,
              letterSpacing: 0.06, textTransform: 'uppercase', padding: '18px 4px 8px',
            }}>{t(lang, 'pendingInvites')}</div>
            <div style={{ background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 16, overflow: 'hidden' }}>
              {pending.map((p, i) => (
                <div key={p.token || i} style={{
                  display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px',
                  borderTop: i === 0 ? 'none' : `1px solid ${palette.line2}`,
                }}>
                  <div style={{ width: 36, height: 36, borderRadius: 18, background: palette.cardSub, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                    <Icon name="clock" size={17} color={palette.ink3} />
                  </div>
                  <div style={{ flex: 1, minWidth: 0, fontSize: 13.5, color: palette.ink2, fontFamily: 'Geist Mono, monospace', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{p.email}</div>
                  <div style={{ fontSize: 10.5, color: palette.ink3 }}>{tt('en attente', 'pending')}</div>
                </div>
              ))}
            </div>
          </>
        )}

        {/* Invite button */}
        <div onClick={() => onInviteFamily && onInviteFamily()} style={{
          marginTop: 16, padding: '15px', borderRadius: 14, background: palette.primary,
          color: palette.primaryInk, textAlign: 'center', fontWeight: 500, fontSize: 15, cursor: 'pointer',
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
        }}>
          <Icon name="plus" size={18} color={palette.primaryInk} />
          {t(lang, 'inviteFamily')}
        </div>

        {/* Meal sharing prefs */}
        {setPrivacyPrefs && privacyPrefs && (
          <>
            <div style={{
              fontSize: 11, fontFamily: 'Geist Mono, monospace', color: palette.ink3,
              letterSpacing: 0.06, textTransform: 'uppercase', padding: '20px 4px 8px',
            }}>{tt('Partage des repas', 'Meal sharing')}</div>
            <div style={{ background: palette.card, border: `1px solid ${palette.line}`, borderRadius: 16, padding: '6px 4px' }}>
              {[
                { key: 'shareBreakfast', label: tt('Déjeuner', 'Breakfast') },
                { key: 'shareLunch', label: tt('Dîner', 'Lunch') },
                { key: 'shareDinner', label: tt('Souper', 'Dinner') },
              ].map((row, i) => (
                <div key={row.key} style={{
                  display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                  padding: '11px 14px', borderTop: i === 0 ? 'none' : `1px solid ${palette.line2}`,
                }}>
                  <span style={{ fontSize: 14, color: palette.ink }}>{row.label}</span>
                  <div onClick={() => setPrivacyPrefs({ ...privacyPrefs, [row.key]: !privacyPrefs[row.key] })} style={{
                    width: 44, height: 26, borderRadius: 13, padding: 3, cursor: 'pointer',
                    background: privacyPrefs[row.key] ? palette.primary : palette.line,
                    transition: 'background 160ms',
                  }}>
                    <div style={{
                      width: 20, height: 20, borderRadius: 10, background: '#fff',
                      transform: privacyPrefs[row.key] ? 'translateX(18px)' : 'translateX(0)',
                      transition: 'transform 160ms',
                    }} />
                  </div>
                </div>
              ))}
            </div>
          </>
        )}
      </div>
    </div>
  );
}

Object.assign(window, {
  InventoryPage, RecipesPage, GroceryPage, ProfilePage, Onboarding, PageHeader, Chip,
  MealPlanPage, FamilyHubPage,
});

// ─────────────────────────────────────────────────────────────
// MEAL PLAN PAGE — weekly calendar + shared assignees + votes
// ─────────────────────────────────────────────────────────────
function MealPlanPage({ palette, lang, inventory, mealPlan, setMealPlan, leftovers, openRecipe, onBack, onToggleLang, onOpenProfile, onAssignMeal, onAddWeekToList, household, privacyPrefs, web, authUser, initialDate }) {
  const today = todayISO();
  // Selected day defaults to today (or a passed-in date, e.g. from the Family hub).
  const [selectedDate, setSelectedDate] = useStateP(initialDate || today);
  const [view, setView] = useStateP('week'); // 'week' | 'month'
  const dayLabels = ['planMon', 'planTue', 'planWed', 'planThu', 'planFri', 'planSat', 'planSun'];
  const dayLabelsLong = ['planMonL', 'planTueL', 'planWedL', 'planThuL', 'planFriL', 'planSatL', 'planSunL'];
  const slots = ['breakfast', 'lunch', 'dinner'];

  const HH = household || HOUSEHOLD;

  const weekStart = startOfWeekISO(selectedDate);
  const weekDates = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
  const dayMeals = mealPlan.filter(m => m.date === selectedDate);
  const memberById = (id) => HH.find(h => h.id === id);
  const longDayLabel = (iso) => t(lang, dayLabelsLong[weekdayIndex(iso)]);

  // Build a month grid (weeks × 7) for the month containing selectedDate.
  const buildMonth = () => {
    const d = parseISO(selectedDate);
    const first = new Date(d.getFullYear(), d.getMonth(), 1);
    const gridStart = parseISO(startOfWeekISO(isoDate(first)));
    const weeks = [];
    for (let w = 0; w < 6; w++) {
      const row = [];
      for (let i = 0; i < 7; i++) {
        const cur = new Date(gridStart); cur.setDate(gridStart.getDate() + w * 7 + i);
        row.push(isoDate(cur));
      }
      weeks.push(row);
      // stop after we've passed the end of the month on a completed week
      const last = parseISO(row[6]);
      if (last.getMonth() !== d.getMonth() && w >= 4) break;
    }
    return weeks;
  };

  const Avatar = ({ user, size = 22 }) => (
    <div style={{
      width: size, height: size, borderRadius: size / 2,
      background: user.tone, color: '#fff',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      fontSize: size * 0.46, fontWeight: 600, fontFamily: 'Geist Mono, monospace',
      flexShrink: 0,
    }}>{user.initial}</div>
  );

  const togglePresence = (meal, userId) => {
    setMealPlan(prev => prev.map(m => {
      if (m.date !== meal.date || m.slot !== meal.slot) return m;
      const att = { ...(m.attendees || HH.reduce((acc, h) => ({ ...acc, [h.id]: true }), {})) };
      att[userId] = !(att[userId] === true);
      return { ...m, attendees: att };
    }));
  };

  // privacy: hide breakfast/lunch for other members if shareBreakfast / shareLunch off
  const isVisibleSlot = (slot) => {
    if (!privacyPrefs) return true;
    if (slot === 'breakfast') return privacyPrefs.shareBreakfast;
    if (slot === 'lunch') return privacyPrefs.shareLunch;
    return true;
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>
        <PageHeader
          palette={palette}
          title={t(lang, 'planTitle')}
          subtitle={t(lang, 'planSub')}
          lang={lang}
          onToggleLang={onToggleLang}
          onOpenProfile={onOpenProfile}
          onBack={onBack}
          authUser={authUser}
          action={onAddWeekToList ? (
            <div onClick={onAddWeekToList} style={{
              padding: '0 12px', height: 38, borderRadius: 12,
              background: palette.primary, color: palette.primaryInk,
              display: 'inline-flex', alignItems: 'center', gap: 6,
              fontSize: 12.5, fontWeight: 500, cursor: 'pointer', flexShrink: 0,
            }}>
              <Icon name="cart" size={14} color={palette.primaryInk} />
              {lang === 'fr' ? 'Épicerie' : 'Groceries'}
            </div>
          ) : null}
        />

        {/* Shared household pill */}
        <div style={{ padding: '0 20px 12px', display: 'flex', alignItems: 'center', gap: 10 }}>
          <div style={{
            display: 'inline-flex', alignItems: 'center', gap: 8,
            padding: '6px 12px 6px 8px', borderRadius: 999,
            background: palette.primarySoft, color: palette.primarySoftInk,
          }}>
            <div style={{ display: 'flex' }}>
              {HH.map((m, i) => (
                <div key={m.id} style={{
                  width: 22, height: 22, borderRadius: 11,
                  background: m.tone, color: '#fff',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  fontSize: 11, fontWeight: 600, fontFamily: 'Geist Mono, monospace',
                  marginLeft: i === 0 ? 0 : -8,
                  border: `1.5px solid ${palette.primarySoft}`,
                }}>{m.initial}</div>
              ))}
            </div>
            <span style={{ fontSize: 12, fontWeight: 500 }}>{t(lang, 'shared')} · {HH.length}</span>
          </div>
        </div>

        {/* Month/year + week/month toggle + nav */}
        <div style={{ padding: '0 20px 10px', display: 'flex', alignItems: 'center', gap: 8 }}>
          <div onClick={() => setSelectedDate(view === 'month' ? addDays(selectedDate, -28) : addDays(selectedDate, -7))} style={{
            width: 30, height: 30, borderRadius: 9, background: palette.cardSub, border: `1px solid ${palette.line}`,
            display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
          }}><Icon name="chevL" size={15} color={palette.ink2} /></div>
          <div style={{ flex: 1, textAlign: 'center', fontSize: 15, fontWeight: 600, color: palette.ink, letterSpacing: -0.2 }}>
            {monthYearLabel(selectedDate, lang)}
          </div>
          <div onClick={() => setSelectedDate(view === 'month' ? addDays(selectedDate, 28) : addDays(selectedDate, 7))} style={{
            width: 30, height: 30, borderRadius: 9, background: palette.cardSub, border: `1px solid ${palette.line}`,
            display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
          }}><Icon name="chevR" size={15} color={palette.ink2} /></div>
        </div>
        <div style={{ padding: '0 20px 12px', display: 'flex', gap: 8, alignItems: 'center' }}>
          {['week', 'month'].map(v => (
            <div key={v} onClick={() => setView(v)} style={{
              padding: '6px 12px', borderRadius: 999, cursor: 'pointer',
              fontSize: 12, fontWeight: 500,
              background: view === v ? palette.ink : palette.card,
              color: view === v ? palette.bg : palette.ink2,
              border: `1px solid ${view === v ? palette.ink : palette.line}`,
            }}>{v === 'week' ? (lang === 'fr' ? 'Semaine' : 'Week') : (lang === 'fr' ? 'Mois' : 'Month')}</div>
          ))}
          <div style={{ flex: 1 }} />
          <div onClick={() => setSelectedDate(today)} style={{
            padding: '6px 12px', borderRadius: 999, cursor: 'pointer',
            fontSize: 12, fontWeight: 500, background: palette.primarySoft, color: palette.primarySoftInk,
          }}>{lang === 'fr' ? "Aujourd'hui" : 'Today'}</div>
        </div>

        {/* Week strip */}
        {view === 'week' && (
          <div style={{
            display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 6,
            padding: '0 20px 16px',
          }}>
            {weekDates.map((iso, i) => {
              const active = selectedDate === iso;
              const isToday = iso === today;
              const mealCount = mealPlan.filter(m => m.date === iso).length;
              return (
                <div key={iso} onClick={() => setSelectedDate(iso)} style={{
                  padding: '10px 0', borderRadius: 12, textAlign: 'center', cursor: 'pointer',
                  background: active ? palette.ink : palette.card,
                  color: active ? palette.bg : palette.ink2,
                  border: `1px solid ${active ? palette.ink : (isToday ? palette.primary : palette.line)}`,
                  position: 'relative',
                }}>
                  <div style={{ fontSize: 10, fontFamily: 'Geist Mono, monospace', letterSpacing: 0.06, opacity: 0.7 }}>
                    {t(lang, dayLabels[i])}
                  </div>
                  <div style={{ fontSize: 16, fontWeight: 600, marginTop: 2, letterSpacing: -0.3 }}>{dateNumber(iso)}</div>
                  {mealCount > 0 && (
                    <div style={{
                      position: 'absolute', top: 7, right: 7,
                      width: 5, height: 5, borderRadius: 3, background: palette.primary,
                    }} />
                  )}
                </div>
              );
            })}
          </div>
        )}

        {/* Month grid */}
        {view === 'month' && (
          <div style={{ padding: '0 20px 16px' }}>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 4, marginBottom: 6 }}>
              {dayLabels.map((k) => (
                <div key={k} style={{ textAlign: 'center', fontSize: 10, fontFamily: 'Geist Mono, monospace', color: palette.ink3 }}>{t(lang, k)}</div>
              ))}
            </div>
            {buildMonth().map((week, wi) => (
              <div key={wi} style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 4, marginBottom: 4 }}>
                {week.map((iso) => {
                  const active = selectedDate === iso;
                  const isToday = iso === today;
                  const inMonth = parseISO(iso).getMonth() === parseISO(selectedDate).getMonth();
                  const mealCount = mealPlan.filter(m => m.date === iso).length;
                  return (
                    <div key={iso} onClick={() => { setSelectedDate(iso); setView('week'); }} style={{
                      aspectRatio: '1', borderRadius: 9, cursor: 'pointer',
                      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 2,
                      background: active ? palette.ink : palette.card,
                      color: active ? palette.bg : (inMonth ? palette.ink2 : palette.ink3),
                      border: `1px solid ${active ? palette.ink : (isToday ? palette.primary : palette.line)}`,
                      opacity: inMonth ? 1 : 0.4,
                    }}>
                      <span style={{ fontSize: 13, fontWeight: 600 }}>{dateNumber(iso)}</span>
                      {mealCount > 0 && <span style={{ width: 4, height: 4, borderRadius: 2, background: active ? palette.bg : palette.primary }} />}
                    </div>
                  );
                })}
              </div>
            ))}
          </div>
        )}

        {/* Day title */}
        <div style={{ padding: '0 20px 8px' }}>
          <div style={{ fontSize: 18, fontWeight: 600, color: palette.ink, letterSpacing: -0.3 }}>
            {longDayLabel(selectedDate)} {dateNumber(selectedDate)}
            {selectedDate === today && <span style={{ fontSize: 12.5, color: palette.primary, marginLeft: 8, fontWeight: 500 }}>\u00b7 {lang === 'fr' ? "aujourd'hui" : 'today'}</span>}
          </div>
          <div style={{ fontSize: 12, color: palette.ink3, fontFamily: 'Geist Mono, monospace', marginTop: 2 }}>
            {dayMeals.length} {lang === 'fr' ? `repas pr\u00e9vus` : 'meals planned'}
          </div>
        </div>

        {/* Leftovers callout for tomorrow's lunch */}
        {selectedDate === addDays(today, 1) && leftovers && leftovers.length > 0 && (
          <div style={{ padding: '4px 20px 12px' }}>
            <div style={{
              background: palette.amberSoft, color: palette.amberSoftInk,
              borderRadius: 14, padding: '12px 14px',
              display: 'flex', alignItems: 'center', gap: 12,
            }}>
              <div style={{ fontSize: 26 }}>{leftovers[0].emoji}</div>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, fontWeight: 600 }}>{t(lang, 'lunchSuggest')}</div>
                <div style={{ fontSize: 11.5, opacity: 0.85 }}>
                  {localized(lang, leftovers[0].name)} · {leftovers[0].portions} {t(lang, 'portionsLeft')}
                </div>
              </div>
              <div style={{
                padding: '5px 10px', borderRadius: 8,
                background: palette.amber, color: '#fff',
                fontSize: 11.5, fontWeight: 500, cursor: 'pointer',
              }}>
                {lang === 'fr' ? 'Planifier' : 'Plan'}
              </div>
            </div>
          </div>
        )}

        {/* Slots */}
        <div style={{ padding: '0 20px 120px', display: 'flex', flexDirection: 'column', gap: 10 }}>
          {slots.map(slot => {
            const meal = dayMeals.find(m => m.slot === slot);
            const _catalog = RECIPES.concat((typeof window !== 'undefined' && window.EXTRA_RECIPES) || []);
            let recipe = meal && _catalog.find(r => r.id === meal.recipeId);
            // Fall back to the meal snapshot (online / unknown recipes).
            if (meal && !recipe && (meal.recipeName || meal.recipeHero)) {
              recipe = {
                id: meal.recipeId, name: meal.recipeName || { fr: 'Recette', en: 'Recipe' },
                hero: meal.recipeHero || '🍽', image: meal.recipeImage || null,
                minutes: 30, portions: 2, difficulty: { fr: '', en: '' },
                bg1: palette.primary, bg2: palette.amber, ingredients: [], steps: [],
                external: String(meal.recipeId || '').startsWith('mdb_'), lite: true,
              };
            }
            const hidden = !isVisibleSlot(slot) && meal && meal.cook !== 'u1';
            return (
              <div key={slot} style={{
                background: palette.card, border: `1px solid ${palette.line}`,
                borderRadius: 16, overflow: 'hidden',
                opacity: hidden ? 0.5 : 1,
              }}>
                <div style={{
                  display: 'flex', alignItems: 'center', gap: 10,
                  padding: '10px 14px',
                  borderBottom: `1px solid ${palette.line2}`,
                  background: palette.cardSub,
                }}>
                  <span style={{
                    fontSize: 10.5, fontFamily: 'Geist Mono, monospace',
                    color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase',
                  }}>{t(lang, 'plan' + slot.charAt(0).toUpperCase() + slot.slice(1))}</span>
                  {meal && (
                    <span style={{ fontSize: 11, color: palette.ink3, fontFamily: 'Geist Mono, monospace' }}>
                      · {meal.time}
                    </span>
                  )}
                  <div style={{ flex: 1 }} />
                  {hidden && (
                    <span style={{ fontSize: 10, color: palette.ink3, fontFamily: 'Geist Mono, monospace' }}>
                      {lang === 'fr' ? 'privé' : 'private'}
                    </span>
                  )}
                  {meal && onAssignMeal && (
                    <div onClick={() => onAssignMeal({ date: selectedDate, slot, meal })} style={{
                      padding: '4px 8px', borderRadius: 8,
                      background: palette.cardSub, border: `1px solid ${palette.line}`,
                      color: palette.ink2, fontSize: 11, fontFamily: 'Geist Mono, monospace',
                      cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 4,
                    }}>
                      <Icon name="pen" size={10} color={palette.ink2} />
                      {t(lang, 'edit')}
                    </div>
                  )}
                </div>
                {meal && recipe && !hidden ? (
                  <div style={{ padding: '12px 14px' }}>
                    <div onClick={() => openRecipe(recipe)} style={{
                      display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer',
                    }}>
                      <div style={{
                        width: 52, height: 52, borderRadius: 12, flexShrink: 0, overflow: 'hidden',
                        background: `linear-gradient(135deg, ${recipe.bg1 || palette.primary}, ${recipe.bg2 || palette.amber})`,
                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                        fontSize: 26,
                      }}>{recipe.image ? <img src={recipe.image} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} /> : recipe.hero}</div>
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{ fontSize: 14.5, fontWeight: 600, color: palette.ink, letterSpacing: -0.2 }}>
                          {localized(lang, recipe.name)}
                        </div>
                        <div style={{ fontSize: 11, color: palette.ink3, marginTop: 2, fontFamily: 'Geist Mono, monospace' }}>
                          {recipe.minutes}m · {recipe.portions} {t(lang, 'portions')} · {localized(lang, recipe.difficulty)}
                        </div>
                      </div>
                      <Icon name="chevR" size={15} color={palette.ink3} />
                    </div>
                    {/* assignment + presence row */}
                    <div style={{
                      display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap',
                      marginTop: 12, paddingTop: 10,
                      borderTop: `1px solid ${palette.line2}`,
                    }}>
                      <span style={{
                        fontSize: 10, fontFamily: 'Geist Mono, monospace',
                        color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase',
                      }}>{t(lang, 'planAssign')}</span>
                      {meal.cook && memberById(meal.cook) && (
                        <div style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
                          <Avatar user={memberById(meal.cook)} size={22} />
                          <span style={{ fontSize: 12.5, color: palette.ink, fontWeight: 500 }}>{memberById(meal.cook).name}</span>
                        </div>
                      )}
                    </div>
                    {/* presence checks */}
                    <div style={{
                      display: 'flex', alignItems: 'center', gap: 8, marginTop: 10, flexWrap: 'wrap',
                    }}>
                      <span style={{
                        fontSize: 10, fontFamily: 'Geist Mono, monospace',
                        color: palette.ink3, letterSpacing: 0.06, textTransform: 'uppercase',
                      }}>{lang === 'fr' ? 'Présents' : 'Present'}</span>
                      {HH.map(m => {
                        const att = meal.attendees || HH.reduce((acc, h) => ({ ...acc, [h.id]: true }), {});
                        const here = att[m.id] === true;
                        return (
                          <div key={m.id} onClick={(e) => { e.stopPropagation(); togglePresence(meal, m.id); }} style={{
                            display: 'inline-flex', alignItems: 'center', gap: 4,
                            padding: '4px 8px 4px 4px', borderRadius: 999,
                            background: here ? palette.primarySoft : palette.cardSub,
                            border: `1px solid ${here ? palette.primary : palette.line}`,
                            cursor: 'pointer',
                            opacity: here ? 1 : 0.5,
                          }}>
                            <Avatar user={m} size={18} />
                            <span style={{ fontSize: 11, color: here ? palette.primarySoftInk : palette.ink3, fontWeight: 500 }}>
                              {here ? '✓' : '×'} {m.name}
                            </span>
                          </div>
                        );
                      })}
                    </div>
                  </div>
                ) : (
                  <div onClick={() => onAssignMeal && onAssignMeal({ date: selectedDate, slot, meal: null })} style={{
                    padding: '16px 14px', display: 'flex', alignItems: 'center', gap: 10,
                    fontSize: 13, color: palette.ink2, cursor: 'pointer',
                  }}>
                    <div style={{
                      width: 36, height: 36, borderRadius: 10,
                      background: palette.cardSub, border: `1px dashed ${palette.line}`,
                      display: 'flex', alignItems: 'center', justifyContent: 'center',
                      color: palette.ink3,
                    }}>
                      <Icon name="plus" size={15} color={palette.ink3} />
                    </div>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontSize: 14, fontWeight: 500, color: palette.ink }}>
                        {hidden ? (lang === 'fr' ? 'Repas privé' : 'Private meal') : `${t(lang, 'planAdd')}`}
                      </div>
                      <div style={{ fontSize: 11.5, color: palette.ink3, marginTop: 2 }}>
                        {hidden
                          ? (lang === 'fr' ? 'Non partagé selon tes préférences' : 'Not shared by preference')
                          : (lang === 'fr' ? `Tape pour planifier le ${t(lang, 'plan' + slot.charAt(0).toUpperCase() + slot.slice(1)).toLowerCase()}` : `Tap to plan ${slot}`)}
                      </div>
                    </div>
                    {!hidden && <Icon name="chevR" size={15} color={palette.ink3} />}
                  </div>
                )}
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}
