/* web-tabs.jsx — Log, History, Insights tab content for the web shell */

// ═══════════════════════════════════════════════════════════════
// LOG TAB
// ═══════════════════════════════════════════════════════════════

function WebLog({ scenario, tdee = TDEE, windowDays = 3, onUpdateDailyIntake, unitSystem = 'metric' }) {
  if (scenario.dailyTotalOnly) {
    return <DailyTotalLog scenario={scenario} tdee={tdee} windowDays={windowDays} onUpdateDailyIntake={onUpdateDailyIntake} unitSystem={unitSystem} />;
  }

  const days = scenario.days;
  const today = days[days.length - 1];
  const totals = TODAY_MEALS.reduce((acc, m) => ({
    p: acc.p + m.p, c: acc.c + m.c, f: acc.f + m.f, kcal: acc.kcal + m.kcal,
  }), { p: 0, c: 0, f: 0, kcal: 0 });

  // Macro targets at TDEE (40% carbs, 30% protein, 30% fat — illustrative)
  const targets = {
    p: Math.round(TDEE * 0.30 / 4),
    c: Math.round(TDEE * 0.40 / 4),
    f: Math.round(TDEE * 0.30 / 9),
  };

  const slotOrder = ['breakfast', 'lunch', 'snack', 'dinner'];
  const mealsBySlot = {};
  slotOrder.forEach((s) => { mealsBySlot[s] = TODAY_MEALS.filter((m) => m.slot === s); });

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 460px', height: '100%', boxSizing: 'border-box' }}>
      {/* LEFT — timeline */}
      <div style={{ padding: '20px 40px 28px', display: 'flex', flexDirection: 'column', gap: 14, overflow: 'hidden' }}>
        {/* search input */}
        <div style={{
          background: 'var(--paper-2)', border: '1.5px solid var(--hair-strong)',
          borderRadius: 14, padding: '14px 18px',
          display: 'flex', alignItems: 'center', gap: 12,
        }}>
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--ink-3)" strokeWidth="2">
            <circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/>
          </svg>
          <input placeholder="what did you eat?" defaultValue=""
            style={{
              flex: 1, border: 'none', outline: 'none', background: 'transparent',
              fontFamily: "'Geist', system-ui, sans-serif", fontSize: 16, color: 'var(--ink)',
            }} />
          <kbd style={{
            fontFamily: "'JetBrains Mono', monospace", fontSize: 10.5,
            color: 'var(--ink-3)', border: '1px solid var(--hair-strong)',
            borderRadius: 4, padding: '2px 7px',
          }}>⌘ K</kbd>
        </div>

        {/* recents row */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'nowrap', overflow: 'hidden' }}>
          <span className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>recent</span>
          {FOOD_LIBRARY.slice(0, 5).map((f, i) => (
            <button key={i} style={{
              background: 'var(--paper-2)', border: '1px solid var(--hair)',
              borderRadius: 99, padding: '6px 12px', cursor: 'pointer',
              fontFamily: "'Geist', system-ui, sans-serif", fontSize: 12.5, color: 'var(--ink)',
              display: 'flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap',
            }}>
              <span style={{ width: 5, height: 5, borderRadius: 99, background: 'var(--amber)' }} />
              {f.name.split(',')[0]}
              <span className="mono num-tab" style={{ color: 'var(--ink-3)', fontSize: 11 }}>{f.kcal}</span>
            </button>
          ))}
        </div>

        {/* timeline */}
        <div style={{ flex: 1, overflow: 'auto', paddingRight: 4 }}>
          {slotOrder.map((slot, idx) => (
            <div key={slot} style={{ marginBottom: 6 }}>
              <div style={{
                display: 'flex', alignItems: 'baseline', gap: 10, marginTop: idx === 0 ? 4 : 14, marginBottom: 4,
              }}>
                <span style={{ fontFamily: "'Instrument Serif', serif", fontSize: 18, color: 'var(--ink-2)', textTransform: 'lowercase', fontStyle: 'italic' }}>
                  {slot}
                </span>
                <span style={{ flex: 1, height: 1, background: 'var(--hair-faint)' }} />
                <span className="mono num-tab" style={{ fontSize: 11, color: 'var(--ink-3)' }}>
                  {mealsBySlot[slot].reduce((s, m) => s + m.kcal, 0)} kcal
                </span>
              </div>
              {mealsBySlot[slot].length === 0 ? (
                <button style={{
                  width: '100%', background: 'transparent', border: '1.5px dashed var(--hair-strong)',
                  borderRadius: 12, padding: '12px 14px', cursor: 'pointer',
                  display: 'flex', alignItems: 'center', gap: 8,
                  color: 'var(--ink-3)', fontFamily: "'Geist', system-ui, sans-serif", fontSize: 13,
                }}>
                  <span style={{ fontFamily: "'Instrument Serif', serif", fontSize: 18, lineHeight: 1 }}>+</span>
                  add a {slot}
                </button>
              ) : (
                mealsBySlot[slot].map((m, i) => <MealRow key={i} meal={m} />)
              )}
            </div>
          ))}
        </div>
      </div>

      {/* RIGHT — totals + macros + suggestions */}
      <aside style={{
        borderLeft: '1px solid var(--hair)',
        padding: '20px 28px 28px',
        display: 'flex', flexDirection: 'column', gap: 18,
        overflow: 'auto',
      }}>
        {/* today header */}
        <div>
          <SectionEyebrow left={today.date.toLowerCase() + ' · monday'} right={TODAY_MEALS.length + ' meals'} />
          <div style={{
            fontFamily: "'Instrument Serif', serif", fontSize: 56, lineHeight: 1,
            color: 'var(--ink)', marginTop: 6,
          }}>
            <span className="mono num-tab" style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: 56, fontWeight: 500 }}>
              {totals.kcal.toLocaleString()}
            </span>
            <span style={{ fontSize: 22, color: 'var(--ink-3)', marginLeft: 6 }}>/ {TDEE.toLocaleString()} kcal</span>
          </div>
          <div style={{ marginTop: 4, fontSize: 13, color: 'var(--ink-2)' }}>
            {TDEE - totals.kcal > 0
              ? `${TDEE - totals.kcal} kcal of headroom before you cross maintenance.`
              : `${totals.kcal - TDEE} kcal above maintenance today.`}
          </div>
        </div>

        {/* daily progress bar */}
        <DailyBar consumed={totals.kcal} target={TDEE} />

        {/* macros */}
        <div>
          <SectionHead title="macros" />
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10, marginTop: 10 }}>
            <MacroRing label="protein" value={totals.p} target={targets.p} unit="g" color="var(--rust)" />
            <MacroRing label="carbs"   value={totals.c} target={targets.c} unit="g" color="var(--amber)" />
            <MacroRing label="fat"     value={totals.f} target={targets.f} unit="g" color="var(--moss)" />
          </div>
        </div>

        {/* quick add */}
        <div>
          <SectionHead title="quick add" right="favourites" />
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 8, marginTop: 10 }}>
            {FOOD_LIBRARY.slice(0, 6).map((f, i) => (
              <button key={i} style={{
                background: 'var(--paper-2)', border: '1px solid var(--hair)',
                borderRadius: 10, padding: '10px 12px', cursor: 'pointer', textAlign: 'left',
                display: 'flex', flexDirection: 'column', gap: 4,
              }}>
                <span style={{ fontSize: 12.5, color: 'var(--ink)', lineHeight: 1.2 }}>{f.name}</span>
                <span className="mono num-tab" style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>{f.kcal} kcal</span>
              </button>
            ))}
          </div>
        </div>
      </aside>
    </div>
  );
}

function DailyTotalLog({ scenario, tdee, windowDays = 3, onUpdateDailyIntake, unitSystem = 'metric' }) {
  const isNarrow = useIsNarrow();
  const days = scenario.days;
  const today = days[days.length - 1];
  const reading = getBalanceReading(days, windowDays, tdee);
  const [drafts, setDrafts] = React.useState(() => Object.fromEntries(days.map((day) => [day.dateKey || day.date, Number.isFinite(day.intake) ? String(day.intake) : ''])));
  const [addDate, setAddDate] = React.useState(() => toDateKey(new Date()));
  const [addIntake, setAddIntake] = React.useState('');

  React.useEffect(() => {
    setDrafts(Object.fromEntries(days.map((day) => [day.dateKey || day.date, Number.isFinite(day.intake) ? String(day.intake) : ''])));
  }, [days.map((day) => `${day.dateKey || day.date}:${day.intake ?? ''}`).join('|')]);

  const saveDay = (day, value) => {
    if (!onUpdateDailyIntake) return;
    if (!Number.isFinite(Number(value))) return;
    onUpdateDailyIntake(day.dateKey || day.date, Number(value));
  };

  const addDay = () => {
    if (!onUpdateDailyIntake || !Number.isFinite(Number(addIntake))) return;
    onUpdateDailyIntake(addDate, Number(addIntake));
    setAddIntake('');
  };

  return (
    <div style={{ display: 'grid', gridTemplateColumns: isNarrow ? '1fr' : '1fr 420px', height: isNarrow ? 'auto' : '100%', boxSizing: 'border-box' }}>
      <div style={{ padding: isNarrow ? '18px' : '20px 40px 28px', display: 'flex', flexDirection: 'column', gap: 16, overflow: 'auto', minWidth: 0 }}>
        <SectionEyebrow left="daily calorie totals" right={`${displayDate(days[0].date)} - ${displayDate(days[days.length - 1].date)}`} />
        <div style={{
          background: 'var(--paper-2)', border: '1px solid var(--hair)',
          borderRadius: 12, padding: '14px 16px',
          display: 'grid', gridTemplateColumns: isNarrow ? '1fr' : '160px 1fr 110px', gap: 12, alignItems: 'end',
        }}>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
            <span className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>date</span>
            <input type="date" value={addDate} onChange={(e) => setAddDate(e.target.value)} style={dailyTotalInputStyle(16)} />
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
            <span className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>daily total</span>
            <input inputMode="numeric" value={addIntake} placeholder="kcal" onChange={(e) => setAddIntake(e.target.value)} style={dailyTotalInputStyle(20)} />
          </label>
          <button onClick={addDay} style={settingsButtonStyle()}>save day</button>
        </div>
        <div style={{ display: 'grid', gap: 10 }}>
          {days.map((day, index) => {
            const key = day.dateKey || day.date;
            const isLogged = day.dayStatus === 'logged' || day.intakeSource === 'logged';
            const zone = Number.isFinite(day.balance) ? classify(day.balance, tdee) : null;
            const color = zone ? ZONES[zone].hex : 'var(--ink-3)';
            const balanceLabel = Number.isFinite(day.balance) ? (day.balance >= 0 ? `+${day.balance}` : `${day.balance}`) : 'N/A';
            return (
              <div key={day.date + index} style={{
                background: 'var(--paper-2)', border: '1px solid var(--hair)',
                borderRadius: 12, padding: '14px 16px',
                display: 'grid', gridTemplateColumns: isNarrow ? '1fr 1fr' : '120px 1fr 90px 90px 90px', gap: isNarrow ? 10 : 14, alignItems: 'center',
              }}>
                <div>
                  <div className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>{day.dow}</div>
                  <div style={{ fontFamily: "'Instrument Serif', serif", fontSize: 25, color: 'var(--ink)', lineHeight: 1 }}>{displayDate(day.date)}</div>
                </div>
                <label style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
                  <input
                    inputMode="numeric"
                    value={drafts[key] || ''}
                    placeholder="N/A"
                    onChange={(e) => setDrafts((current) => ({ ...current, [key]: e.target.value }))}
                    onBlur={(e) => saveDay(day, e.target.value)}
                    onKeyDown={(e) => {
                      if (e.key === 'Enter') {
                        e.currentTarget.blur();
                      }
                    }}
                    style={{
                      width: '100%',
                      background: 'transparent',
                      border: 'none',
                      outline: 'none',
                      color: 'var(--ink)',
                      fontFamily: "'JetBrains Mono', monospace",
                      fontSize: 28,
                      fontVariantNumeric: 'tabular-nums',
                    }}
                  />
                  <span className="mono" style={{ fontSize: 12, color: 'var(--ink-3)' }}>kcal</span>
                </label>
                <div className="mono num-tab" style={{ fontSize: 16, color }}>{balanceLabel}</div>
                <div className="mono" style={{ fontSize: 10.5, color: isLogged ? 'var(--ink-2)' : 'var(--ink-3)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>
                  {isLogged ? 'logged' : 'empty'}
                </div>
                <div style={{
                  justifySelf: 'end',
                  fontFamily: "'Instrument Serif', serif",
                  fontSize: 24,
                  fontStyle: 'italic',
                  color,
                }}>{zone || 'N/A'}</div>
              </div>
            );
          })}
        </div>
      </div>

      <aside style={{
        borderLeft: isNarrow ? 'none' : '1px solid var(--hair)',
        borderTop: isNarrow ? '1px solid var(--hair)' : 'none',
        padding: isNarrow ? '18px' : '20px 28px 28px',
        display: 'flex', flexDirection: 'column', gap: 16,
      }}>
        <div>
          <SectionEyebrow left={displayDate(today.date).toLowerCase()} right="today" />
          <div className="mono num-tab" style={{ fontSize: 56, lineHeight: 1, marginTop: 10, color: 'var(--ink)' }}>
            {Number.isFinite(today.intake) ? today.intake.toLocaleString() : 'N/A'}
          </div>
          <div className="mono" style={{ marginTop: 3, fontSize: 12, color: 'var(--ink-3)' }}>{Number.isFinite(today.intake) ? 'kcal intake' : 'no total logged'}</div>
        </div>
        <DailyBar consumed={Number.isFinite(today.intake) ? today.intake : 0} target={tdee} />
        <div style={{
          background: 'var(--paper-2)', border: '1px solid var(--hair)',
          borderRadius: 12, padding: 16,
        }}>
          <SectionHead title="current reading" right={reading.confidence} />
          <div style={{
            fontFamily: "'Instrument Serif', serif", fontSize: 42, lineHeight: 1,
            fontStyle: 'italic', color: ZONES[reading.state].hex, marginTop: 12,
          }}>
            {reading.state}.
          </div>
          <div style={{ marginTop: 10, fontSize: 13.5, color: 'var(--ink-2)', lineHeight: 1.45 }}>
            {todayStatusBody(reading, unitSystem)}
          </div>
        </div>
      </aside>
    </div>
  );
}

function dailyTotalInputStyle(fontSize = 18) {
  return {
    width: '100%',
    minWidth: 0,
    boxSizing: 'border-box',
    background: 'transparent',
    border: 'none',
    outline: 'none',
    color: 'var(--ink)',
    fontFamily: "'JetBrains Mono', monospace",
    fontSize,
    fontVariantNumeric: 'tabular-nums',
  };
}

function MealRow({ meal }) {
  return (
    <div style={{
      background: 'var(--paper-2)', border: '1px solid var(--hair-faint)',
      borderRadius: 12, padding: '12px 14px', marginBottom: 6,
      display: 'grid', gridTemplateColumns: '50px 1fr auto', alignItems: 'center', gap: 14,
    }}>
      <span className="mono num-tab" style={{ fontSize: 12, color: 'var(--ink-3)', letterSpacing: '0.04em' }}>{meal.time}</span>
      <div style={{ minWidth: 0 }}>
        <div style={{ fontSize: 13.5, color: 'var(--ink)', lineHeight: 1.2 }}>{meal.name}</div>
        <div style={{ marginTop: 4, display: 'flex', alignItems: 'center', gap: 8 }}>
          <MacroPip color="var(--rust)" label="p" g={meal.p} />
          <MacroPip color="var(--amber)" label="c" g={meal.c} />
          <MacroPip color="var(--moss)" label="f" g={meal.f} />
        </div>
      </div>
      <span className="mono num-tab" style={{ fontSize: 14, color: 'var(--ink)', fontWeight: 500 }}>{meal.kcal} kcal</span>
    </div>
  );
}

function MacroPip({ color, label, g }) {
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
      <span style={{ width: 6, height: 6, borderRadius: 99, background: color }} />
      <span className="mono num-tab" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.04em' }}>
        {label} {g}g
      </span>
    </span>
  );
}

function DailyBar({ consumed, target }) {
  const pct = Math.min(1, consumed / target);
  return (
    <div>
      <div style={{
        height: 10, background: 'var(--paper-2)', border: '1px solid var(--hair)',
        borderRadius: 99, overflow: 'hidden', position: 'relative',
      }}>
        <div style={{
          width: `${pct * 100}%`, height: '100%', background: 'var(--ink)',
          borderRadius: 99,
        }} />
      </div>
      <div style={{ marginTop: 6, display: 'flex', justifyContent: 'space-between' }}>
        <span className="mono num-tab" style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>0</span>
        <span className="mono num-tab" style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>{Math.round(pct * 100)}%</span>
        <span className="mono num-tab" style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>{target}</span>
      </div>
    </div>
  );
}

function MacroRing({ label, value, target, unit, color }) {
  const pct = Math.min(1, value / target);
  const R = 30, C = 2 * Math.PI * R;
  return (
    <div style={{
      background: 'var(--paper-2)', border: '1px solid var(--hair)',
      borderRadius: 12, padding: '12px 8px',
      display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6,
    }}>
      <div style={{ position: 'relative', width: 72, height: 72 }}>
        <svg viewBox="0 0 80 80" width="72" height="72">
          <circle cx="40" cy="40" r={R} stroke="var(--hair-faint)" strokeWidth="6" fill="none" />
          <circle cx="40" cy="40" r={R} stroke={color} strokeWidth="6" fill="none"
            strokeDasharray={`${C * pct} ${C}`} strokeLinecap="round"
            transform="rotate(-90 40 40)" />
        </svg>
        <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
          <span className="mono num-tab" style={{ fontSize: 15, color: 'var(--ink)', fontWeight: 500 }}>{value}</span>
        </div>
      </div>
      <div style={{ textAlign: 'center' }}>
        <div className="mono" style={{ fontSize: 10, color: 'var(--ink-3)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>{label}</div>
        <div className="mono num-tab" style={{ fontSize: 10.5, color: 'var(--ink-3)', marginTop: 2 }}>of {target}{unit}</div>
      </div>
    </div>
  );
}


// ═══════════════════════════════════════════════════════════════
// HISTORY TAB
// ═══════════════════════════════════════════════════════════════

function WebHistory({ scenario, tdee = TDEE, windowDays = 3 }) {
  const isNarrow = useIsNarrow();
  const history = scenario.dailyTotalOnly
    ? scenario.days.map((day) => ({ ...day, dateObj: parseEntryDate(day), label: displayDate(day.date) }))
    : HISTORY.map((day) => ({ ...day, dateObj: day.date }));
  if (history.length === 0) {
    return <EmptyDataView title="history" detail="Daily totals will appear here after logging." />;
  }
  const loggedHistory = history.filter((day) => day.intakeSource === 'logged' && Number.isFinite(day.balance));
  const todayIdx = history.length - 1;
  const [selectedIdx, setSelectedIdx] = React.useState(todayIdx);
  React.useEffect(() => {
    setSelectedIdx(history.length - 1);
  }, [history.length, history.map((day) => day.intake).join('|')]);
  const selected = history[selectedIdx];

  // Pad calendar so first row starts on Monday (ISO week)
  const firstDow = history[0].dateObj.getDay(); // 0 = Sun
  const padBefore = (firstDow + 6) % 7; // Mon=0
  // pad after: extend to fill last row
  const totalCells = padBefore + history.length;
  const padAfter = (7 - (totalCells % 7)) % 7;
  const grid = [
    ...Array.from({ length: padBefore }, () => null),
    ...history,
    ...Array.from({ length: padAfter }, () => 'future'),
  ];

  // Weekly averages for trend
  const weeks = [];
  for (let i = 0; i < history.length; i += 7) {
    const slice = history.slice(i, i + 7);
    const logged = slice.filter((d) => d.intakeSource === 'logged' && Number.isFinite(d.balance));
    const avg = logged.length ? logged.reduce((s, d) => s + d.balance, 0) / logged.length : null;
    weeks.push({ avg, days: slice, loggedDays: logged.length });
  }

  const streaks = getZoneStreaks(history, tdee);

  const overallAvg = loggedHistory.length ? loggedHistory.reduce((s, d) => s + d.balance, 0) / loggedHistory.length : null;
  const coveragePct = Math.round((loggedHistory.length / history.length) * 100);

  return (
    <div style={{ display: 'grid', gridTemplateColumns: isNarrow ? '1fr' : '1fr 380px', height: isNarrow ? 'auto' : '100%' }}>
      {/* LEFT — calendar */}
      <div style={{ padding: isNarrow ? '18px' : '20px 40px 28px', display: 'flex', flexDirection: 'column', overflow: isNarrow ? 'visible' : 'hidden', minWidth: 0 }}>
        <SectionEyebrow
          left={scenario.dailyTotalOnly ? `${history.length}-day log · ${history[0].label} — ${history[history.length - 1].label}` : 'last 4 weeks · apr 19 — may 16'}
          right={overallAvg == null ? 'avg N/A' : `avg ${overallAvg > 0 ? '+' : ''}${Math.round(overallAvg)} kcal/day`}
        />

        {/* day-of-week header */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, minmax(0, 1fr))', gap: isNarrow ? 5 : 12, marginTop: 18 }}>
          {['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'].map((d) => (
            <div key={d} className="mono" style={{
              fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.10em',
              textTransform: 'uppercase', textAlign: 'center',
            }}>{d}</div>
          ))}
        </div>

        {/* calendar grid */}
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, minmax(0, 1fr))', gap: isNarrow ? 5 : 12, marginTop: 10 }}>
          {grid.map((cell, i) => {
            if (cell === null) {
              return <div key={`pad-${i}`} style={{ aspectRatio: '1 / 1' }} />;
            }
            if (cell === 'future') {
              return (
                <div key={`fut-${i}`} style={{ display: 'flex', justifyContent: 'center', aspectRatio: '1 / 1' }}>
                  <MiniOrb isFuture size={isNarrow ? 42 : 82} />
                </div>
              );
            }
            const idx = history.indexOf(cell);
            const isToday = idx === todayIdx;
            const isSelected = idx === selectedIdx;
            const isLogged = cell.intakeSource === 'logged' && Number.isFinite(cell.balance);
            return (
              <button key={i} onClick={() => setSelectedIdx(idx)} style={{
                background: 'transparent', border: 'none', padding: 0, cursor: 'pointer',
                display: 'flex', justifyContent: 'center', aspectRatio: '1 / 1',
                position: 'relative',
              }}>
                <div style={{
                  position: 'relative',
                  outline: isSelected ? '2px solid var(--ink)' : 'none',
                  outlineOffset: 3,
                  borderRadius: 999,
                }}>
                  {isLogged
                    ? <MiniOrb balance={cell.balance} label={String(cell.d)} size={isNarrow ? 42 : 82} isToday={isToday} tdee={tdee} />
                    : <EmptyCalendarDay label={String(cell.d)} isToday={isToday} size={isNarrow ? 42 : 82} />}
                </div>
              </button>
            );
          })}
        </div>

        {/* legend */}
        <div style={{ marginTop: 'auto', paddingTop: 12, display: 'flex', alignItems: 'center', gap: isNarrow ? 10 : 18, flexWrap: 'wrap' }}>
          <span className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.10em', textTransform: 'uppercase' }}>legend</span>
          <LegendDot color="var(--rust)" label="gaining" />
          <LegendDot color="var(--amber)" label="keeping" />
          <LegendDot color="var(--moss)" label="losing" />
          <LegendDot color="transparent" label="N/A empty" />
          <span style={{ flex: 1 }} />
          <span className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)' }}>fluid level = caloric balance</span>
        </div>
      </div>

      {/* RIGHT — selected day + stats */}
      <aside style={{
        borderLeft: isNarrow ? 'none' : '1px solid var(--hair)',
        borderTop: isNarrow ? '1px solid var(--hair)' : 'none',
        padding: isNarrow ? '18px' : '20px 28px 28px',
        display: 'flex', flexDirection: 'column', gap: 20, overflow: 'auto',
      }}>
        <div>
          <SectionEyebrow left={selected.dow.toLowerCase() + ' · ' + selected.label.toLowerCase()} />
          <div style={{ display: 'flex', alignItems: 'center', gap: 18, marginTop: 12 }}>
            {selected.intakeSource === 'logged' && Number.isFinite(selected.balance)
              ? <FluidOrb days={[selected]} size={132} showMarkers={false} animate={false} tdee={tdee} />
              : <InsufficientData label="N/A" detail="No total logged" />}
            <div>
              <div style={{
                fontFamily: "'Instrument Serif', serif", fontSize: 40, lineHeight: 1,
                fontStyle: 'italic', color: selected.intakeSource === 'logged' && Number.isFinite(selected.balance) ? ZONES[classify(selected.balance, tdee)].hex : 'var(--ink-3)',
              }}>{selected.intakeSource === 'logged' && Number.isFinite(selected.balance) ? `${classify(selected.balance, tdee)}.` : 'N/A'}</div>
              <div className="mono num-tab" style={{ fontSize: 13, color: 'var(--ink-2)', marginTop: 6 }}>
                {selected.intakeSource === 'logged' && Number.isFinite(selected.intake) ? `${selected.intake.toLocaleString()} kcal intake` : 'No logged intake'}
              </div>
              <div className="mono num-tab" style={{ fontSize: 13, color: 'var(--ink-3)', marginTop: 2 }}>
                {selected.intakeSource === 'logged' && Number.isFinite(selected.balance) ? `${selected.balance > 0 ? '+' : ''}${selected.balance} vs maintenance` : 'Not included in summaries'}
              </div>
            </div>
          </div>
        </div>

        <div>
          <SectionHead title="coverage" right={`${loggedHistory.length} logged`} />
          <div style={{
            background: 'var(--paper-2)', border: '1px solid var(--hair)',
            borderRadius: 12, padding: '14px', marginTop: 8,
            display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 10,
          }}>
            <MiniSummary label="logged" value={loggedHistory.length} unit="days" />
            <MiniSummary label="coverage" value={`${coveragePct}%`} unit="range" />
            <MiniSummary label="window" value={windowDays} unit="days" />
          </div>
        </div>

        {/* weekly avg trend */}
        <div>
          <SectionHead title="weekly avg" right="logged days" />
          <div style={{
            background: 'var(--paper-2)', border: '1px solid var(--hair)',
            borderRadius: 12, padding: '14px', marginTop: 8,
          }}>
            <WeeklyBars weeks={weeks} tdee={tdee} />
          </div>
        </div>

        <div>
          <SectionHead title="streaks" />
          <div style={{
            background: 'var(--paper-2)', border: '1px solid var(--hair)',
            borderRadius: 12, padding: '14px', marginTop: 8,
            display: 'grid', gap: 10,
          }}>
            {['gaining', 'keeping', 'losing'].map((zone) => (
              <ZoneStreakRow key={zone} zone={zone} streak={streaks[zone]} />
            ))}
          </div>
        </div>
      </aside>
    </div>
  );
}

function LegendDot({ color, label }) {
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
      <span style={{ width: 8, height: 8, borderRadius: 99, background: color, border: color === 'transparent' ? '1px dashed var(--hair-strong)' : 'none' }} />
      <span style={{ fontSize: 12, color: 'var(--ink-2)' }}>{label}</span>
    </span>
  );
}

function EmptyCalendarDay({ label, isToday, size = 82 }) {
  return (
    <div style={{
      width: size, height: size, borderRadius: 999,
      border: `1.5px ${isToday ? 'solid' : 'dashed'} var(--hair-strong)`,
      background: 'var(--paper-2)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      flexDirection: 'column', gap: 2,
      color: 'var(--ink-3)',
    }}>
      <span className="mono num-tab" style={{ fontSize: 16, color: 'var(--ink-2)' }}>{label}</span>
      <span className="mono" style={{ fontSize: 9, letterSpacing: '0.08em' }}>N/A</span>
    </div>
  );
}

function MiniSummary({ label, value, unit }) {
  return (
    <div>
      <div className="mono" style={{ fontSize: 10, color: 'var(--ink-3)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>{label}</div>
      <div className="mono num-tab" style={{ marginTop: 5, fontSize: 22, color: 'var(--ink)' }}>{value}</div>
      <div className="mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{unit}</div>
    </div>
  );
}

function getZoneStreaks(days, tdee) {
  const streaks = {
    gaining: { count: 0, startLabel: '' },
    keeping: { count: 0, startLabel: '' },
    losing: { count: 0, startLabel: '' },
  };
  const current = {
    gaining: { count: 0, startLabel: '' },
    keeping: { count: 0, startLabel: '' },
    losing: { count: 0, startLabel: '' },
  };
  days.forEach((day) => {
    const dayZone = day.intakeSource === 'logged' && Number.isFinite(day.balance)
      ? classify(day.balance, tdee)
      : null;
    Object.keys(current).forEach((zone) => {
      if (dayZone === zone) {
        if (current[zone].count === 0) current[zone].startLabel = day.label || displayDate(day.date);
        current[zone].count += 1;
        if (current[zone].count > streaks[zone].count) {
          streaks[zone] = { ...current[zone] };
        }
      } else {
        current[zone] = { count: 0, startLabel: '' };
      }
    });
  });
  return streaks;
}

function ZoneStreakRow({ zone, streak }) {
  const color = ZONES[zone].hex;
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '58px 1fr', gap: 10, alignItems: 'baseline' }}>
      <span style={{
        fontFamily: "'Instrument Serif', serif", fontSize: 36, lineHeight: 1,
        fontStyle: 'italic', color,
      }}>{streak.count}</span>
      <div>
        <div style={{ fontSize: 13.5, color: 'var(--ink-2)' }}>{zone} days</div>
        <div className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', marginTop: 2 }}>
          {streak.count ? `started ${streak.startLabel.toLowerCase()}` : 'N/A'}
        </div>
      </div>
    </div>
  );
}

function WeeklyBars({ weeks, tdee = TDEE }) {
  // each bar: vertical, anchored at center line. up = gaining (rust), down = losing (moss).
  const H = 80;
  const max = Math.max(800, ...weeks.map((w) => Math.abs(w.avg || 0))) * 1.1;
  return (
    <div style={{ position: 'relative', height: H, display: 'flex', alignItems: 'stretch', gap: 14 }}>
      {/* center axis */}
      <div style={{ position: 'absolute', left: 0, right: 0, top: '50%', height: 1, background: 'var(--hair-strong)' }} />
      {weeks.map((w, i) => {
        if (w.avg == null) {
          return (
            <div key={i} style={{ flex: 1, position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
              <div className="mono" style={{ fontSize: 11, color: 'var(--ink-3)' }}>N/A</div>
              <div className="mono" style={{ position: 'absolute', bottom: -16, fontSize: 10, color: 'var(--ink-3)' }}>w{i + 1}</div>
            </div>
          );
        }
        const zone = classify(w.avg, tdee);
        const color = ZONES[zone].hex;
        const h = Math.abs(w.avg) / max * (H / 2);
        const top = w.avg >= 0 ? H / 2 - h : H / 2;
        return (
          <div key={i} style={{ flex: 1, position: 'relative', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-end' }}>
            <div style={{
              position: 'absolute', top, left: '50%', transform: 'translateX(-50%)',
              width: 18, height: h, background: color, borderRadius: 4,
            }} />
            <div className="mono" style={{ position: 'absolute', bottom: -16, fontSize: 10, color: 'var(--ink-3)' }}>w{i + 1}</div>
            <div className="mono num-tab" style={{
              position: 'absolute', top: w.avg >= 0 ? top - 14 : top + h + 2,
              fontSize: 10, color: 'var(--ink-2)',
            }}>{w.avg > 0 ? '+' : ''}{Math.round(w.avg)}</div>
          </div>
        );
      })}
    </div>
  );
}


// ═══════════════════════════════════════════════════════════════
// INSIGHTS TAB
// ═══════════════════════════════════════════════════════════════

function WebInsights({ scenario, tdee = TDEE, windowDays = 3, userData, unitSystem = 'metric' }) {
  const isNarrow = useIsNarrow();
  const days = scenario.days;
  const loggedDays = days.filter((day) => day.intakeSource === 'logged' && Number.isFinite(day.balance));
  const hasLoggedDays = loggedDays.length > 0;
  const reading = hasLoggedDays ? getBalanceReading(days, windowDays, tdee) : null;
  const zone = reading?.state || 'keeping';
  const avg = reading?.averageBalance ?? null;
  const accent = hasLoggedDays ? ZONES[zone].hex : 'var(--ink-3)';

  // 4-week projection based on current 3-day avg
  // weeklyKg = avg * 7 / 7700
  const weeklyKg = hasLoggedDays ? avg * 7 / 7700 : null;
  const weeklyDisplay = hasLoggedDays && unitSystem === 'imperial' ? weeklyKg * 2.2046226218 : weeklyKg;
  const proj4w = hasLoggedDays ? (weeklyDisplay * 4).toFixed(2) : 'N/A';
  const projSign = hasLoggedDays && weeklyKg >= 0 ? '+' : '';
  const startWeight = userData?.profile?.weightKg || PROFILE.weight;
  const startWeightDisplay = displayWeightValue(startWeight, unitSystem);
  const endWeight = hasLoggedDays ? displayWeightValue(startWeight + weeklyKg * 4, unitSystem).toFixed(1) : 'N/A';
  const weightLabel = weightUnit(unitSystem);

  const insightHistory = scenario.dailyTotalOnly
    ? loggedDays.map((day) => ({ ...day, dateObj: parseEntryDate(day) }))
    : HISTORY.map((day) => ({ ...day, dateObj: day.date }));

  // Day-of-week pattern from logged history where available.
  const dowBins = Array(7).fill(null).map(() => []);
  insightHistory.forEach((d) => {
    const dow = (d.dateObj.getDay() + 6) % 7; // Mon=0
    dowBins[dow].push(d.balance);
  });
  const dowAvg = dowBins.map((b) => b.reduce((s, x) => s + x, 0) / (b.length || 1));
  const hasPatternData = insightHistory.length >= 7;
  const loggedAvg = loggedDays.length ? loggedDays.reduce((sum, day) => sum + day.balance, 0) / loggedDays.length : null;
  const bestKeepingRun = getBestKeepingRun(days, tdee);

  return (
    <div style={{ height: isNarrow ? 'auto' : '100%', overflow: 'auto', padding: isNarrow ? '18px' : '20px 40px 28px', display: 'flex', flexDirection: 'column', gap: 18 }}>
      {/* Projection hero */}
      <div style={{
        background: 'var(--paper-2)', border: '1px solid var(--hair)',
        borderRadius: 16, padding: isNarrow ? '18px' : '24px 28px', display: 'grid',
        gridTemplateColumns: isNarrow ? '1fr' : '1.1fr 1fr', gap: 24,
      }}>
        <div>
          <SectionEyebrow left="projection · next 4 weeks" />
          <div style={{
            fontFamily: "'Instrument Serif', serif", fontSize: isNarrow ? 34 : 44, lineHeight: 1.08,
            color: 'var(--ink)', marginTop: 12,
          }}>
            {hasLoggedDays ? <>at this pace, in <span style={{ color: accent, fontStyle: 'italic' }}>four weeks</span>{' '}
            you'll be{' '}</> : <>projection is{' '}</>}
            <span className="mono num-tab" style={{ fontFamily: "'JetBrains Mono', monospace", fontSize: isNarrow ? 34 : 44, color: accent, fontStyle: 'normal' }}>
              {endWeight}
            </span>
            {hasLoggedDays && <span style={{ color: 'var(--ink-3)', fontSize: 24 }}> {weightLabel}</span>}.
          </div>
          <div style={{ marginTop: 10, fontSize: 13.5, color: 'var(--ink-2)', lineHeight: 1.5, maxWidth: 380 }}>
            {hasLoggedDays
              ? <>That's {projSign}{proj4w} {weightLabel} from today's {startWeightDisplay} {weightLabel} — assuming the recent logged days continue.
                This is a simple estimate from calorie balance, not a recommendation.</>
              : <>N/A until at least one daily total is logged.</>}
          </div>
        </div>
        <div>
          {hasLoggedDays
            ? <ProjectionChart startKg={startWeightDisplay} weeklyKg={weeklyDisplay} weeks={4} accent={accent} />
            : <InsufficientData label="N/A" detail="No logged totals" />}
        </div>
      </div>

      {/* insight cards */}
      <div style={{ display: 'grid', gridTemplateColumns: isNarrow ? '1fr' : 'repeat(3, 1fr)', gap: 14 }}>
        <InsightCard
          title="day of week"
          eyebrow={hasPatternData ? "patterns · logged days" : "patterns · collecting"}
        >
          {hasPatternData ? <DowChart dowAvg={dowAvg} tdee={tdee} /> : <InsufficientData label="N/A" detail="7 logged days needed" />}
        </InsightCard>

        <InsightCard
          title={scenario.dailyTotalOnly ? "daily totals" : "meal timing"}
          eyebrow={scenario.dailyTotalOnly ? "daily totals" : "demo meal clock"}
        >
          {scenario.dailyTotalOnly ? <DailyTotalSummary days={loggedDays} tdee={tdee} /> : <MealClock meals={TODAY_MEALS} />}
        </InsightCard>

        <InsightCard
          title="observations"
          eyebrow={hasPatternData ? "observations" : "limited data"}
        >
          <ObsList>
            <Obs hi={hasLoggedDays ? `${loggedDays.length}d` : 'N/A'} text="logged so far." color="var(--amber)" />
            <Obs hi={loggedAvg == null ? 'N/A' : `${loggedAvg > 0 ? '+' : ''}${Math.round(loggedAvg)}`} text="kcal/day average on logged days." color={accent} />
            <Obs hi={hasLoggedDays ? `${bestKeepingRun}d` : 'N/A'} text="best keeping run." color="var(--moss)" />
          </ObsList>
        </InsightCard>
      </div>
    </div>
  );
}

function getBestKeepingRun(days, tdee) {
  let best = 0;
  let current = 0;
  days.forEach((day) => {
    if (day.intakeSource === 'logged' && Number.isFinite(day.balance) && classify(day.balance, tdee) === 'keeping') {
      current++;
      best = Math.max(best, current);
    } else {
      current = 0;
    }
  });
  return best;
}

function ProjectionChart({ startKg, weeklyKg, weeks, accent }) {
  const W = 380, H = 200;
  const padL = 36, padR = 12, padT = 18, padB = 28;
  const innerW = W - padL - padR, innerH = H - padT - padB;
  // y range ±2 kg from start
  const yMin = startKg - 2, yMax = startKg + 2;
  const yScale = (kg) => padT + (yMax - kg) / (yMax - yMin) * innerH;
  const xScale = (w) => padL + (w / weeks) * innerW;

  // historical line: last 4 weeks held flat-ish (just decorative based on weeklyKg / 3)
  const hist = [];
  for (let w = -4; w <= 0; w++) {
    hist.push({ w, kg: startKg - (weeklyKg / 3) * (w + 4) });
  }
  const proj = [];
  for (let w = 0; w <= weeks; w++) {
    proj.push({ w, kg: startKg + weeklyKg * w });
  }
  // Map historical x onto same scale (-4..0 → leftmost 40% of innerW)
  const histX = (w) => padL + ((w + 4) / 4) * (innerW * 0.42);
  const projX = (w) => padL + innerW * 0.42 + (w / weeks) * (innerW * 0.58);

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: 'block' }}>
      {/* axes */}
      <line x1={padL} x2={W - padR} y1={H - padB} y2={H - padB} stroke="var(--hair-strong)" />
      <line x1={padL} x2={W - padR} y1={yScale(startKg)} y2={yScale(startKg)} stroke="var(--hair-strong)" strokeDasharray="2 4" />
      {/* divider between past and projected */}
      <line x1={padL + innerW * 0.42} x2={padL + innerW * 0.42} y1={padT} y2={H - padB} stroke="var(--hair-strong)" strokeDasharray="2 4" />

      {/* y labels */}
      {[yMax, startKg, yMin].map((kg, i) => (
        <text key={i} x={padL - 6} y={yScale(kg) + 3} textAnchor="end" fontSize="9.5"
          fontFamily="JetBrains Mono, monospace" fill="var(--ink-3)">{kg.toFixed ? kg.toFixed(1) : kg}</text>
      ))}

      {/* x labels */}
      <text x={padL + innerW * 0.42} y={H - padB + 14} textAnchor="middle" fontSize="9.5"
        fontFamily="JetBrains Mono, monospace" fill="var(--ink-3)">today</text>
      <text x={padL + innerW * 0.42 + innerW * 0.58} y={H - padB + 14} textAnchor="end" fontSize="9.5"
        fontFamily="JetBrains Mono, monospace" fill="var(--ink-3)">+ 4 weeks</text>

      {/* historical (solid grey) */}
      <polyline
        points={hist.map((p) => `${histX(p.w)},${yScale(p.kg)}`).join(' ')}
        fill="none" stroke="var(--ink-2)" strokeWidth="1.6"
      />
      {/* projected (accent dashed → solid gradient) */}
      <polyline
        points={proj.map((p) => `${projX(p.w)},${yScale(p.kg)}`).join(' ')}
        fill="none" stroke={accent} strokeWidth="2.2" strokeDasharray="0"
      />
      {/* endpoint dot */}
      <circle cx={projX(weeks)} cy={yScale(proj[proj.length - 1].kg)} r="5" fill={accent} stroke="var(--paper-2)" strokeWidth="2" />
      <circle cx={projX(0)} cy={yScale(startKg)} r="4" fill="var(--ink)" />
    </svg>
  );
}

function InsightCard({ title, eyebrow, children }) {
  return (
    <div style={{
      background: 'var(--paper-2)', border: '1px solid var(--hair)',
      borderRadius: 14, padding: '18px 18px',
      display: 'flex', flexDirection: 'column', gap: 12,
      minHeight: 220,
    }}>
      <div>
        <div className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', letterSpacing: '0.10em', textTransform: 'uppercase' }}>{eyebrow}</div>
        <div style={{ fontFamily: "'Instrument Serif', serif", fontSize: 26, color: 'var(--ink)', lineHeight: 1, marginTop: 4 }}>{title}</div>
      </div>
      <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        {children}
      </div>
    </div>
  );
}

function DowChart({ dowAvg, tdee = TDEE }) {
  const labels = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
  const max = Math.max(...dowAvg.map(Math.abs)) * 1.15 || 500;
  const H = 110;
  return (
    <div style={{ width: '100%', position: 'relative', height: H, display: 'flex', alignItems: 'stretch', gap: 8 }}>
      <div style={{ position: 'absolute', left: 0, right: 0, top: '50%', height: 1, background: 'var(--hair-strong)' }} />
      {dowAvg.map((v, i) => {
        const zone = classify(v, tdee);
        const color = ZONES[zone].hex;
        const h = Math.abs(v) / max * (H / 2 - 8);
        const top = v >= 0 ? H / 2 - h : H / 2;
        return (
          <div key={i} style={{ flex: 1, position: 'relative' }}>
            <div style={{
              position: 'absolute', top, left: '50%', transform: 'translateX(-50%)',
              width: 16, height: h, background: color, borderRadius: 3,
            }} />
            <div className="mono" style={{
              position: 'absolute', bottom: -2, left: 0, right: 0, textAlign: 'center',
              fontSize: 10, color: 'var(--ink-3)',
            }}>{labels[i]}</div>
          </div>
        );
      })}
    </div>
  );
}

function EmptyDataView({ title, detail }) {
  return (
    <div style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 40 }}>
      <div style={{
        width: 'min(420px, 100%)', minHeight: 180,
        border: '1px dashed var(--hair-strong)', borderRadius: 14,
        display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
        textAlign: 'center', gap: 8, color: 'var(--ink-3)',
      }}>
        <div className="mono" style={{ fontSize: 32, color: 'var(--ink)' }}>N/A</div>
        <div className="mono" style={{ fontSize: 10.5, letterSpacing: '0.10em', textTransform: 'uppercase' }}>{title}</div>
        <div style={{ fontSize: 13, color: 'var(--ink-2)' }}>{detail}</div>
      </div>
    </div>
  );
}

function InsufficientData({ label, detail }) {
  return (
    <div style={{
      width: '100%', height: 130,
      flexDirection: 'column', gap: 6,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      border: '1px dashed var(--hair-strong)', borderRadius: 12,
      color: 'var(--ink-3)',
    }}>
      <span className="mono" style={{ fontSize: 24, color: 'var(--ink-2)' }}>{label}</span>
      {detail && <span className="mono" style={{ fontSize: 10, letterSpacing: '0.06em', textTransform: 'uppercase' }}>{detail}</span>}
    </div>
  );
}

function DailyTotalSummary({ days, tdee }) {
  if (days.length === 0) {
    return <InsufficientData label="N/A" detail="No logged totals" />;
  }
  const max = Math.max(1, ...days.map((day) => Math.abs(day.balance)));
  return (
    <div style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 8 }}>
      {days.map((day, index) => {
        const zone = classify(day.balance, tdee);
        const color = ZONES[zone].hex;
        const width = Math.max(8, Math.abs(day.balance) / max * 100);
        return (
          <div key={index} style={{ display: 'grid', gridTemplateColumns: '52px 1fr 58px', gap: 8, alignItems: 'center' }}>
            <span className="mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{displayDate(day.date)}</span>
            <div style={{ height: 8, background: 'var(--hair-faint)', borderRadius: 99, overflow: 'hidden' }}>
              <div style={{ width: `${width}%`, height: '100%', background: color, borderRadius: 99 }} />
            </div>
            <span className="mono num-tab" style={{ fontSize: 10.5, color: 'var(--ink-2)', textAlign: 'right' }}>
              {day.balance > 0 ? '+' : ''}{day.balance}
            </span>
          </div>
        );
      })}
    </div>
  );
}

function MealClock({ meals }) {
  // 24h ring with dots at meal times
  const cx = 70, cy = 70, R = 56;
  return (
    <svg viewBox="0 0 140 140" width="140" height="140">
      <circle cx={cx} cy={cy} r={R} fill="none" stroke="var(--hair-strong)" strokeWidth="1.2" strokeDasharray="2 4" />
      {/* day/night halves */}
      <path d={`M ${cx} ${cy - R} A ${R} ${R} 0 0 1 ${cx} ${cy + R}`} fill="none" stroke="var(--amber)" strokeWidth="2" opacity="0.6" />
      <path d={`M ${cx} ${cy + R} A ${R} ${R} 0 0 1 ${cx} ${cy - R}`} fill="none" stroke="var(--ink-2)" strokeWidth="2" opacity="0.3" />
      {/* hour ticks */}
      {[0, 6, 12, 18].map((h) => {
        const a = (h / 24) * Math.PI * 2 - Math.PI / 2;
        const x1 = cx + Math.cos(a) * (R - 4), y1 = cy + Math.sin(a) * (R - 4);
        const x2 = cx + Math.cos(a) * (R + 4), y2 = cy + Math.sin(a) * (R + 4);
        const lx = cx + Math.cos(a) * (R + 14), ly = cy + Math.sin(a) * (R + 14);
        return (
          <React.Fragment key={h}>
            <line x1={x1} y1={y1} x2={x2} y2={y2} stroke="var(--ink-2)" strokeWidth="1.4" />
            <text x={lx} y={ly + 3} textAnchor="middle" fontSize="9" fontFamily="JetBrains Mono, monospace" fill="var(--ink-3)">{h}</text>
          </React.Fragment>
        );
      })}
      {meals.map((m, i) => {
        const [hh, mm] = m.time.split(':').map(Number);
        const t = (hh + mm / 60) / 24;
        const a = t * Math.PI * 2 - Math.PI / 2;
        const x = cx + Math.cos(a) * R, y = cy + Math.sin(a) * R;
        const r = Math.max(3, Math.sqrt(m.kcal) / 6);
        return (
          <circle key={i} cx={x} cy={y} r={r} fill="var(--rust)" stroke="var(--paper-2)" strokeWidth="1.4" />
        );
      })}
      <text x={cx} y={cy + 4} textAnchor="middle" fontSize="11" fontFamily="JetBrains Mono, monospace" fill="var(--ink-2)">{meals.length} meals</text>
    </svg>
  );
}

function ObsList({ children }) {
  return <div style={{ display: 'flex', flexDirection: 'column', gap: 10, width: '100%' }}>{children}</div>;
}

function Obs({ hi, text, color }) {
  return (
    <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
      <span className="mono num-tab" style={{
        fontSize: 18, fontWeight: 500, color, minWidth: 46,
      }}>{hi}</span>
      <span style={{ fontSize: 12.5, color: 'var(--ink-2)', lineHeight: 1.4 }}>{text}</span>
    </div>
  );
}

function parseEntryDate(day) {
  if (day.dateObj instanceof Date) return day.dateObj;
  const parsed = new Date(day.date);
  if (!Number.isNaN(parsed.getTime())) return parsed;
  const fallback = new Date();
  fallback.setDate(day.d || fallback.getDate());
  return fallback;
}

Object.assign(window, { WebLog, WebHistory, WebInsights });
