// Moderis — "Wat ga je eten?"-sectie (de maaltijdenpool), onderdeel van het
// dashboard. Blader door al je maaltijden op elk moment van de dag: geen vaste
// eetmomenten, maar filteren op grootte (hap/klein/normaal/groot), "in huis"
// en eiwitrijk. Dekking en ranking zijn 100% lokaal (pool-shared.js) — er is
// geen AI-call nodig om te kiezen.
//
// Pool-kwaliteit: AI-ideeën die je nog nooit at zijn gemarkeerd als "nieuw
// voor jou" en zakken weg als je ze niet kunt maken; met ✕ archiveer je een
// gerecht (beoordeling -1) — het komt niet meer terug, maar staat in
// Recepten → Archief klaar om terug te zetten.
//
// Dagplanner: met de +-knop op een kaart zet je maaltijden in je plan om te
// ZIEN wat ze samen doen met je kcal en macro's — er wordt niets gelogd
// totdat je zelf op de log-knop in het paneel tikt. Met het "Mee naar
// werk"-filter aan is het plan je smeer/maak-lijstje voor onderweg.

function KiesChip({ actief, onClick, children, title }) {
  return (
    <button className={'m-chip' + (actief ? ' is-active' : '')} onClick={onClick} title={title}>
      {children}
    </button>
  );
}

// De grootte als voelbaar stipje (hapje = klein, groot = groot) — dezelfde
// visuele taal als de filter-chips en het kompas.
function GrootteStip({ g }) {
  return <span className={'m-stip ' + g.key} title={g.label + ' · ' + g.hint}></span>;
}

// Warm/koud + geschatte bereidingstijd als subtiele chips (afgeleid in
// pool-shared uit de bereidingstekst — geen aparte kolom).
function TempTijdBadges({ warm, min, size = 10.5 }) {
  const chip = { display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: size, fontWeight: 600,
    color: 'var(--ink-2)', background: 'var(--m-card-3)', borderRadius: 999, padding: '1px 7px', flex: '0 0 auto' };
  return (
    <React.Fragment>
      {warm != null && (
        <span style={chip}>
          <MIcon name={warm ? 'flame' : 'snow'} size={size + 1} color={warm ? '#c0704a' : '#4a86c0'} />
          {warm ? 'warm' : 'koud'}
        </span>
      )}
      {min != null && (
        <span style={chip}>
          <MIcon name="clock" size={size + 1} color="var(--ink-3)" />~{min} min
        </span>
      )}
    </React.Fragment>
  );
}

function PoolKaart({ r, onOpen, onFav, onVerberg, onPlan, planAantal, toonTelling, halen }) {
  const nieuw = window.pool.isNieuwVoorJou(r);
  return (
    <div className={'tap m-pool-card' + (r._pastNu ? ' is-past' : '')} onClick={() => onOpen(r)} role="button"
      style={planAantal ? { borderColor: 'var(--forest-300)' } : undefined}>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 5 }}>
          <GrootteStip g={r._grootte} />
          <span style={{ fontSize: 14.5, fontWeight: 600, lineHeight: 1.25, minWidth: 0,
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{r.naam}</span>
          {nieuw && <span className="m-badge m-label-omega" style={{ flex: '0 0 auto' }}>Nieuw</span>}
        </div>
        <div style={{ display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap' }}>
          <span className="num" style={{ fontSize: 12, fontWeight: 600 }}>{Math.round(r.kcal || 0)}</span>
          <span style={{ fontSize: 11, color: 'var(--ink-3)' }}>kcal</span>
          <MacroBadges m={{ p: Math.round(r.eiwitten || 0), c: Math.round(r.koolhydraten || 0), f: Math.round(r.vetten || 0) }} size={10.5} />
          <TempTijdBadges warm={r._warm} min={r._bereidMin} />
          {/* Bij sorteren op vaak/zelden: laat zien wáár dit gerecht staat. */}
          {toonTelling && (
            <span style={{ fontSize: 10.5, fontWeight: 600, color: 'var(--ink-2)', background: 'var(--m-card-3)',
              borderRadius: 999, padding: '1px 7px', flex: '0 0 auto' }}>
              {Number(r.aantal_keer_gebruikt) > 0 ? Number(r.aantal_keer_gebruikt) + '× gegeten' : 'nog nooit'}
            </span>
          )}
          {/* Gematcht via een ingrediënt (niet de naam)? Laat zien waarom. */}
          {r._zoekVia && (
            <span style={{ fontSize: 10.5, fontWeight: 600, color: 'var(--forest-700)', background: 'var(--forest-100)',
              border: '1px solid var(--forest-200)', borderRadius: 999, padding: '1px 7px', flex: '0 0 auto' }}>
              met {r._zoekVia.toLowerCase()}
            </span>
          )}
          {/* Zonder ingrediënten is de dekking onbekend — dan géén (vals) "✓ alles in huis" */}
          {r._mist != null && <InHuisBadge m={{ inHuis: r._mist.length === 0, mist: r._mist }} size={10}
            mistLabel={halen ? 'halen' : 'mist'} />}
          {/* Dagkompas-bewaking: past dit bij je volgende eetmoment / nog in je dag? */}
          {r._teVeel > 0 ? (
            <span style={{ fontSize: 10, fontWeight: 600, color: '#8a5a2b', flex: '0 0 auto' }}>
              +{Math.round(r._teVeel)} boven je dag
            </span>
          ) : r._pastNu ? (
            <span style={{ fontSize: 10, fontWeight: 600, color: 'var(--leaf-deep)', flex: '0 0 auto' }}>
              past nu
            </span>
          ) : null}
        </div>
      </div>
      <button className="tap" onClick={(e) => { e.stopPropagation(); onFav(r); }} title="Favoriet"
        style={{ width: 30, height: 30, flex: '0 0 auto', padding: 0, borderRadius: 999, border: 'none',
          background: 'transparent', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <MIcon name="star" size={16} sw={r.favoriet ? 0 : 1.8}
          color={r.favoriet ? 'var(--leaf-deep)' : 'var(--ink-3)'}
          style={r.favoriet ? { fill: 'var(--leaf-deep)' } : {}} />
      </button>
      <button className="tap" onClick={(e) => { e.stopPropagation(); onVerberg(r); }}
        title="Archiveren — niet meer voorstellen (terug te zetten via Recepten → Archief)"
        style={{ width: 30, height: 30, flex: '0 0 auto', padding: 0, borderRadius: 999,
          border: '1px solid var(--m-line)', background: 'transparent', cursor: 'pointer',
          display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <MIcon name="close" size={12} color="var(--ink-3)" />
      </button>
      {/* Dagplanner: + zet dit gerecht in je plan (alleen kijken — nog niet
          loggen); nogmaals tikken = een portie extra (bv. 3 boterhammen). */}
      {onPlan ? (
        <button className="tap" onClick={(e) => { e.stopPropagation(); onPlan(r); }}
          title={planAantal ? 'Nog een portie in je plan' : 'Zet in je plan — kijken wat het doet, nog niet loggen'}
          style={{ minWidth: 30, height: 30, flex: '0 0 auto', padding: '0 6px', borderRadius: 999,
            border: '1px solid ' + (planAantal ? 'var(--forest-300)' : 'var(--m-line)'),
            background: planAantal ? 'var(--forest-100)' : 'transparent', cursor: 'pointer',
            display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          {planAantal
            ? <span className="num" style={{ fontSize: 11.5, fontWeight: 700, color: 'var(--forest-700)' }}>{planAantal}×</span>
            : <MIcon name="plus" size={14} sw={2.2} color="var(--forest-600)" />}
        </button>
      ) : (
        <MIcon name="chevron" size={16} color="var(--ink-3)" style={{ flex: '0 0 auto' }} />
      )}
    </div>
  );
}

// De Aanrader: het hoogst gerankte gerecht krijgt het podium — de app reikt
// je één keuze aan, de rest is lijst. Zelfde tik → zelfde preview.
function PoolTopKaart({ r, onOpen, reden, onPlan, planAantal, halen }) {
  return (
    <button className="m-topkeuze tap" onClick={() => onOpen(r)}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 7 }}>
        <MIcon name="spark" size={13} color="var(--leaf-deep)" />
        <span className="m-eyebrow" style={{ color: 'var(--forest-600)' }}>Aanrader voor nu</span>
        {window.pool.isNieuwVoorJou(r) && (
          <span className="m-badge m-label-omega" style={{ marginLeft: 'auto' }}>Nieuw</span>
        )}
      </div>
      <div className="m-topkeuze-naam" style={{ marginBottom: 10 }}>{r.naam}</div>
      <div style={{ display: 'flex', gap: 7, alignItems: 'center', flexWrap: 'wrap' }}>
        <span className="num" style={{ fontSize: 14.5, fontWeight: 700 }}>{Math.round(r.kcal || 0)}</span>
        <span style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>kcal</span>
        <MacroBadges m={{ p: Math.round(r.eiwitten || 0), c: Math.round(r.koolhydraten || 0), f: Math.round(r.vetten || 0) }} size={11.5} />
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 11.5, color: 'var(--ink-3)' }}>
          <GrootteStip g={r._grootte} />{r._grootte.label.toLowerCase()}
        </span>
        <TempTijdBadges warm={r._warm} min={r._bereidMin} size={11.5} />
        {r._mist != null && <InHuisBadge m={{ inHuis: r._mist.length === 0, mist: r._mist }} size={10.5}
          mistLabel={halen ? 'halen' : 'mist'} />}
      </div>
      {/* Waarom dit de beste keuze is voor wat je vandaag nog nodig hebt */}
      {reden && (
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 6, marginTop: 10,
          fontSize: 11.5, color: 'var(--forest-700)', lineHeight: 1.35 }}>
          <MIcon name="check" size={13} sw={2.4} color="var(--forest-600)" style={{ marginTop: 1, flex: '0 0 auto' }} />
          <span>{reden}</span>
        </div>
      )}
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginTop: 12 }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4,
          fontSize: 12.5, fontWeight: 600, color: 'var(--forest-700)' }}>
          Bekijk & eet <MIcon name="chevron" size={14} color="var(--forest-600)" />
        </span>
        {/* Span i.p.v. button (we zitten al ín een <button>); stopPropagation
            houdt de tik weg van "openen" — dit zet alleen in je plan. */}
        {onPlan && (
          <span className="tap" role="button" onClick={(e) => { e.stopPropagation(); onPlan(r); }}
            title={planAantal ? 'Nog een portie in je plan' : 'Zet in je plan — kijken wat het doet, nog niet loggen'}
            style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 12.5, fontWeight: 600,
              color: 'var(--forest-700)', border: '1px solid ' + (planAantal ? 'var(--forest-300)' : 'var(--m-line-2)'),
              background: planAantal ? 'var(--forest-100)' : 'var(--m-card)', borderRadius: 999,
              padding: '5px 11px', cursor: 'pointer' }}>
            {planAantal
              ? <span className="num">{planAantal}× in je plan</span>
              : <React.Fragment><MIcon name="plus" size={13} sw={2.2} color="var(--forest-600)" /> In je plan</React.Fragment>}
          </span>
        )}
      </div>
    </button>
  );
}

// De haal-tip: één gerecht dat je nét niet kunt maken, met "halen:" als
// mini-boodschappenlijstje. Dit is de tegenhanger van het in-huis-filter —
// zonder deze kaart zie je nooit waarvoor het de moeite is om te winkelen.
function HaalTipBlok({ r, hint, onOpen, onFav, onVerberg, onPlan, planAantal }) {
  return (
    <div style={{ margin: '2px 0 12px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 6, flexWrap: 'wrap' }}>
        <MIcon name="bag" size={13} color="var(--forest-600)" />
        <span className="m-eyebrow" style={{ color: 'var(--forest-600)' }}>Voor je boodschappen</span>
        <span style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>{hint}</span>
      </div>
      <PoolKaart r={r} onOpen={onOpen} onFav={onFav} onVerberg={onVerberg}
        onPlan={onPlan} planAantal={planAantal} toonTelling={false} halen={true} />
    </div>
  );
}

// Categorie-tegels: de ingang tot je maaltijden. De pool groeit met elke
// "bedenk 6" door, en een platte lijst van tientallen kaarten helpt je niet
// kiezen — bladeren door álles hoort thuis in "Alle maaltijden". Hier kies je
// eerst een richting ("snel klaar", "past nu"), mét het aantal erbij zodat je
// ziet of het de moeite is. Tegels met 0 treffers tonen we niet.
function CategorieTegel({ c, onClick }) {
  return (
    <button className="tap m-cat-tegel" onClick={() => onClick(c.key)} title={c.hint || c.label}>
      <span className="m-cat-tegel-kop">
        {c.stip
          ? <span className={'m-stip ' + c.stip}></span>
          : <MIcon name={c.icon || 'bowl'} size={14} color={c.kleur || 'var(--forest-600)'} />}
        <span className="m-cat-tegel-naam">{c.label}</span>
      </span>
      <span className="m-cat-tegel-num num">{c.aantal}</span>
    </button>
  );
}

function CategorieTegels({ categorieen, onKies }) {
  return (
    <div style={{ marginBottom: 12 }}>
      <div className="m-eyebrow" style={{ marginBottom: 8 }}>Waar zoek je naar?</div>
      <div className="m-cat-grid">
        {categorieen.map((c) => <CategorieTegel key={c.key} c={c} onClick={onKies} />)}
      </div>
    </div>
  );
}

// Macro-sturing: wat heb je vandaag nog nodig, en stuur er direct op. De balk
// laat per macro zien hoeveel er nog te gaan is; tikken opent de gerechten die
// er rijk aan zijn, gesorteerd op hoeveel ze van dát gat dekken. De macro die
// het verst achterloopt staat vooraan en is gemarkeerd.
function MacroSturing({ macros, onKies }) {
  if (!macros.length) return null;
  return (
    <div style={{ marginBottom: 12 }}>
      <div className="m-eyebrow" style={{ marginBottom: 8 }}>Waar stuur je op?</div>
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
        {macros.map((m) => (
          <button key={m.key} className="tap m-macro-stuur" onClick={() => onKies(m.categorie)}
            title={'Toon maaltijden die veel ' + m.label.toLowerCase() + ' leveren'}
            style={m.stuurt ? { borderColor: m.kleur, background: 'var(--m-card)' } : undefined}>
            <span className="m-macro-stuur-punt" style={{ background: m.kleur }}></span>
            <span style={{ fontWeight: 600 }}>{m.label}</span>
            <span className="num" style={{ color: 'var(--ink-2)' }}>
              nog {Math.round(m.gap)} g
            </span>
            {m.stuurt && <span className="m-macro-stuur-vlag">loopt achter</span>}
          </button>
        ))}
      </div>
    </div>
  );
}

// Dagkompas: wat past er vandaag nog — als voelbare pills (grootte-stip +
// aantal), niet alleen tekst. Puur informatief — jij kiest; het kompas stuurt
// alleen de sortering en de "past nu"-markering op de kaarten.
function KompasNote({ kompas, restKcal, eiwitGap, eiwitFocus, koolGap, koolFocus }) {
  if (kompas.klaar) {
    return (
      <div className="m-kompas is-klaar">
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 9 }}>
          <MIcon name="check" size={15} color="var(--forest-600)" style={{ marginTop: 1, flex: '0 0 auto' }} />
          <span>Je zit (vrijwel) aan je doel van vandaag — goed bezig. Trek in iets? Kies een hapje.</span>
        </div>
      </div>
    );
  }
  const uren = Math.round((kompas.eetVensterMin / 60) * 2) / 2;
  const urenLabel = kompas.eetVensterMin <= 0 ? 'laat op de dag'
    : '±' + String(uren).replace('.', ',') + 'u eettijd';
  // Grootte-mix als pills, groot → klein (zelfde stippen als op de kaarten).
  const telling = {};
  kompas.momenten.forEach((m) => { telling[m.klasse.key] = (telling[m.klasse.key] || 0) + 1; });
  const pills = window.pool.GROOTTES.slice().reverse()
    .filter((g) => telling[g.key])
    .map((g) => ({ key: g.key, tekst: telling[g.key] + '× ' + g.ev }));
  return (
    <div className="m-kompas">
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 9 }}>
        <MIcon name="spark" size={15} color="var(--forest-500)" style={{ marginTop: 1, flex: '0 0 auto' }} />
        <span>
          Nog <strong className="num">{fmt(restKcal)} kcal</strong> · {urenLabel} — dit past er nog:
          {/* Macro-coaching: welk gat is nu het grootst? Past het gat niet meer
              in je restbudget (laat op de dag), dan noemen we geen aantallen
              meer — anders vraagt de app je 2× je restdag te eten. Dan alleen
              nog de richting: laat wát er nog bij past die macro zijn. */}
          {(eiwitFocus || koolFocus) && (() => {
            const kost = (eiwitFocus ? eiwitGap * 4 : 0) + (koolFocus ? koolGap * 4 : 0);
            const past = kost <= restKcal;
            const wat = eiwitFocus && koolFocus ? 'eiwit en koolhydraten'
              : eiwitFocus ? 'eiwit' : 'koolhydraten';
            return (
              <React.Fragment>{' '}<span style={{ color: 'var(--forest-700)', fontWeight: 600 }}>
                {!past
                  ? 'je loopt achter op ' + wat + ' — laat wat er nog bij past vooral dát zijn.'
                  : eiwitFocus && koolFocus
                    ? 'en nog ' + eiwitGap + 'g eiwit én ' + koolGap + 'g koolhydraten te gaan — kies iets dat allebei levert.'
                    : eiwitFocus
                      ? 'en nog ' + eiwitGap + 'g eiwit te gaan, dus eiwitrijk is slim.'
                      : 'en nog ' + koolGap + 'g koolhydraten te gaan — brood, pasta, rijst of fruit is nu slim.'}
              </span></React.Fragment>
            );
          })()}
        </span>
      </div>
      <div className="m-kompas-pills">
        {pills.map((p) => (
          <span key={p.key} className="m-kompas-pill">
            <span className={'m-stip ' + p.key}></span>{p.tekst}
          </span>
        ))}
      </div>
    </div>
  );
}

// ── Bedenk-sheet ───────────────────────────────────────────────────────────
// Stuur de AI in plaats van te hopen: zeg welke ingrediënten er zeker in
// moeten, hoe streng "in huis" telt en (vrij veld) welke stijl je wilt. Zo
// krijg je gerichte, bruikbare ideeën en hoef je minder door de lijst te
// spitten. De resultaten landen zoals altijd bovenaan onder "Net bedacht".
function BedenkSheet({ werklunch, voorstelMoet, voorstelModus, onClose, onBedenk }) {
  const { useState } = React;
  const [wens, setWens] = useState('');
  const [moet, setMoet] = useState(voorstelMoet || []);
  const [invoer, setInvoer] = useState('');
  // Default 'vrij': variatie wint het van "alles al in huis" — wat je niet
  // hebt staat gewoon als "halen" op de kaart. Strikt/beperkt blijven er
  // voor wie nú iets wil kunnen maken; de voorraad-hints openen daar direct
  // in, want die beloven expliciet iets uit je voorraad.
  const [modus, setModus] = useState(voorstelModus || 'vrij');
  const input = { width: '100%', boxSizing: 'border-box', padding: '10px 12px', fontSize: 14,
    border: '1px solid var(--m-line-2)', borderRadius: 11, background: 'var(--m-card)', fontFamily: 'inherit' };
  const voegToe = () => {
    const t = invoer.trim();
    if (t && !moet.some((m) => m.toLowerCase() === t.toLowerCase())) setMoet([...moet, t]);
    setInvoer('');
  };
  const MODI = [
    { key: 'strikt',  label: '✓ Alles in huis', hint: 'Alleen ingrediënten uit je voorraad — direct te maken.' },
    { key: 'beperkt', label: 'Max 2 nieuwe',    hint: 'Hooguit 2 te kopen ingrediënten per gerecht.' },
    { key: 'vrij',    label: 'Vrij',            hint: 'Mag ook met dingen die je nog niet hebt — die haal je gewoon.' },
  ];
  const actieveModus = MODI.find((m) => m.key === modus);
  return (
    <Sheet title={werklunch ? 'Werklunches bedenken' : 'Maaltijden bedenken'} onClose={onClose}>
      <label className="m-eyebrow" style={{ display: 'block', marginBottom: 8 }}>Wat moet er zeker in? (optioneel)</label>
      <div style={{ display: 'flex', gap: 7, marginBottom: moet.length ? 8 : 16 }}>
        <input value={invoer} onChange={(e) => setInvoer(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && voegToe()}
          placeholder={werklunch ? 'bv. hüttenkäse, wraps…' : 'bv. kipfilet, rijst…'}
          style={{ ...input, flex: 1 }} />
        <button className="m-btn m-btn-ghost tap" onClick={voegToe} disabled={!invoer.trim()}
          style={{ fontSize: 13, padding: '9px 13px', flex: '0 0 auto' }}>
          <MIcon name="plus" size={14} color="var(--forest-600)" /> Voeg toe
        </button>
      </div>
      {moet.length > 0 && (
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 16 }}>
          {moet.map((m) => (
            <span key={m} className="m-chip is-active" style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
              {m}
              <button className="tap" onClick={() => setMoet(moet.filter((x) => x !== m))} title="Weghalen"
                style={{ padding: 0, border: 'none', background: 'none', cursor: 'pointer', display: 'flex' }}>
                <MIcon name="close" size={11} color="currentColor" />
              </button>
            </span>
          ))}
        </div>
      )}

      <label className="m-eyebrow" style={{ display: 'block', marginBottom: 8 }}>In huis?</label>
      <div style={{ display: 'flex', gap: 7, flexWrap: 'wrap', marginBottom: 6 }}>
        {MODI.map((m) => (
          <button key={m.key} className={'m-chip' + (modus === m.key ? ' is-active' : '')}
            onClick={() => setModus(m.key)} title={m.hint} style={{ padding: '7px 13px' }}>
            {m.label}
          </button>
        ))}
      </div>
      <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginBottom: 16, lineHeight: 1.4 }}>
        {actieveModus ? actieveModus.hint : ''}
      </div>

      <label className="m-eyebrow" style={{ display: 'block', marginBottom: 8 }}>Extra wens (optioneel)</label>
      <input value={wens} onChange={(e) => setWens(e.target.value)}
        placeholder="bv. pittig, Italiaans, snel klaar…"
        style={{ ...input, marginBottom: 12 }} />

      <div style={{ fontSize: 11.5, color: 'var(--ink-3)', lineHeight: 1.5, marginBottom: 14 }}>
        Moderis vermijdt automatisch ingrediënten die al vaak in je pool zitten
        en stuurt op de macro's die bij jou achterblijven (bv. koolhydraten).
      </div>

      <button className="m-btn m-btn-primary tap"
        onClick={() => onBedenk({ wens: wens.trim(), moet_bevatten: moet, voorraad_modus: modus,
          ...(werklunch ? { werklunch: true } : {}) })}
        style={{ width: '100%', fontSize: 14.5, padding: '13px' }}>
        <MIcon name="spark" size={16} color="var(--on-forest)" />
        {werklunch ? 'Bedenk 6 werklunches' : 'Bedenk 6 maaltijden'}
      </button>
    </Sheet>
  );
}

// ── Dagplanner ─────────────────────────────────────────────────────────────
// Mini-macrobalk met twee lagen: wat je al at (vol) + wat er in je plan zit
// (dezelfde kleur, doorschijnend). Zo zie je per macro of het plan je bij je
// doel brengt of eroverheen schiet.
function PlanMacroBalkje({ label, have, plan, goal, color }) {
  const pct = (x) => (goal > 0 ? Math.min(100, (x / goal) * 100) : 0);
  const over = goal > 0 && have + plan > goal + 2;
  return (
    <div style={{ flex: 1, minWidth: 0 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', gap: 4, marginBottom: 3 }}>
        <span style={{ fontSize: 10.5, fontWeight: 600, color: 'var(--ink-2)' }}>{label}</span>
        <span className="num" style={{ fontSize: 10.5, color: over ? '#8a5a2b' : 'var(--ink-3)', whiteSpace: 'nowrap' }}>
          {Math.round(have + plan)}<span style={{ opacity: 0.7 }}>/{Math.round(goal)}</span>
        </span>
      </div>
      <div className="m-track" style={{ height: 5, position: 'relative' }}>
        <div style={{ position: 'absolute', top: 0, bottom: 0, left: 0, width: pct(have + plan) + '%',
          background: color, opacity: 0.35, borderRadius: 999 }}></div>
        <div style={{ position: 'absolute', top: 0, bottom: 0, left: 0, width: pct(have) + '%',
          background: color, borderRadius: 999 }}></div>
      </div>
    </div>
  );
}

// Het plan-paneel: je gekozen maaltijden + wat ze sámen met je dag doen.
// Puur een kijkglas — er wordt pas gelogd als je zelf op de log-knop tikt.
// Met het "Mee naar werk"-filter aan heet dit je meeneem-lijstje.
function PlanPaneel({ items, totals, meeneem, busy, onAantal, onWissen, onLog }) {
  const stepKnop = { width: 26, height: 26, padding: 0, border: 'none', background: 'transparent',
    cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' };
  const som = (k) => items.reduce((s, i) => s + (Number(i[k]) || 0) * (i.aantal || 1), 0);
  // Haallijstje: alles wat je voor dit plan nog niet in huis hebt, ontdubbeld.
  // Vooral goud bij "mee naar werk": plan je bammetjes en zie meteen welk
  // beleg je nog moet halen.
  const halen = [...new Set(items.flatMap((i) => i.mist || []))];
  const planKcal = Math.round(som('kcal'));
  const doel = Math.max(1, Math.round(totals?.doel || 0));
  const gegeten = Math.round(totals?.gegeten || 0);
  const naRest = Math.round((totals?.rest || 0) - planKcal);
  const eatenPct = Math.min(100, (gegeten / doel) * 100);
  const totPct = Math.min(100, ((gegeten + planKcal) / doel) * 100);
  const m = totals?.macros || {};
  const nItems = items.reduce((s, i) => s + (i.aantal || 1), 0);
  return (
    <div className="m-planpaneel">
      <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 8 }}>
        <MIcon name={meeneem ? 'bag' : 'utensils'} size={14} color="var(--forest-600)" />
        <span className="m-eyebrow" style={{ color: 'var(--forest-600)' }}>
          {meeneem ? 'Mee naar werk' : 'Jouw plan'}
        </span>
        <span style={{ flex: 1 }}></span>
        <span style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>alleen kijken — nog niets gelogd</span>
      </div>
      {items.map((i) => (
        <div key={i.id} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 0' }}>
          <span style={{ flex: 1, minWidth: 0, fontSize: 13, fontWeight: 600,
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{i.naam}</span>
          <span className="num" style={{ fontSize: 11.5, color: 'var(--ink-3)', flex: '0 0 auto' }}>
            {Math.round((i.kcal || 0) * (i.aantal || 1))} kcal
          </span>
          <div style={{ display: 'flex', alignItems: 'center', flex: '0 0 auto',
            border: '1px solid var(--m-line)', borderRadius: 999 }}>
            <button className="tap" style={stepKnop} onClick={() => onAantal(i.id, -1)}
              title={(i.aantal || 1) === 1 ? 'Uit je plan' : 'Eén portie minder'}>
              <MIcon name={(i.aantal || 1) === 1 ? 'close' : 'minus'} size={11} sw={2.2} color="var(--ink-2)" />
            </button>
            <span className="num" style={{ fontSize: 12, fontWeight: 700, minWidth: 22, textAlign: 'center' }}>
              {i.aantal || 1}×
            </span>
            <button className="tap" style={stepKnop} onClick={() => onAantal(i.id, 1)} title="Eén portie extra">
              <MIcon name="plus" size={11} sw={2.2} color="var(--ink-2)" />
            </button>
          </div>
        </div>
      ))}
      {/* Samen + effect op je energiebalk: gegeten (vol) + plan (gearceerd) */}
      <div style={{ marginTop: 8 }}>
        <div style={{ fontSize: 11.5, fontWeight: 600, color: 'var(--ink-2)', marginBottom: 5 }}>
          Samen <span className="num">{fmt(planKcal)}</span> kcal
          · E {Math.round(som('eiwitten'))} K {Math.round(som('koolhydraten'))} V {Math.round(som('vetten'))}
        </div>
        <div className="m-track" style={{ height: 8, position: 'relative' }}>
          <div style={{ position: 'absolute', top: 0, bottom: 0, left: 0, width: totPct + '%', borderRadius: 999,
            background: 'repeating-linear-gradient(135deg, rgba(139,194,74,0.55) 0 5px, rgba(139,194,74,0.22) 5px 10px)' }}></div>
          <div style={{ position: 'absolute', top: 0, bottom: 0, left: 0, width: eatenPct + '%',
            background: 'var(--forest-700)', borderRadius: 999 }}></div>
        </div>
        <div style={{ fontSize: 11.5, marginTop: 5, fontWeight: 600,
          color: naRest >= 0 ? 'var(--forest-700)' : '#8a5a2b' }}>
          {naRest >= 0
            ? <React.Fragment>Daarna nog <span className="num">{fmt(naRest)}</span> kcal over vandaag</React.Fragment>
            : <React.Fragment><span className="num">{fmt(-naRest)}</span> kcal boven je dagdoel</React.Fragment>}
        </div>
      </div>
      <div style={{ display: 'flex', gap: 10, marginTop: 8 }}>
        <PlanMacroBalkje label="Eiwit"  have={m.protein?.have || 0} plan={som('eiwitten')}     goal={m.protein?.goal || 0} color="var(--macro-protein)" />
        <PlanMacroBalkje label="Koolh." have={m.carb?.have || 0}    plan={som('koolhydraten')} goal={m.carb?.goal || 0}    color="var(--macro-carb)" />
        <PlanMacroBalkje label="Vet"    have={m.fat?.have || 0}     plan={som('vetten')}       goal={m.fat?.goal || 0}     color="var(--macro-fat)" />
      </div>
      {/* Haallijstje voor dit plan — even langs de super en je kunt alles maken */}
      {halen.length > 0 && (
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 6, marginTop: 9,
          fontSize: 11.5, lineHeight: 1.45, color: '#8a5a2b' }}>
          <MIcon name="bag" size={13} color="#8a5a2b" style={{ marginTop: 1, flex: '0 0 auto' }} />
          <span><strong>Nog halen:</strong> {halen.join(', ')}</span>
        </div>
      )}
      <div style={{ display: 'flex', gap: 8, marginTop: 11 }}>
        <button className="m-btn m-btn-ghost tap" onClick={onWissen} disabled={busy}
          style={{ fontSize: 12.5, padding: '9px 13px' }}>
          Wissen
        </button>
        <button className="m-btn m-btn-primary tap" onClick={onLog} disabled={busy}
          style={{ flex: 1, fontSize: 13, padding: '9px 13px', ...(busy ? { opacity: 0.6, cursor: 'wait' } : {}) }}>
          {busy ? <Spinner size={14} /> : <MIcon name="check" size={14} sw={2.4} color="var(--on-forest)" />}
          {busy ? 'Loggen…' : (nItems === 1 ? 'Dit eet ik — log het' : 'Dit eet ik — log alles (' + nItems + ')')}
        </button>
      </div>
    </div>
  );
}

function PoolKiezen({ actief, voorraad, totals, profiel, logs, onKies, onEetPlan, onNaarRecepten, showToast }) {
  const { useState, useEffect, useMemo } = React;
  const [recepten, setRecepten] = useState(null);   // null = nog niet geladen
  const [historie, setHistorie] = useState([]);     // logs laatste 7 dagen (variatie)
  const [laden, setLaden] = useState(false);
  const [grootte, setGrootte] = useState('alle');
  // Standaard UIT: "Direct te maken" is nu een eigen categorie-tegel, en zo
  // kloppen de aantallen op de tegels met de hele pool i.p.v. met een
  // onzichtbaar voorgefilterde deelverzameling.
  const [inHuis, setInHuis] = useState(false);
  const [categorie, setCategorie] = useState(null);   // open categorie (tegel) of null
  const [eiwitrijk, setEiwitrijk] = useState(false);
  const [koolrijk, setKoolrijk] = useState(false);
  const [temp, setTemp] = useState('alle');   // alle | warm | koud
  const [snel, setSnel] = useState(false);     // alleen snel klaar (≈ ≤ 12 min)
  const [nieuwOnly, setNieuwOnly] = useState(false);   // alleen "nieuw voor jou"
  const [meeneem, setMeeneem] = useState(false);   // mee naar werk: koud of lunch/meeneem-tag
  const [sorteer, setSorteer] = useState('slim');  // slim | vaak | zelden (gegeten-teller)
  const [zoek, setZoek] = useState('');
  const [plan, setPlan] = useState([]);            // dagplanner: [{id, naam, macro's, aantal}]
  const [planBusy, setPlanBusy] = useState(false);
  const [vullen, setVullen] = useState(false);   // pool-vul (AI/starterset) bezig
  const [netBedacht, setNetBedacht] = useState([]);   // namen van de laatst bedachte gerechten
  const [bedenkOpen, setBedenkOpen] = useState(null);   // Bedenk-sheet: null | { moet?: [] }

  const ververst = React.useRef(new Set());     // deze sessie al ververst
  const verversBezig = React.useRef(false);

  // Proactief macro's verversen: de server-pipeline is de bron van de
  // waarheid, maar een recept dat al een week niet is aangeraakt kan
  // verouderde waarden dragen (database verbeterd, voorraad veranderd).
  // Ververs daarom stilletjes een paar van de oudste per keer — de kaarten
  // schuiven zo vanzelf naar de actuele werkelijkheid. Alleen recepten MET
  // ingrediënten (zonder zou herbereken ze eerst door AI laten schatten).
  const verversOudeMacros = async (lijst) => {
    if (verversBezig.current) return;
    const week = 7 * 24 * 60 * 60 * 1000;
    const oud = (lijst || [])
      .filter((r) => Number(r.beoordeling) >= 0
        && (r.ingredienten || []).length
        && !ververst.current.has(r.id)
        && (!r.bijgewerkt_op || (Date.now() - new Date(r.bijgewerkt_op).getTime()) > week))
      .sort((a, b) => new Date(a.bijgewerkt_op || 0) - new Date(b.bijgewerkt_op || 0))
      .slice(0, 3);
    if (!oud.length) return;
    verversBezig.current = true;
    try {
      for (const r of oud) {
        ververst.current.add(r.id);
        const vers = await api.updateRecept(r.id, { herbereken: true });
        if (vers && vers.id) setRecepten((L) => (L || []).map((x) => (x.id === vers.id ? vers : x)));
      }
    } catch (_) { /* stil — volgende keer opnieuw */ }
    verversBezig.current = false;
  };

  const herlaad = async () => {
    if (laden) return;
    setLaden(true);
    const [recR, histR] = await Promise.allSettled([api.recepten(), api.logsHistorie(7)]);
    if (recR.status === 'fulfilled') {
      setRecepten(recR.value || []);
      verversOudeMacros(recR.value || []);   // bewust niet awaiten — achtergrond
    } else if (recepten === null) showToast('Maaltijden laden mislukt: ' + recR.reason.message);
    if (histR.status === 'fulfilled') setHistorie(histR.value || []);
    setLaden(false);
  };

  // (Her)laden zodra het tabblad actief wordt — recepten/logs kunnen intussen
  // gewijzigd zijn en de query is goedkoop.
  useEffect(() => {
    if (actief) herlaad();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [actief]);

  // Pool aanvullen — starterset (gratis, lokaal) of een AI-batch van 6
  // gerechten vanuit de voorraad. Expliciete gebruikersactie, nooit stiekem.
  const vulAan = async (opts) => {
    if (vullen) return;
    setVullen(true);
    try {
      const r = await api.poolVul(opts || {});
      const over = (r.overgeslagen || []).length;
      showToast(r.aantal > 0
        ? r.aantal + ' nieuwe ' + (r.aantal === 1 ? 'maaltijd' : 'maaltijden') + ' toegevoegd — zie "Net bedacht" bovenaan'
        : 'Geen nieuwe' + (over ? ' — Moderis kende deze al' : ' — probeer het nog eens'));
      await herlaad();
      // Zet ze bovenaan in "Net bedacht" (buiten filters/ranking) zodat je
      // precies ziet wat er is toegevoegd i.p.v. dat ze wegzakken.
      if (r.toegevoegd && r.toegevoegd.length) setNetBedacht(r.toegevoegd);
    } catch (err) { showToast('Aanvullen mislukt: ' + err.message); }
    setVullen(false);
  };

  // Net-bedacht-markering opheffen zodra je ergens naartoe navigeert (een
  // categorie in of zoeken) — daar hoort een tijdelijke sectie van zojuist
  // bedachte gerechten niet boven te blijven staan. Bij het terugkeren naar
  // het overzicht juist NIET wissen: dan wil je nog kunnen zien wat erbij is
  // gekomen. (Daarom staan de losse chips hier niet meer in: die bestaan
  // alleen nog binnen een categorie, waar de markering al weg is.)
  useEffect(() => {
    if (categorie || zoek) setNetBedacht([]);
  }, [categorie, zoek]);

  // ── Dagplanner ──
  // + op een kaart: in je plan (of een portie extra). Alles blijft lokaal —
  // pas "Log alles" schrijft het naar je logs (via App.eetPlan).
  const planToe = (r) => {
    setPlan((P) => {
      const idx = P.findIndex((x) => x.id === r.id);
      if (idx >= 0) return P.map((x, i) => (i === idx ? { ...x, aantal: (x.aantal || 1) + 1 } : x));
      return [...P, {
        id: r.id, naam: r.naam, aantal: 1,
        kcal: Math.round(r.kcal || 0), eiwitten: Math.round(r.eiwitten || 0),
        koolhydraten: Math.round(r.koolhydraten || 0), vetten: Math.round(r.vetten || 0),
        ingredienten: (r.ingredienten || []).map((i) => ({ naam: i.naam, hoeveelheid_g: i.hoeveelheid_g })),
        mist: r._mist || [],   // wat je hiervoor nog moet halen (haallijstje in het paneel)
      }];
    });
  };
  const planAantal = (id, delta) => {
    setPlan((P) => P
      .map((x) => (x.id === id ? { ...x, aantal: (x.aantal || 1) + delta } : x))
      .filter((x) => (x.aantal || 1) > 0));
  };
  const inPlan = {};   // recept-id → aantal (voor de kaart-knopjes)
  plan.forEach((p) => { inPlan[p.id] = p.aantal || 1; });

  const logPlan = async () => {
    if (planBusy || !plan.length || !onEetPlan) return;
    setPlanBusy(true);
    const ok = await onEetPlan(plan);
    if (ok) { setPlan([]); herlaad(); }   // herlaad ververst de gegeten-tellers
    setPlanBusy(false);
  };

  const restKcal = Math.max(0, Math.round(totals?.rest || 0));
  const eiwitGap = Math.max(0, Math.round((totals?.macros?.protein?.goal || 0) - (totals?.macros?.protein?.have || 0)));
  const koolGap = Math.max(0, Math.round((totals?.macros?.carb?.goal || 0) - (totals?.macros?.carb?.have || 0)));

  // Stuurt een macro vandaag echt de keuze? Zelfde drempels als scoreRecept en
  // het dagkompas — zo zeggen de ranking, de categorie-volgorde en de uitleg
  // onder de aanrader alle drie hetzelfde.
  const eiwitStuurt = restKcal > 0 && eiwitGap / restKcal >= 0.08 && eiwitGap >= 15;
  const koolStuurt = restKcal > 0 && koolGap / restKcal >= 0.13 && koolGap >= 30;

  // Dagkompas: wat past er nog, en wat is het budget voor je vólgende maaltijd
  // (daar rankt de lijst op — niet op de hele restdag).
  const nuMinuten = (() => { const d = new Date(); return d.getHours() * 60 + d.getMinutes(); })();
  const kompas = window.pool.dagkompas({
    restKcal, nuMin: nuMinuten, eindEten: window.pool.eindEtenMin(profiel),
  });
  // (eiwit-/kool-focus voor de kompas-tekst wordt op het dashboard berekend
  //  en aan KompasNote doorgegeven — hier is alleen de gap zelf nodig.)

  // Recent gegeten (variatie-demping in de ranking): historie (7 dagen) plus
  // de live logs van vandaag — zo zakt iets wat je nét at direct weg.
  const recent = useMemo(() => {
    const s = new Set();
    [...(historie || []), ...(logs || [])].forEach((l) => {
      if (l.naam) s.add(String(l.naam).trim().toLowerCase());
      if (l.recept_id) s.add(String(l.recept_id));
    });
    return s;
  }, [historie, logs]);

  // Kern-ingrediënten van de laatste ~3 dagen (ingrediënt-variatie): "weer
  // kip" zakt óók als de kip gisteren in een ander gerecht zat.
  const recentKern = useMemo(
    () => window.pool.recentGegetenKern([...(historie || []), ...(logs || [])], recepten || [], 3),
    [historie, logs, recepten]);

  // Pool verrijken: dekking, grootte, score — alles lokaal. Geen ingrediënten
  // bekend? Dan is de dekking onbekend (mist=null) — neutraal in de ranking.
  const verrijkt = useMemo(() => {
    return (recepten || [])
      .filter((r) => Number(r.beoordeling) >= 0)
      .map((r) => {
        const mist = (r.ingredienten || []).length
          ? window.pool.ontbrekendeIngredienten(r.ingredienten, voorraad || [])
          : null;
        const kcal = Number(r.kcal) || 0;
        return {
          ...r, _mist: mist,
          _grootte: window.pool.grootteKlasse(r.kcal),
          _eiwitrijk: window.pool.isEiwitrijk(r),
          _koolrijk: window.pool.isKoolhydraatrijk(r),
          _warm: window.pool.isWarm(r),
          _bereidMin: window.pool.bereidingsMinuten(r),
          // Kaart-bewaking: past dit nog in je dag / bij je volgende maaltijd?
          // Ook bij een restbudget van 0 (je zit aan/over je doel) is "+N boven
          // je dag" de eerlijke boodschap — juist dan.
          _teVeel: kcal > restKcal + 100 ? kcal - restKcal : 0,
          _pastNu: kompas.volgendBudget > 0 && kcal <= restKcal + 100
            && kcal / kompas.volgendBudget >= 0.6 && kcal / kompas.volgendBudget <= 1.35,
          _score: window.pool.scoreRecept(r, {
            mist, restKcal, eiwitGap, koolGap, recent, recentKern,
            volgendBudget: kompas.volgendBudget,
            // Mee-naar-werk: ontbrekend beleg is haalbaar → dekking telt mild.
            mistZacht: meeneem,
          }),
        };
      });
  }, [recepten, voorraad, restKcal, eiwitGap, koolGap, recent, recentKern, kompas.volgendBudget, meeneem]);

  // Hoeveel kun je nú echt maken? (drijft de "vul aan"-hint)
  const inHuisTeller = verrijkt.filter((r) => r._mist && r._mist.length === 0).length;
  // Hoeveel "nieuw voor jou" (nog nooit gegeten/beoordeeld)? — drijft het Nieuw-filter.
  const nieuwCount = verrijkt.filter((r) => window.pool.isNieuwVoorJou(r)).length;

  // "Mee naar werk": expliciet als meeneem/lunch gemarkeerd, óf koud — dat
  // dekt bammetjes, wraps en salades ook als de tags (nog) niet gezet zijn.
  const isMeeneem = (r) => {
    const mom = r.momenten || [];
    return mom.includes('meeneem') || mom.includes('lunch') || r._warm === false;
  };


  // Punt 3 — voorraad-bewustzijn: welke producten heb je in huis waar GEEN enkel
  // gerecht in je pool iets mee doet? Verander je je voorraad (nieuw ingekocht),
  // dan verschijnen die hier en biedt Moderis aan er verse ideeën bij te
  // bedenken — de pool "denkt dan opnieuw na" over wat bij je voorraad past.
  const ongebruikt = useMemo(() => {
    const inHuisV = (voorraad || []).filter((v) => !(v.hoeveelheid_g != null && Number(v.hoeveelheid_g) === 0));
    const ingWoorden = [];
    (recepten || []).forEach((r) => (r.ingredienten || []).forEach((i) => {
      if (i && i.naam) ingWoorden.push(...window.pool.woorden(i.naam));
    }));
    return inHuisV
      .filter((v) => {
        const vw = window.pool.woorden(v.naam);
        if (!vw.length || window.pool.isBasic(vw)) return false;
        return !vw.some((w) => ingWoorden.some((iw) => window.pool.woordMatch(w, iw)));
      })
      .map((v) => v.naam);
  }, [voorraad, recepten]);

  // Zoeken: op naam én op ingrediënt, met dezelfde tolerante woord-matching
  // als de voorraad-dekking — "tomaat" vindt dus ook gerechten met "tomaten",
  // "wraps" vindt "wrap". Matcht een gerecht alléén via een ingrediënt, dan
  // onthouden we welk ("_zoekVia") zodat de kaart kan tonen wáárom het matcht.
  const zoekW = zoek.trim().toLowerCase();
  const zoekWoorden = zoekW ? window.pool.woorden(zoekW) : [];
  const woordenMatchen = (doelW) =>
    zoekWoorden.length > 0 && zoekWoorden.every((qw) => doelW.some((dw) => window.pool.woordMatch(qw, dw)));
  const zoekMatch = (r) => {
    if (!zoekW) return { ok: true, via: null };
    if (r.naam.toLowerCase().includes(zoekW) || woordenMatchen(window.pool.woorden(r.naam))) {
      return { ok: true, via: null };
    }
    for (const i of (r.ingredienten || [])) {
      const inaam = String(i.naam || '');
      if (inaam.toLowerCase().includes(zoekW) || woordenMatchen(window.pool.woorden(inaam))) {
        return { ok: true, via: inaam };
      }
    }
    return { ok: false, via: null };
  };

  // Zojuist bedachte gerechten — apart, boven de filters/ranking getoond.
  const netBedachtSet = new Set(netBedacht.map((n) => String(n).trim().toLowerCase()));
  const netBedachtRecepten = netBedachtSet.size
    ? verrijkt.filter((r) => netBedachtSet.has(String(r.naam).trim().toLowerCase()))
    : [];

  // ── Categorieën ────────────────────────────────────────────────────────
  // De basis waarop geteld wordt: alles wat niet al in "Net bedacht" staat.
  // Bewust zónder de filter-chips: die zitten binnen een categorie, dus een
  // teller die daarop zou meebewegen zou onverklaarbaar springen.
  // Tellen doen we over de HELE pool, inclusief wat er net bedacht is: een
  // tegel openen wist die markering (zie de effect hierboven), dus de lijst die
  // je dan krijgt bevat ze ook. Zou je ze hier weglaten, dan belooft de tegel
  // een ander aantal dan hij levert — en zou "Nieuw voor jou" juist de verse
  // gerechten niet tellen.
  const basisPool = verrijkt;

  const categorieen = useMemo(() => {
    const maak = (key, label, test, extra) => ({ key, label, test, ...(extra || {}) });
    const macro = [
      maak('kool', 'Koolhydraatrijk', (r) => r._koolrijk,
        { icon: 'bowl', hint: 'veel koolhydraten per calorie', sorteerOp: 'koolhydraten', gap: koolGap, chip: 'kool' }),
      maak('eiwit', 'Eiwitrijk', (r) => r._eiwitrijk,
        { icon: 'bowl', hint: 'veel eiwit per calorie', sorteerOp: 'eiwitten', gap: eiwitGap, chip: 'eiwit' }),
    ];
    // De macro die achterloopt komt eerst — dát is waar je vandaag iets aan hebt.
    if (eiwitStuurt && !koolStuurt) macro.reverse();
    const alle = [
      // `chip` = de verfijn-chip(s) die binnen deze categorie niets meer doen.
      // Die verbergen we: ze zouden niets wegfilteren en lezen dan als een
      // kapotte knop — of erger, ze zetten stilletjes een modus om (meeneem
      // schakelt de hele werklunch-flow) zonder dat de lijst verandert.
      maak('nu', 'Past nu', (r) => r._pastNu, { icon: 'spark', hint: 'past bij je volgende eetmoment' }),
      maak('inhuis', 'Direct te maken', (r) => r._mist && r._mist.length === 0,
        { icon: 'check', hint: 'alles ligt in huis', chip: 'inhuis' }),
      maak('snel', 'Snel klaar', (r) => r._bereidMin != null && r._bereidMin <= 15,
        { icon: 'clock', hint: 'binnen ~15 minuten' }),
      ...macro,
      maak('nieuw', 'Nieuw voor jou', window.pool.isNieuwVoorJou,
        { icon: 'spark', negeerInHuis: true, hint: 'nog nooit gegeten', chip: 'nieuw' }),
      maak('meeneem', 'Mee naar werk', isMeeneem,
        { icon: 'bag', meeneem: true, hint: 'broodjes, wraps, koude lunches', chip: 'meeneem' }),
      maak('warm', 'Warm', (r) => r._warm === true, { icon: 'flame', kleur: '#c0704a', chip: 'temp' }),
      maak('koud', 'Koud', (r) => r._warm === false,
        // isMeeneem telt elk koud gerecht al mee, dus de meeneem-chip zou hier
        // alleen stilletjes de werklunch-modus omzetten zonder de lijst te raken.
        { icon: 'snow', kleur: '#4a86c0', chip: ['temp', 'meeneem'] }),
      ...window.pool.GROOTTES.map((g) =>
        maak('g:' + g.key, g.label, (r) => r._grootte.key === g.key,
          { stip: g.key, hint: g.hint, chip: 'grootte' })),
    ];
    // Lege categorieën blijven in de lijst staan (ze worden alleen niet als
    // tegel getoond): zakt je open categorie naar 0 doordat je het laatste
    // gerecht archiveert, dan blijft hij vindbaar en houd je je terug-knop.
    return alle
      .map((c) => ({ ...c, aantal: basisPool.filter(c.test).length }))
      .concat([{ key: 'alles', label: 'Alle maaltijden', test: () => true, icon: 'book',
        aantal: basisPool.length, hint: 'de hele lijst' }]);
    // koolGap/eiwitGap staan er expliciet bij: ze worden als `gap` in de
    // categorie ingebakken en sturen de sortering. Ze lopen vandaag toevallig
    // mee via basisPool, maar op zo'n indirecte koppeling wil je niet leunen.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [basisPool, eiwitStuurt, koolStuurt, koolGap, eiwitGap]);

  const actieveCategorie = categorie ? categorieen.find((c) => c.key === categorie) : null;
  // Verberg de chip die de actieve categorie al afdwingt (zie `chip` hierboven).
  const toonChip = (naam) => {
    const c = actieveCategorie && actieveCategorie.chip;
    return !c || (Array.isArray(c) ? !c.includes(naam) : c !== naam);
  };

  // Macro-sturing op het overzicht: alleen macro's waar vandaag nog een echt
  // gat zit én waarvoor de pool ook iets te bieden heeft. De ondergrenzen zijn
  // dezelfde als in de ranking en het dagkompas (30 g koolhydraten, 15 g
  // eiwit): onder dat gat is sturen geen sturen meer.
  const macroSturing = [
    { key: 'kool', label: 'Koolhydraten', gap: koolGap, drempel: 30, stuurt: koolStuurt,
      kleur: 'var(--macro-carb)', categorie: 'kool' },
    { key: 'eiwit', label: 'Eiwit', gap: eiwitGap, drempel: 15, stuurt: eiwitStuurt,
      kleur: 'var(--macro-protein)', categorie: 'eiwit' },
  // Zit je dag er (vrijwel) op, dan zwijgt het dagkompas al ("goed bezig") —
  // dan hier geen "nog 90 g te gaan" eronder zetten. Zonder deze grens zou een
  // restbudget van 0 elke ratio triviaal halen en zou het scherm je de zwaarste
  // maaltijd uit de pool aanreiken.
  ].filter((m) => restKcal >= 100 && m.gap >= m.drempel
    && categorieen.some((c) => c.key === m.categorie && c.aantal > 0))
    .sort((a, b) => (b.stuurt ? 1 : 0) - (a.stuurt ? 1 : 0));

  // Categorie openen/sluiten. "Mee naar werk" zet de meeneem-modus aan: daar
  // hangt meer aan dan een filter (zachte dekking, "halen"-labels, en de
  // Bedenk-knop maakt dan werklunches). Sluiten zet de verfijn-chips terug,
  // zodat de tegel-aantallen altijd de hele pool beschrijven.
  const kiesCategorie = (key) => {
    const c = categorieen.find((x) => x.key === key);
    setCategorie(key);
    setMeeneem(!!(c && c.meeneem));
    setInHuis(false);
  };
  const resetChips = () => {
    setMeeneem(false);
    setInHuis(false);
    setGrootte('alle');
    setEiwitrijk(false);
    setKoolrijk(false);
    setTemp('alle');
    setSnel(false);
    setNieuwOnly(false);
    setSorteer('slim');
  };
  const sluitCategorie = () => { setCategorie(null); resetChips(); };

  // Alle filters behalve de in-huis-check. De haal-tip hieronder gebruikt
  // dezelfde regels — anders zou hij een gerecht tonen dat niet bij je
  // actieve filters past (bv. "Warm" aan, koude tip eronder).
  const filtersBehalveInHuis = (r) =>
    // Niet dubbel: staat het al in "Net bedacht", laat het daar (niet ook hier).
    !netBedachtSet.has(String(r.naam).trim().toLowerCase())
    // Nieuw-filter: alleen wat je nog nooit at of beoordeelde.
    && (!nieuwOnly || window.pool.isNieuwVoorJou(r))
    && (grootte === 'alle' || r._grootte.key === grootte)
    && (!eiwitrijk || r._eiwitrijk)
    && (!koolrijk || r._koolrijk)
    // Warm/koud: onbekend (geen bereiding) valt weg zodra je hierop filtert.
    && (temp === 'alle' || (temp === 'warm' ? r._warm === true : r._warm === false))
    && (!meeneem || isMeeneem(r))
    && (!snel || (r._bereidMin != null && r._bereidMin <= 12));

  const gefilterd = verrijkt
    .filter(filtersBehalveInHuis)
    // De gekozen categorie is de grofste zeef; de chips verfijnen daarbinnen.
    .filter((r) => !actieveCategorie || actieveCategorie.test(r))
    // Dekking onbekend (geen ingrediënten) blijft zichtbaar bij "in huis" —
    // wegfilteren zou recepten onvindbaar maken; ze ranken al lager. Bij het
    // Nieuw-filter negeren we de in-huis-check (anders zie je juist de verse
    // ideeën niet die nog ingrediënten missen).
    .filter((r) => nieuwOnly || !inHuis || (actieveCategorie && actieveCategorie.negeerInHuis)
      || r._mist == null || r._mist.length <= 2)
    .map((r) => {
      const m = zoekMatch(r);
      return m.ok ? (m.via ? { ...r, _zoekVia: m.via } : r) : null;
    })
    .filter(Boolean)
    .sort((a, b) => {
      // Volgorde-keuze: vaak gegeten eerst (je vertrouwde maaltijden) of juist
      // zelden/nooit (variatie) — anders de slimme ranking.
      const geg = (x) => Number(x.aantal_keer_gebruikt) || 0;
      if (sorteer === 'vaak' && geg(b) !== geg(a)) return geg(b) - geg(a);
      if (sorteer === 'zelden' && geg(a) !== geg(b)) return geg(a) - geg(b);
      // In een macro-categorie stuur je op dié macro: bovenaan staat wat het
      // meeste van je gat dekt. Meer dan het gat levert niets extra's op, dus
      // dat telt niet zwaarder mee. Is het doel al gehaald, dan zegt "veel van
      // deze macro" niets meer en beslist de gewone ranking.
      // restKcal > 0 is een voorwaarde, geen detail: zit je aan of over je
      // dagdoel, dan kunnen we niet meer op "past het nog" wegen en zou puur
      // sorteren op macro-grammen de zwaarste maaltijd bovenaan zetten. Dan
      // beslist de gewone ranking, die dat wél afstraft.
      if (sorteer === 'slim' && actieveCategorie && actieveCategorie.sorteerOp
        && actieveCategorie.gap > 0 && restKcal > 0) {
        const dekt = (x) => {
          // Past het niet meer in je dag, dan helpt het gat dichten ook niet —
          // achteraan, en laat de score de onderlinge volgorde bepalen.
          if ((Number(x.kcal) || 0) > restKcal + 100) return -1;
          return Math.min(Number(x[actieveCategorie.sorteerOp]) || 0, actieveCategorie.gap);
        };
        const v = dekt(b) - dekt(a);
        if (Math.abs(v) >= 3) return v;   // < 3 g verschil is ruis
      }
      return b._score - a._score
        || (a._mist ? a._mist.length : 3) - (b._mist ? b._mist.length : 3)
        // Bij gelijke stand: sneller klaar eerst (onbekende tijd achteraan).
        || (a._bereidMin == null ? 99 : a._bereidMin) - (b._bereidMin == null ? 99 : b._bereidMin)
        || a.kcal - b.kcal;
    });

  // "Waarom" bij de aanrader: hoe helpt de nummer 1 je bij wat je vandaag nog
  // nodig hebt (eiwit-gat en/of resterende kcal)? Alleen tonen als het écht iets
  // zegt — anders geen loze tekst.
  // De aanrader alleen bij de slimme volgorde zonder zoekterm — bij een eigen
  // sortering (vaak/zelden) wil je gewoon de kale lijst zien.
  const toonTop = !zoekW && !categorie && sorteer === 'slim';

  // Overzicht = het beslis-scherm: aanrader, haal-tip en de categorie-tegels,
  // zonder de volledige lijst. Bij een kleine pool is verstoppen alleen maar in
  // de weg — dan tonen we de lijst gewoon. De grens ligt boven het aantal
  // tegels (9), anders ruil je een korte lijst in voor evenveel tegels.
  const overzicht = !zoekW && !categorie;
  const toonTegels = overzicht && basisPool.length > 14;

  // Staat er iets aan wat de lijst inperkt? Bepaalt zowel de reset-knop als de
  // tekst in de lege staat — die moet benoemen wát er filtert, anders wijst de
  // app naar "Net bedacht" terwijl het een aangevinkte chip is.
  const filtersActief = !!(zoekW || categorie || inHuis || nieuwOnly || eiwitrijk
    || koolrijk || snel || meeneem || temp !== 'alle' || grootte !== 'alle');

  // Op het tegel-overzicht zijn de chips onzichtbaar, dus een filter die daar
  // nog aan staat is een spook: de tegel-tellingen negeren hem, de lijst
  // eronder niet, en er is geen knop om hem uit te zetten. Zodra we op het
  // tegel-overzicht belanden (terug-knop, of een zoekterm wissen) zetten we ze
  // daarom terug. Resetten raakt basisPool niet, dus dit kan niet lussen.
  useEffect(() => {
    if (toonTegels) resetChips();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [toonTegels]);

  const topReden = (() => {
    const r = gefilterd[0];
    if (!r || !toonTop) return '';
    const stukken = [];
    const eiwit = Math.round(r.eiwitten || 0);
    const kool = Math.round(r.koolhydraten || 0);
    if (eiwitStuurt && eiwit >= 8) stukken.push('dekt ' + Math.min(eiwit, eiwitGap) + ' g van je ' + eiwitGap + ' g eiwit');
    if (koolStuurt && kool >= 25) stukken.push('dekt ' + Math.min(kool, koolGap) + ' g van je ' + koolGap + ' g koolhydraten');
    if (restKcal > 0 && (r.kcal || 0) <= restKcal + 60) stukken.push('past in je ' + fmt(restKcal) + ' kcal');
    return stukken.length ? stukken.join(' · ') : '';
  })();

  // Haal-tip: het beste gerecht dat je nét niet kunt maken (1-4 dingen halen).
  // Dekking telt hier bewust niet mee — dit is je "wat is het waard om in huis
  // te halen"-inspiratie; macro-fit en variatie bepalen de keuze. Niet in de
  // werklunch-modus (daar telt dekking al zacht voor de hele lijst).
  // In een memo: het her-scoren van tientallen kandidaten hoort niet bij elke
  // toetsaanslag in het zoekveld te gebeuren.
  const haalTip = useMemo(() => {
    if (!toonTop || meeneem) return null;
    let best = null, bestScore = -Infinity;
    for (const r of verrijkt) {
      if (!r._mist || r._mist.length < 1 || r._mist.length > 4) continue;
      if (!filtersBehalveInHuis(r)) continue;
      const s = window.pool.scoreRecept(r, {
        mist: r._mist, restKcal, eiwitGap, koolGap, recent, recentKern,
        volgendBudget: kompas.volgendBudget, negeerDekking: true,
      });
      if (s > bestScore) { bestScore = s; best = r; }
    }
    return best;
    // filtersBehalveInHuis is een verse closure per render; de filter-state
    // waar hij van afhangt staat expliciet in de deps.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [verrijkt, toonTop, meeneem, restKcal, eiwitGap, koolGap, recent, recentKern,
    kompas.volgendBudget, netBedacht, nieuwOnly, grootte, eiwitrijk, koolrijk, temp, snel]);

  // De lijst onder de Aanrader — zonder de aanrader zelf en zonder de
  // haal-tip (die staan al op hun eigen podium).
  const lijst = (toonTop ? gefilterd.slice(1) : gefilterd)
    .filter((r) => !haalTip || r.id !== haalTip.id);

  // Archiveren = beoordeling -1: verdwijnt uit de pool én uit de AI-suggesties,
  // maar blijft in Recepten → Archief staan om terug te zetten.
  const verberg = async (r) => {
    setRecepten((L) => (L || []).map((x) => x.id === r.id ? { ...x, beoordeling: -1 } : x));
    try {
      await api.updateRecept(r.id, { beoordeling: -1 });
      showToast('Naar archief — terug te zetten via Recepten → Archief');
    } catch (err) {
      setRecepten((L) => (L || []).map((x) => x.id === r.id ? { ...x, beoordeling: r.beoordeling } : x));
      showToast('Verbergen mislukt: ' + err.message);
    }
  };

  const toggleFav = async (r) => {
    setRecepten((L) => (L || []).map((x) => x.id === r.id ? { ...x, favoriet: !r.favoriet } : x));
    try { await api.updateRecept(r.id, { favoriet: !r.favoriet }); }
    catch (err) { showToast('Opslaan mislukt: ' + err.message); }
  };

  // Sectie (geen eigen scherm): de kop en het kompas staan al op het dashboard.
  return (
    <div style={{ marginBottom: 18 }}>
      {/* Zoeken — op naam én ingrediënt, met wis-knop */}
      <div className="m-zoek" style={{ marginBottom: 10 }}>
        <MIcon name="search" size={16} color="var(--ink-3)" />
        {/* Zoeken doorzoekt altijd je hele bibliotheek: een openstaande
            categorie sluit daarom, anders zoek je stilletjes in een
            deelverzameling en denk je dat iets er niet is. */}
        <input value={zoek} placeholder="Zoek op naam of ingrediënt…"
          onChange={(e) => { const v = e.target.value; setZoek(v); if (v && categorie) sluitCategorie(); }} />
        {zoek && (
          <button className="tap" onClick={() => setZoek('')} title="Wissen"
            style={{ width: 24, height: 24, padding: 0, borderRadius: 999, border: 'none',
              background: 'var(--m-card-3)', cursor: 'pointer', flex: '0 0 auto',
              display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <MIcon name="close" size={12} color="var(--ink-2)" />
          </button>
        )}
      </div>

      {/* De chips horen bij de lijst: overal waar een lijst staat (categorie,
          zoekresultaat, of een pool die te klein is voor tegels) kun je
          verfijnen. Alleen op het tegel-overzicht blijven ze weg — daar kies
          je eerst een richting. Zonder deze koppeling zou een kleine pool
          helemaal zonder filters komen te zitten. Is er nog niets geladen of
          is de pool leeg, dan is er ook niets te filteren: dan geen chips
          boven de skeletons of boven het "je pool is nog leeg"-scherm. */}
      {!toonTegels && recepten !== null && verrijkt.length > 0 && (
      <React.Fragment>
      {actieveCategorie && (
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
          <button className="tap" onClick={sluitCategorie} title="Terug naar het overzicht"
            style={{ width: 30, height: 30, flex: '0 0 auto', padding: 0, borderRadius: 999,
              border: '1px solid var(--m-line)', background: 'var(--m-card)', cursor: 'pointer',
              display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <MIcon name="chevron" size={15} color="var(--ink-2)" style={{ transform: 'rotate(180deg)' }} />
          </button>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: 14.5, fontWeight: 600, lineHeight: 1.2 }}>{actieveCategorie.label}</div>
            {actieveCategorie.hint && (
              <div style={{ fontSize: 11, color: 'var(--ink-3)' }}>{actieveCategorie.hint}</div>
            )}
          </div>
        </div>
      )}
      {/* Grootte-chips (met stip die meegroeit) + toggles */}
      {toonChip('grootte') && (
      <div className="m-chip-rij" style={{ marginBottom: 4 }}>
        <KiesChip actief={grootte === 'alle'} onClick={() => setGrootte('alle')}>Alles</KiesChip>
        {window.pool.GROOTTES.map((g) => (
          <KiesChip key={g.key} actief={grootte === g.key} onClick={() => setGrootte(grootte === g.key ? 'alle' : g.key)} title={g.hint}>
            <span className={'m-stip ' + g.key} style={grootte === g.key ? { background: 'var(--leaf)' } : {}}></span>
            {g.label}
          </KiesChip>
        ))}
      </div>
      )}
      <div style={{ display: 'flex', gap: 7, flexWrap: 'wrap', marginBottom: 6 }}>
        {toonChip('inhuis') && (
        <KiesChip actief={inHuis} onClick={() => setInHuis(!inHuis)} title="Alleen wat je (bijna) kunt maken met wat in huis is">
          ✓ in huis
        </KiesChip>
        )}
        {toonChip('nieuw') && (
        <KiesChip actief={nieuwOnly} onClick={() => setNieuwOnly(!nieuwOnly)} title="Gerechten die je nog nooit at of beoordeelde">
          <MIcon name="spark" size={12} color={nieuwOnly ? 'var(--leaf)' : 'var(--forest-600)'} /> Nieuw{nieuwCount ? ' (' + nieuwCount + ')' : ''}
        </KiesChip>
        )}
        {toonChip('eiwit') && (
        <KiesChip actief={eiwitrijk} onClick={() => setEiwitrijk(!eiwitrijk)} title="≥ 7 g eiwit per 100 kcal">
          Eiwitrijk
        </KiesChip>
        )}
        {toonChip('kool') && (
        <KiesChip actief={koolrijk} onClick={() => setKoolrijk(!koolrijk)} title="≥ 11 g koolhydraten per 100 kcal — handig als je koolhydraten achterlopen">
          Koolhydraatrijk
        </KiesChip>
        )}
        {toonChip('temp') && (
        <React.Fragment>
        <KiesChip actief={temp === 'warm'} onClick={() => setTemp(temp === 'warm' ? 'alle' : 'warm')} title="Warme gerechten (koken/bakken/oven)">
          <MIcon name="flame" size={12} color={temp === 'warm' ? 'var(--leaf)' : '#c0704a'} /> Warm
        </KiesChip>
        <KiesChip actief={temp === 'koud'} onClick={() => setTemp(temp === 'koud' ? 'alle' : 'koud')} title="Koude gerechten (geen kookstap)">
          <MIcon name="snow" size={12} color={temp === 'koud' ? 'var(--leaf)' : '#4a86c0'} /> Koud
        </KiesChip>
        </React.Fragment>
        )}
        {toonChip('snel') && (
        <KiesChip actief={snel} onClick={() => setSnel(!snel)} title="Binnen ~15 minuten klaar">
          <MIcon name="clock" size={12} color={snel ? 'var(--leaf)' : 'var(--ink-3)'} /> Snel klaar
        </KiesChip>
        )}
        {toonChip('meeneem') && (
        <KiesChip actief={meeneem}
          onClick={() => {
            const m = !meeneem;
            setMeeneem(m);
            // Beleg haal je gewoon in huis: het in-huis-filter gaat in deze
            // modus uit (zichtbaar — de chip springt mee), zodat je ook ziet
            // wat je kunt maken als je even langs de super gaat.
            if (m) setInHuis(false);
          }}
          title="Om mee te nemen naar werk of school — bammetjes, wraps, salades (koud, of gemarkeerd als lunch/meeneem)">
          <MIcon name="bag" size={12} color={meeneem ? 'var(--leaf)' : 'var(--forest-600)'} /> Mee naar werk
        </KiesChip>
        )}
      </div>

      {/* Uitleg bij de werklunch-modus: "halen:" is een boodschappenlijstje,
          geen blokkade — beleg is zo gehaald. */}
      {meeneem && (
        <div style={{ fontSize: 11.5, color: 'var(--ink-3)', margin: '2px 0 8px', lineHeight: 1.45 }}>
          Broodjes, wraps en koude lunches voor onderweg — ook mét beleg dat je nog
          niet in huis hebt. <strong style={{ color: '#8a5a2b' }}>halen:</strong> op een
          kaartje is je mini-boodschappenlijstje.
        </div>
      )}

      {/* Volgorde: slim (ranking) of op je gegeten-teller — vaak gegeten voor
          je vertrouwde maaltijden, zelden voor variatie/nog te ontdekken. */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 7, flexWrap: 'wrap', marginBottom: 14 }}>
        <span style={{ fontSize: 11, fontWeight: 600, color: 'var(--ink-3)' }}>Volgorde:</span>
        <KiesChip actief={sorteer === 'slim'} onClick={() => setSorteer('slim')}
          title="Wat nu het beste past (in huis, dagbudget, variatie)">Slim</KiesChip>
        <KiesChip actief={sorteer === 'vaak'} onClick={() => setSorteer(sorteer === 'vaak' ? 'slim' : 'vaak')}
          title="Wat je het vaakst at bovenaan">Vaak gegeten</KiesChip>
        <KiesChip actief={sorteer === 'zelden'} onClick={() => setSorteer(sorteer === 'zelden' ? 'slim' : 'zelden')}
          title="Wat je zelden of nog nooit at bovenaan — voor meer variatie">Zelden gegeten</KiesChip>
      </div>
      </React.Fragment>
      )}

      {/* Dagplanner-paneel: verschijnt zodra er iets in je plan staat en
          blijft (sticky) in beeld terwijl je verder bladert. */}
      {plan.length > 0 && (
        <PlanPaneel items={plan} totals={totals} meeneem={meeneem} busy={planBusy}
          onAantal={planAantal} onWissen={() => setPlan([])} onLog={logPlan} />
      )}

      {/* Net bedacht: wat je zojuist liet bedenken — bovenaan, buiten de filters
          en ranking, zodat je precies ziet wat er is toegevoegd. */}
      {netBedachtRecepten.length > 0 && (
        <div style={{ marginBottom: 14 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 8 }}>
            <MIcon name="spark" size={14} color="var(--forest-600)" />
            <span className="m-eyebrow" style={{ color: 'var(--forest-600)' }}>Net bedacht</span>
            <span style={{ flex: 1 }}></span>
            <button className="tap" onClick={() => setNetBedacht([])}
              style={{ background: 'none', border: 'none', color: 'var(--ink-3)', fontSize: 11.5, cursor: 'pointer', padding: '2px 4px' }}>
              Klaar
            </button>
          </div>
          {netBedachtRecepten.map((r) => (
            <PoolKaart key={r.id} r={r} onOpen={onKies} onFav={toggleFav} onVerberg={verberg}
              onPlan={planToe} planAantal={inPlan[r.id]} toonTelling={sorteer !== 'slim'} halen={meeneem} />
          ))}
        </div>
      )}

      {recepten === null ? (
        <React.Fragment>
          {[0, 1, 2, 3].map((i) => (
            <div key={i} className="m-skel" style={{ height: 66, borderRadius: 16, marginBottom: 9, border: 'none' }}></div>
          ))}
        </React.Fragment>
      ) : gefilterd.length ? (
        <React.Fragment>
          {/* Voorraad gewijzigd? Producten in huis waar nog geen gerecht bij past
              → bied aan er verse ideeën bij te bedenken. Anders: dunne pool.
              (Niet in werklunch-modus — daar draait het niet om de voorraad.) */}
          {!vullen && !meeneem && ongebruikt.length >= 2 ? (
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '11px 13px', marginBottom: 10,
              background: 'var(--forest-100)', border: '1px solid var(--forest-200)', borderRadius: 14 }}>
              <MIcon name="spark" size={15} color="var(--forest-600)" style={{ flex: '0 0 auto' }} />
              <span style={{ flex: 1, fontSize: 12, color: 'var(--forest-900)', lineHeight: 1.4 }}>
                Je hebt <strong>{ongebruikt.slice(0, 3).join(', ')}</strong>{ongebruikt.length > 3 ? ` +${ongebruikt.length - 3}` : ''} in
                huis waar nog geen maaltijd bij past — laat Moderis er nieuwe bedenken.
              </span>
              {/* Prefill: die ongebruikte producten worden de moet-erin-chips */}
              <button className="m-btn m-btn-primary tap"
                onClick={() => setBedenkOpen({ moet: ongebruikt.slice(0, 6), modus: 'beperkt' })}
                style={{ fontSize: 12, padding: '8px 12px', flex: '0 0 auto' }}>
                Bedenk 6
              </button>
            </div>
          ) : inHuisTeller < 5 && !vullen && !meeneem ? (
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '11px 13px', marginBottom: 10,
              background: 'var(--forest-100)', border: '1px solid var(--forest-200)', borderRadius: 14 }}>
              <MIcon name="spark" size={15} color="var(--forest-600)" style={{ flex: '0 0 auto' }} />
              <span style={{ flex: 1, fontSize: 12, color: 'var(--forest-900)', lineHeight: 1.4 }}>
                Weinig maaltijden die je nu kunt maken — Moderis kan er 6 bedenken uit je voorraad.
              </span>
              <button className="m-btn m-btn-primary tap" onClick={() => setBedenkOpen({ modus: 'beperkt' })}
                style={{ fontSize: 12, padding: '8px 12px', flex: '0 0 auto' }}>
                Bedenk 6
              </button>
            </div>
          ) : null}
          {/* Aanrader: de nummer 1 van de ranking op het podium — behalve bij
              een zoekopdracht, dan wil je gewoon de kale lijst. */}
          {toonTop && gefilterd.length > 0 && (
            <PoolTopKaart r={gefilterd[0]} onOpen={onKies} reden={topReden}
              onPlan={planToe} planAantal={inPlan[gefilterd[0].id]} halen={meeneem} />
          )}
          {/* Haal-tip: het beste gerecht dat je nét niet kunt maken — "mist"
              is hier je boodschappenlijstje. Zo zie je wat het waard is om in
              huis te halen, óók met het in-huis-filter aan. */}
          {toonTop && haalTip && (!gefilterd[0] || haalTip.id !== gefilterd[0].id) && (
            <HaalTipBlok r={haalTip} hint="haal dit in huis voor meer variatie"
              onOpen={onKies} onFav={toggleFav} onVerberg={verberg}
              onPlan={planToe} planAantal={inPlan[haalTip.id]} />
          )}
          {/* Op het overzicht: sturen op je macro-gaten en dan pas de lijst in.
              Zo hoef je niet langs tientallen kaarten te scrollen om te vinden
              wat je vandaag nodig hebt. */}
          {toonTegels ? (
            <React.Fragment>
              <MacroSturing macros={macroSturing} onKies={kiesCategorie} />
              {/* Hoogstens 10 richtingen + "Alle maaltijden": meer keuzes dan
                  dat is zelf weer een lijst om doorheen te lezen. De volgorde
                  in `categorieen` is op nut gesorteerd, dus afkappen laat de
                  grootte-tegels als eerste vallen — die zijn ook binnen elke
                  categorie nog als chip te kiezen, en "Past nu" plus het
                  dagkompas dekken ze al grotendeels af. */}
              <CategorieTegels onKies={kiesCategorie}
                categorieen={categorieen.filter((c) => c.aantal > 0 && c.key !== 'alles').slice(0, 10)
                  .concat(categorieen.filter((c) => c.key === 'alles'))} />
            </React.Fragment>
          ) : (
            <React.Fragment>
              {lijst.length > 0 && (
                <div style={{ fontSize: 11.5, color: 'var(--ink-3)', marginBottom: 8 }}>
                  {toonTop
                    ? lijst.length + ' andere ' + (lijst.length === 1 ? 'maaltijd' : 'maaltijden') + (inHuis && !nieuwOnly ? ' die je (bijna) kunt maken' : '')
                    : lijst.length + ' ' + (lijst.length === 1 ? 'maaltijd' : 'maaltijden')}
                </div>
              )}
              {lijst.map((r) => (
                <PoolKaart key={r.id} r={r} onOpen={onKies} onFav={toggleFav} onVerberg={verberg}
                  onPlan={planToe} planAantal={inPlan[r.id]} toonTelling={sorteer !== 'slim'} halen={meeneem} />
              ))}
            </React.Fragment>
          )}
          <button className="m-btn m-btn-ghost tap" onClick={() => setBedenkOpen({})} disabled={vullen}
            style={{ width: '100%', fontSize: 13.5, padding: '12px', marginTop: 4,
              ...(vullen ? { opacity: 0.55, cursor: 'wait' } : {}) }}>
            {vullen ? <Spinner size={15} /> : <MIcon name={meeneem ? 'bag' : 'spark'} size={15} color="var(--forest-600)" />}
            {vullen ? (meeneem ? 'Moderis bedenkt werklunches…' : 'Moderis bedenkt nieuwe maaltijden…')
              : (meeneem ? 'Laat Moderis werklunches bedenken (jij stuurt)' : 'Laat Moderis nieuwe bedenken (jij stuurt)')}
          </button>
        </React.Fragment>
      ) : verrijkt.length ? (
        <React.Fragment>
          {/* Juist hier is de haal-tip het nuttigst: er is niets te maken, dus
              laat zien wat het dichtstbij ligt en wat je ervoor moet halen. */}
          {haalTip && (
            <HaalTipBlok r={haalTip} hint="dit ligt het dichtstbij — dit heb je ervoor nodig"
              onOpen={onKies} onFav={toggleFav} onVerberg={verberg}
              onPlan={planToe} planAantal={inPlan[haalTip.id]} />
          )}
          <div style={{ textAlign: 'center', padding: '32px 20px', color: 'var(--ink-3)' }}>
            {/* De tekst moet kloppen met wat de gebruiker ziet: staat er geen
                zoekterm, categorie of chip aan, dan is "deze filters" onzin —
                dan gaat het om de "Net bedacht"-sectie die alles opslokt. */}
            <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--ink-2)', marginBottom: 6 }}>
              {zoekW ? 'Niets gevonden voor "' + zoek.trim() + '"'
                : filtersActief ? 'Niets gevonden met deze filters'
                  : netBedachtRecepten.length ? 'Alles staat hierboven onder "Net bedacht"'
                    : 'Niets gevonden'}
            </div>
            {/* Eén knop die altijd terugbrengt naar een gevulde weergave —
                ongeacht welke combinatie van zoekterm, categorie en chips het
                leeg maakte. */}
            {filtersActief && (
              <button className="m-btn m-btn-ghost tap" style={{ fontSize: 13, marginTop: 6 }}
                onClick={() => { setZoek(''); setCategorie(null); resetChips(); }}>
                Toon alle maaltijden
              </button>
            )}
            <div style={{ marginTop: 10 }}>
              <button className="m-btn m-btn-ghost tap" onClick={() => setBedenkOpen({})} disabled={vullen} style={{ fontSize: 13 }}>
                {vullen ? <Spinner size={14} /> : <MIcon name={meeneem ? 'bag' : 'spark'} size={14} color="var(--forest-600)" />}
                {vullen ? 'Bezig…' : (meeneem ? 'Laat Moderis werklunches bedenken' : 'Laat Moderis nieuwe bedenken')}
              </button>
            </div>
          </div>
        </React.Fragment>
      ) : (
        <div style={{ textAlign: 'center', padding: '40px 20px', color: 'var(--ink-3)' }}>
          <MIcon name="bowl" size={34} color="var(--m-line-2)" style={{ margin: '0 auto 14px' }} />
          <div style={{ fontSize: 15, fontWeight: 600, color: 'var(--ink-2)', marginBottom: 6 }}>Je pool is nog leeg</div>
          <div style={{ fontSize: 13, lineHeight: 1.5, marginBottom: 16 }}>
            Begin met een starterset van 20 alledaagse gerechten, of laat Moderis
            iets bedenken uit je voorraad. Eigen maaltijden maak je bij "Alle maaltijden".
          </div>
          <button className="m-btn m-btn-primary tap" onClick={() => vulAan({ starterset: true })} disabled={vullen}
            style={{ fontSize: 14, marginBottom: 10 }}>
            {vullen ? <Spinner size={16} /> : <MIcon name="box" size={16} color="var(--on-forest)" />} {vullen ? 'Laden…' : 'Starterset laden (20 gerechten)'}
          </button>
          <div style={{ display: 'flex', gap: 8, justifyContent: 'center', flexWrap: 'wrap' }}>
            <button className="m-btn m-btn-ghost tap" onClick={() => setBedenkOpen({})} disabled={vullen} style={{ fontSize: 13 }}>
              <MIcon name="spark" size={14} color="var(--forest-600)" /> Moderis laat bedenken
            </button>
            <button className="m-btn m-btn-ghost tap" onClick={onNaarRecepten} style={{ fontSize: 13 }}>
              <MIcon name="book" size={14} color="var(--forest-600)" /> Alle maaltijden
            </button>
          </div>
        </div>
      )}

      {/* Bedenk-sheet: stuur wat erin moet + hoe streng "in huis" telt.
          Via een portal naar <body>: deze component zit diep in de
          (geanimeerde) dashboard-boom en daar zou position:fixed niet
          schermvullend zijn — op body-niveau altijd wel. */}
      {bedenkOpen && ReactDOM.createPortal(
        <BedenkSheet werklunch={meeneem} voorstelMoet={bedenkOpen.moet} voorstelModus={bedenkOpen.modus}
          onClose={() => setBedenkOpen(null)}
          onBedenk={(opts) => { setBedenkOpen(null); vulAan(opts); }} />,
        document.body
      )}
    </div>
  );
}

Object.assign(window, { PoolKiezen, KompasNote });
