const { useState, useEffect, useRef, useMemo, useCallback } = React;

const BASE_URL = '';

const generateAIContent = async (systemPrompt, userText) => {
  try {
    // Production: call the same-origin proxy. The Anthropic key lives server-side
    // (Cloudflare secret) and the endpoint sits behind the email gate — see
    // functions/api/reflect.js.
    if (typeof window !== 'undefined' && window.OBE_PRODUCTION) {
      const res = await fetch('/api/reflect', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ system: systemPrompt, user: userText }),
      });
      if (!res.ok) throw new Error('reflect request failed');
      const data = await res.json();
      if (!data.text) throw new Error('empty reflection');
      return data.text;
    }
    // Preview / local design work: keep a mock so no key is needed.
    if (window.claude && window.claude.complete) {
      const prompt = systemPrompt + "\n\n" + userText;
      return await window.claude.complete(prompt);
    }
    await new Promise((r) => setTimeout(r, 600));
    return "you logged five days this week. that's the whole job, some weeks. you showed up for the small tries and let go when the day asked it of you.\n\nthe heavy moments outnumbered the light ones, but you noticed both — which is the harder thing on tired days.\n\nwhat you call \"enough\" is what I'd call a boundary held. keep going. you're doing the work that doesn't show.";
  } catch {
    return "the words aren't coming just now. try again in a little while.";
  }
};

// --- 2. THEME & PALETTE ---
// Remapped to Still Orchard palette. Keys preserved so call-sites don't break.
//   parchment = warm cream/brown neutrals
//   sage      = One Bite (amber-brown ochre, --primary)
//   berry     = Enough  (slate-blue,         --secondary)
//   honey     = Insight (soft rose,          --tertiary)
const theme = {
  parchment: { 50: '#fdf9f0', 100: '#f7f3ea', 200: '#ece8df', 300: '#d4c4b7', 400: '#b8a99a', 500: '#82756a', 600: '#6e6258', 700: '#50453b', 800: '#3a2f24', 900: '#1c1c17' },
  ochre: { 50: '#faf2e7', 100: '#f5e4cb', 200: '#f0bd8b', 300: '#d49960', 400: '#9e6e3a', 500: '#7d562d', 600: '#623f18' },
  slate: { 50: '#eef3f8', 100: '#cce2f7', 200: '#b3c9dd', 300: '#88a0b3', 400: '#5f7689', 500: '#4c6172', 600: '#34495a' },
  rose: { 50: '#fbf0ef', 100: '#f5dcdb', 200: '#eabbba', 300: '#b88e8d', 400: '#5f3e3e', 500: '#553636', 600: '#4a2f2f' }
};

// --- Semantic selected-chip styling (Home context panel + Diary Core Context) ---
// Unselected chips are untouched. Selected chips carry a semantic tone:
//   ochre   = the child's world   (Child Mood, Social Intent)
//   rose    = the parent's self   (My Energy)
//   neutral = structural / unbiased (How Did It Feel, Day Type, Factors, Environment)
const SEL_OCHRE = { background: 'rgba(240,189,139,0.14)', border: '0.5px solid rgba(160,104,58,0.35)', color: '#623f18', fontWeight: 600 };
const SEL_ROSE = { background: 'rgba(184,142,141,0.14)', border: '0.5px solid rgba(184,142,141,0.40)', color: '#96615f', fontWeight: 600 };
const SEL_NEUTRAL = { background: 'rgba(130,117,106,0.12)', border: '0.5px solid rgba(107,92,80,0.35)', color: '#3d3028', fontWeight: 600 };
const SEL_CHIP = {
  energy: SEL_ROSE, // My Energy — the parent's self
  mood: SEL_OCHRE, // Child Mood — the child's world
  feel: SEL_NEUTRAL, // How Did It Feel — structural
  dayType: SEL_NEUTRAL, // Day Type — structural
  factor: SEL_NEUTRAL // Factors & Environment — structural
};
// Social Intent uses a card layout but carries the ochre (child's-world) tone.
const SEL_SOCIAL_CARD = { background: 'rgba(240,189,139,0.14)', border: '0.5px solid rgba(160,104,58,0.35)' };

// --- 3. ICONS ---
const Icons = {
  Home: ({ active }) => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke={active ? theme.parchment[900] : theme.parchment[400]} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12 12 3l9 9" /><path d="M5 10v10h14V10" /></svg>,
  Pattern: ({ active }) => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke={active ? theme.parchment[900] : theme.parchment[400]} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 20V10" /><path d="M9 20V4" /><path d="M15 20v-8" /><path d="M21 20v-6" /></svg>,
  Diary: ({ active }) => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke={active ? theme.parchment[900] : theme.parchment[400]} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M4 19V5a2 2 0 0 1 2-2h12v18l-3-2-3 2-3-2-3 2Z" /></svg>,
  Log: () => <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" /></svg>,
  Child: ({ active }) => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke={active ? theme.parchment[900] : theme.parchment[400]} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="8" r="4" /><path d="M4 21a8 8 0 0 1 16 0" /></svg>,
  Me: ({ active }) => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke={active ? theme.parchment[900] : theme.parchment[400]} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="5" r="2.8" /><path d="M5 21v-3a7 7 0 0 1 14 0v3" /><path d="M12 16.5c-1-1.3-3-.8-3 .8 0 1.5 3 3 3 3s3-1.5 3-3c0-1.6-2-2.1-3-.8z" /></svg>,
  Coffee: () => <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke={theme.parchment[400]} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="mb-4"><path d="M17 8h1a4 4 0 1 1 0 8h-1" /><path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z" /><line x1="6" y1="2" x2="6" y2="4" /><line x1="10" y1="2" x2="10" y2="4" /><line x1="14" y1="2" x2="14" y2="4" /></svg>,
  Star: () => <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" /></svg>,
  Strawberry: () => <svg width="48" height="48" viewBox="0 0 24 24" fill={theme.slate[300]} stroke={theme.slate[500]} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M12 22c-4.4 0-8-4.4-8-9 0-4.4 3.6-8 8-8s8 3.6 8 8c0 4.6-3.6 9-8 9z" /><path d="M12 5c-1.7 0-3-1.3-3-3 0-1.7 1.3-3 3-3s3 1.3 3 3c0 1.7-1.3 3-3 3z" fill={theme.ochre[400]} stroke={theme.ochre[600]} /></svg>,
  BackArrow: () => <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke={theme.parchment[600]} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M15 18l-6-6 6-6" /></svg>,
  Plus: () => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><line x1="12" y1="5" x2="12" y2="19" style={{ stroke: "rgb(184, 142, 141)" }} /><line x1="5" y1="12" x2="19" y2="12" /></svg>,
  Edit: () => <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" /><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" /></svg>,
  Settings: () => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#82756a" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" /></svg>,
  Download: () => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" y1="15" x2="12" y2="3" /></svg>
};

// --- 4. TAXONOMY CONFIG ---
const Taxonomy = {
  dayTypes: ['School', 'Home', 'Sick Day', 'Weekend', 'Holiday', 'Travel Day', 'Special Event', 'Caregiver Swap'],
  moods: ['Overtired', 'Wary', 'Assertive', 'Unwell', 'Subdued', 'Restless', 'Hyper', 'Stable', 'Attached', 'Overstimulated', 'Playful', 'Fussy', 'Apathetic', 'Frustrated', 'Curious'],
  environments: ['Home', 'School', 'Outdoor', 'Park', 'Restaurant', 'Cafe', 'Shopping', 'Hosting', 'Visiting', 'Play Center', 'Transit', 'Car', 'Party', 'Clinic'],
  factors: ['No Nap', 'Restless Night', 'Routine Shift', 'Separation Anxiety', 'Sensory Sensitivity', 'Parental Unwell', 'Teething', 'Growth Spurt', 'Illness', 'Allergies', 'Peers', 'Safe Food', 'New Food', 'Screen Time', 'Sugar', 'New People', 'New Place', 'Travel', 'Heatwave']
};

// --- Child Mood clusters ---
// Child Mood is the one selector long enough to be worth grouping: its 15 chips
// split into three felt clusters (bright / stormy / quiet), each under a quiet
// italic-serif label, so the parent scans three short groups instead of one
// wall. Every chip stays visible and one tap to toggle — grouping costs zero
// extra taps. The other selectors (Day Type, factors, environments) stay flat
// lists. Used in both render sites (Diary form + feed "+ add context").
const MOOD_GROUPS = [
{ label: 'Bright', items: ['Stable', 'Playful', 'Curious', 'Assertive'] },
{ label: 'Stormy', items: ['Hyper', 'Restless', 'Overstimulated', 'Frustrated', 'Fussy', 'Overtired'] },
{ label: 'Quiet', items: ['Subdued', 'Apathetic', 'Attached', 'Wary', 'Unwell'] }];

// Returns the clusters, appending any mood not yet placed in a cluster as a
// final "More" group — so adding a new mood to Taxonomy can never make it
// silently disappear from the selector.
const clusterize = (all, groups) => {
  const seen = new Set([].concat.apply([], groups.map((g) => g.items)));
  const leftover = all.filter((x) => !seen.has(x));
  return leftover.length ? groups.concat([{ label: 'More', items: leftover }]) : groups;
};

// Renders labeled chip clusters: a quiet italic-serif label sits above each
// group's wrap-row of chips. The italic serif (the app's reading voice) reads
// clearly distinct from the UPPERCASE Hanken section heading above it.
// `renderChip(item)` keeps each call site's own chip styling/handlers.
function ChipClusters({ all, groups, intraGap, interGap, renderChip }) {
  return (
    // paddingTop separates the first cluster from the section heading (padding,
    // not margin, so it can't collapse with the heading's bottom margin). Labels
    // and chips stay flush with the page's left margin like the other selectors;
    // the gap below the heading plus the italic label style carry the grouping.
    <div style={{ display: 'flex', flexDirection: 'column', gap: interGap, paddingTop: '12px' }}>
      {clusterize(all, groups).map((grp, gi) =>
      <div key={gi}>
        <div style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontStyle: 'italic', fontWeight: 400, fontSize: '13px', lineHeight: 1, textTransform: 'lowercase', color: '#6e6258', marginBottom: '10px' }}>{grp.label}</div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: intraGap }}>
          {grp.items.map(renderChip)}
        </div>
      </div>
      )}
    </div>);
}

// --- 5. MOCK DATA ---
const getInitialEntries = (childName = 'Sun') => [
{ id: 's1', types: ['bite'], childId: 1, sunsMood: ['Playful'], note: 'Ate a piece of cheese at the playdate. The other kids were eating it.', timestamp: new Date(Date.now() - 0.5 * 24 * 60 * 60 * 1000).toISOString(), energy: 'Functional', friction: 'Light', factors: ['Peers'], environment: ['Visit'], socialIntent: 'High', dailyKindness: false },
{ id: 's2', types: ['enough'], childId: 1, sunsMood: ['Subdued'], note: 'Too much noise at the cafe. Refused snack, wanted to sit in my lap.', timestamp: new Date(Date.now() - 2.5 * 24 * 60 * 60 * 1000).toISOString(), energy: 'Low', friction: 'Mixed', factors: ['New Place'], environment: ['Cafe'], socialIntent: 'Observer', dailyKindness: false },
{ id: 's3', types: ['bite'], childId: 1, sunsMood: ['Curious'], note: 'Quiet breakfast alone. Investigated the oatmeal and ate a few spoons.', timestamp: new Date(Date.now() - 3.5 * 24 * 60 * 60 * 1000).toISOString(), energy: 'Max', friction: 'Light', factors: ['Safe Food'], environment: ['Home'], socialIntent: 'Internal', dailyKindness: false },
{ id: 's4', types: ['enough'], childId: 1, sunsMood: ['Frustrated'], note: 'Wanted nothing to do with family dinner. Just pushed peas around.', timestamp: new Date(Date.now() - 5.5 * 24 * 60 * 60 * 1000).toISOString(), energy: 'Low', friction: 'Heavy', factors: ['Routine Shift'], environment: ['Home'], socialIntent: 'Quiet', dailyKindness: true, dailyKindnessNote: 'Had a cup of tea in silence after bedtime.' },
{ id: 'b3', types: ['bite'], childId: 1, sunsMood: ['Playful'], note: 'Tried a slice of kiwi! He made a sour face but then asked for another piece. Big win.', timestamp: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), energy: 'Max', friction: 'Light', factors: ['Sugar'], environment: ['Home'], dailyKindness: false },
{ id: 'e1', types: ['enough'], childId: 1, sunsMood: ['Fussy', 'Overtired'], note: 'Dinner was a disaster. Refused everything except crackers. Letting it go and putting him to bed early.', timestamp: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), energy: 'Low', friction: 'Heavy', factors: ['No Nap'], environment: ['Home'], dailyKindness: false },
{ id: 'b4', types: ['bite'], childId: 1, sunsMood: ['Curious'], note: 'Ate the carrot sticks at the park today! He saw another kid eating them and just joined in.', timestamp: new Date(Date.now() - 4 * 24 * 60 * 60 * 1000).toISOString(), energy: 'Functional', friction: 'Light', factors: ['Peers'], environment: ['Park'], dailyKindness: false },
{ id: 'e2', types: ['enough'], childId: 1, sunsMood: ['Restless'], note: 'Too hot today. Just gave him cold fruit and yogurt for dinner. Didn\'t even try the actual meal.', timestamp: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), energy: 'Functional', friction: 'Mixed', factors: ['Heatwave'], environment: ['Home'], dailyKindness: true, dailyKindnessNote: 'Sat in front of the fan for 15 mins.' },
{ id: 'b1', types: ['bite'], childId: 1, sunsMood: [], note: 'Broccoli — held it without crying. Didn\'t eat it, but touched it to his lips. A huge sensory win.', timestamp: '2026-04-08T18:00:00Z', energy: 'Functional', friction: 'Light', factors: ['New Food'], environment: [], dailyKindness: false },
{ id: 'b2', types: ['bite'], childId: 1, sunsMood: [], note: 'Tofu — swallowed! Disguised it slightly in the soup, but he noticed and still ate it.', timestamp: '2026-04-03T12:00:00Z', energy: 'Max', friction: 'Light', factors: ['New Food'], environment: [], dailyKindness: false },
{ id: '1', types: ['bite'], childId: 1, sunsMood: [], note: 'Curry incident at preschool! Ate everything. Teachers were shocked. Peer influence is real. A massive social win.', timestamp: '2026-04-01T12:00:00Z', energy: 'Functional', friction: 'Light', factors: ['Peers', 'New Place'], environment: ['School'], dailyKindness: true, dailyKindnessNote: 'Coffee shop hour for myself.' },
{ id: '2', types: ['enough'], childId: 1, sunsMood: [], note: 'Toy cleanup battle. Refused tofu. So tired. We are all done for tonight.', timestamp: '2026-03-25T18:30:00Z', energy: 'Low', friction: 'Heavy', factors: ['No Nap'], environment: ['Home'], dailyKindness: false },
{ id: '3', types: ['enough'], childId: 1, sunsMood: [], note: 'Refused chicken. Greek yogurt with raspberries instead. 18g protein. Permission to let go.', timestamp: '2026-03-14T18:00:00Z', energy: 'Functional', friction: 'Mixed', factors: ['Heatwave'], environment: ['Home'], dailyKindness: true, dailyKindnessNote: 'Pilates class.' },
{ id: '4', types: ['bite'], childId: 1, sunsMood: [], note: 'Uncle Tetsu strawberry cheesecake! Used his little fork perfectly. A pink spring omen.', timestamp: '2026-03-01T12:00:00Z', energy: 'Max', friction: 'Light', factors: ['Sugar'], environment: ['Home'], dailyKindness: false },
{ id: 'k1', types: ['enough'], childId: 1, sunsMood: [], note: '', timestamp: '2026-04-05T10:00:00Z', dailyKindness: true, dailyKindnessNote: 'weekly pilates session — shoulders still hurt but showed up', factors: [] },
{ id: 'k2', types: ['bite'], childId: 1, sunsMood: [], note: 'bought a pineapple bun just for me at the cha chaan teng, but Sun ate half of it. a surprising win.', timestamp: '2026-04-05T14:00:00Z', dailyKindness: true, dailyKindnessNote: 'bought a pineapple bun just for me at the cha chaan teng', factors: [] },
{ id: 'k3', types: ['enough'], childId: 1, sunsMood: [], note: '', timestamp: '2026-04-09T09:00:00Z', dailyKindness: true, dailyKindnessNote: 'focused strength training — felt my body getting stronger', factors: [] },
{ id: 'k4', types: ['enough'], childId: 1, sunsMood: [], note: '', timestamp: '2026-04-10T21:00:00Z', dailyKindness: true, dailyKindnessNote: 'quiet hour to myself after Sun\'s bedtime — just sat still', factors: [] }].
map((e) => ({ ...e, note: e.note.replace(/Sun/g, childName) }));

// --- 5. UTILS ---
const formatTime = (iso) => new Date(iso).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }).toLowerCase();

// Child names always render sentence-case (uppercase first letter), even inside lowercase narrative copy.
const capName = (s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : s;

// --- ACCESSIBILITY: status announcer ---
// A single polite live region (rendered once in App, persists across screens)
// carries short status messages to screen readers. We update its textContent
// imperatively: clear first so re-announcing the same message still fires, then
// clear again a moment later so the message doesn't linger in the buffer.
let _announceSetTimer = null;
let _announceClearTimer = null;
const announce = (msg) => {
  if (typeof document === 'undefined') return;
  const el = document.getElementById('ob-live-region');
  if (!el) return;
  el.textContent = '';
  // Set after a short tick so assistive tech registers a change even when the
  // new message is identical to the one just cleared. setTimeout (not rAF) so
  // it still fires when the tab is backgrounded or rendered headlessly.
  if (_announceSetTimer) clearTimeout(_announceSetTimer);
  _announceSetTimer = setTimeout(() => {el.textContent = msg;}, 50);
  if (_announceClearTimer) clearTimeout(_announceClearTimer);
  _announceClearTimer = setTimeout(() => {if (el.textContent === msg) el.textContent = '';}, 1500);
};
// Type-aware "saved" copy, per the entry's One Bite / Enough type.
const savedMsg = (types) =>
types && types.includes('bite') ? 'One bite entry saved.' :
types && types.includes('enough') ? 'Enough entry saved.' :
'Entry saved.';

// --- ACCESSIBILITY: modal focus management ---
// One reusable hook for every role="dialog" surface. On open it moves focus
// into the dialog (first focusable element, else the container itself) and
// remembers what was focused before. While open it traps Tab / Shift+Tab inside
// the dialog (wrapping at both ends) and closes on Escape. On close (by any
// means) it restores focus to the previously-focused element. A small stack
// ensures only the topmost dialog handles keys, so a dialog nested inside
// another (e.g. the remove-profile confirm inside Settings) behaves correctly.
// Usage: const ref = useModalA11y(isOpen, closeFn); then ref on the dialog node.
const _modalStack = [];
const FOCUSABLE_SEL = 'a[href],area[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
function useModalA11y(active, onClose) {
  const ref = useRef(null);
  const tokenRef = useRef(null);
  const closeRef = useRef(onClose);
  closeRef.current = onClose;
  if (!tokenRef.current) tokenRef.current = {};
  useEffect(() => {
    if (!active) return;
    const node = ref.current;
    if (!node) return;
    const token = tokenRef.current;
    const prevFocused = document.activeElement;
    _modalStack.push(token);
    const isTop = () => _modalStack[_modalStack.length - 1] === token;
    const focusables = () =>
    Array.prototype.slice.call(node.querySelectorAll(FOCUSABLE_SEL)).
    filter((el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement);
    // Initial focus: first focusable, else the dialog container.
    const firstEl = focusables()[0];
    if (firstEl) {firstEl.focus();} else {node.setAttribute('tabindex', '-1');node.focus();}
    const onKey = (e) => {
      if (!isTop()) return;
      if (e.key === 'Escape') {
        e.preventDefault();
        if (closeRef.current) closeRef.current();
        return;
      }
      if (e.key !== 'Tab') return;
      const items = focusables();
      if (!items.length) {e.preventDefault();node.focus();return;}
      const first = items[0], last = items[items.length - 1], a = document.activeElement;
      if (e.shiftKey) {
        if (a === first || !node.contains(a)) {e.preventDefault();last.focus();}
      } else {
        if (a === last || !node.contains(a)) {e.preventDefault();first.focus();}
      }
    };
    document.addEventListener('keydown', onKey, true);
    return () => {
      document.removeEventListener('keydown', onKey, true);
      const i = _modalStack.indexOf(token);
      if (i !== -1) _modalStack.splice(i, 1);
      if (prevFocused && typeof prevFocused.focus === 'function') prevFocused.focus();
    };
  }, [active]);
  return ref;
}

const getWeekKey = (date = new Date()) => {
  // Shift time forward by 3 hours. 
  // This makes Sunday 9:00 PM act as Monday 12:00 AM, unlocking the new reflection on Sunday evening.
  const shiftedTimestamp = new Date(date).getTime() + 3 * 60 * 60 * 1000;
  const d = new Date(shiftedTimestamp);

  d.setHours(0, 0, 0, 0);
  d.setDate(d.getDate() + 3 - (d.getDay() + 6) % 7);
  const week1 = new Date(d.getFullYear(), 0, 4);
  const weekNum = 1 + Math.round(((d.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
  return `${d.getFullYear()}-W${String(weekNum).padStart(2, '0')}`;
};

// Deterministic variant picker. Hashes a seed string (week key + card id) to a
// stable index into `variants`, so the same copy holds all week and rotates
// when the week key flips — no stored state. Pools are seeded independently
// (e.g. "…|win-headline" vs "…|win-body") so headline and body mix freely.
const pickVariant = (variants, seed) => {
  let h = 2166136261;
  for (let i = 0; i < seed.length; i++) {
    h ^= seed.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  return variants[(h >>> 0) % variants.length];
};

// Maps each factor to one of five reframe "clusters" so the factor card's body
// copy can speak to the *kind* of hard the week held. Unmapped factors default
// to "routine".
const FACTOR_CLUSTERS = {
  "No Nap": "child-body",
  "Teething": "child-body",
  "Growth Spurt": "child-body",
  "Illness": "child-body",
  "Restless Night": "child-body",
  "Allergies": "child-body",
  "Sensory Sensitivity": "child-body",
  "Parental Unwell": "parent-state",
  "Routine Shift": "routine",
  "Caregiver Swap": "routine",
  "Travel": "routine",
  "Peers": "social-sensory",
  "New People": "social-sensory",
  "New Place": "social-sensory",
  "Separation Anxiety": "social-sensory",
  "Heatwave": "social-sensory",
  "Screen Time": "social-sensory",
  "Sugar": "food",
  "New Food": "food",
  "Safe Food": "food"
};

const formatDate = (iso) => {
  const d = new Date(iso);
  const today = new Date();
  if (d.toDateString() === today.toDateString()) return 'today';
  const yesterday = new Date(today);yesterday.setDate(yesterday.getDate() - 1);
  if (d.toDateString() === yesterday.toDateString()) return 'yesterday';
  return d.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' }).toLowerCase().replace(/,/g, '');
};

const hasType = (entry, typeToCheck) => (entry.types || []).includes(typeToCheck);

// Energy level → number of active bars
const getEnergyLevel = (level) => {
  if (level === 'Max') return 3;
  if (level === 'Functional') return 2;
  if (level === 'Low') return 1;
  return 0;
};

// Three ascending bars; inherit currentColor so they pick up the chip's text tone
function EnergyBars({ level }) {
  const active = getEnergyLevel(level);
  const heights = [5, 7.5, 10];
  return (
    <span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'flex-end', gap: '1.5px', height: '10px' }}>
      {heights.map((h, i) =>
      <span key={i} style={{ width: '2.5px', height: h + 'px', borderRadius: '1px', background: 'currentColor', opacity: i < active ? 0.85 : 0.25 }} />
      )}
    </span>);
}

// Bars + label, with the 6px gap the spec calls for
function EnergyLabel({ level }) {
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: '6px' }}>
      <EnergyBars level={level} />
      <span>{level}</span>
    </span>);
}

// --- SCORING ENGINE ---
const FEEL_SCORES = { 'Light': 10, 'Mixed': 5, 'Heavy': 0 };

const getFeelTier = (score) => {
  if (score === null || isNaN(score)) return null;
  if (score >= 7) return 'lighter';
  if (score >= 5) return 'mixed';
  if (score >= 2) return 'heavier';
  return 'heavy';
};

const getWeeklyFeelScore = (entries) => {
  const validEntries = entries.filter((e) => e.friction && FEEL_SCORES[e.friction] !== undefined);
  if (validEntries.length === 0) return null;
  const sum = validEntries.reduce((acc, e) => acc + FEEL_SCORES[e.friction], 0);
  const score = sum / validEntries.length;
  return {
    score,
    tier: getFeelTier(score),
    count: validEntries.length
  };
};

const getEnergyCorrelation = (entries) => {
  // 1. Filter to entries that have BOTH energy and a feel score
  const validEntries = entries.filter((e) => e.energy && e.friction && FEEL_SCORES[e.friction] !== undefined);

  if (validEntries.length < 3) {
    return { hasEnoughData: false };
  }

  // 2. Group by energy
  const highEnergyEntries = validEntries.filter((e) => e.energy === 'Max' || e.energy === 'Functional');
  const lowEnergyEntries = validEntries.filter((e) => e.energy === 'Low');

  // Helper to calculate stats for a group
  const calcStats = (group) => {
    if (group.length === 0) return { score: null, tier: null, count: 0, hasDirectional: false };
    const sum = group.reduce((acc, e) => acc + FEEL_SCORES[e.friction], 0);
    // Check if there are any non-Mixed entries to provide a true directional signal
    const hasDirectional = group.some((e) => e.friction === 'Light' || e.friction === 'Heavy');
    const score = sum / group.length;
    return { score, tier: getFeelTier(score), count: group.length, hasDirectional };
  };

  const highStats = calcStats(highEnergyEntries);
  const lowStats = calcStats(lowEnergyEntries);

  let correlationStrength = "none";

  // 3. Determine Correlation Strength
  if (
  highStats.count >= 2 &&
  lowStats.count >= 2 &&
  highStats.score !== null &&
  lowStats.score !== null && (
  highStats.hasDirectional || lowStats.hasDirectional) // Exclude purely mixed weeks
  ) {
    const diff = highStats.score - lowStats.score;
    if (diff >= 3) correlationStrength = "strong";else
    if (diff >= 1.5) correlationStrength = "moderate";else
    if (diff > 0) correlationStrength = "weak";
  }

  return {
    hasEnoughData: true,
    highEnergyFeelScore: highStats.score,
    highEnergyFeelTier: highStats.tier,
    highEnergyEntryCount: highStats.count,
    lowEnergyFeelScore: lowStats.score,
    lowEnergyFeelTier: lowStats.tier,
    lowEnergyEntryCount: lowStats.count,
    correlationStrength
  };
};

// --- Child identity tints (replaces the old emoji avatars) ---
// Six on-palette identity colors, spread across the hue wheel so they stay
// distinct at small sizes (dots/chips): warm terracotta + gold, cool blue +
// teal, pink rose + plum. Muted enough for the gallery feel, separated enough
// to read apart.
const CHILD_TINTS = ['#a9684a', '#b39a3c', '#4f6f93', '#3f8378', '#c08089', '#8a5a7e'];
// Legacy emoji → tint, preserving the order of the retired picker so existing
// children keep a stable, predictable color after the migration.
const LEGACY_EMOJI_TINT = { '☀️': '#a9684a', '⭐': '#b39a3c', '🌙': '#4f6f93', '☁️': '#3f8378', '🌸': '#c08089', '🐻': '#8a5a7e' };
function tintFor(child) {
  if (!child) return CHILD_TINTS[0];
  if (child.tint) return child.tint;
  if (child.emoji && LEGACY_EMOJI_TINT[child.emoji]) return LEGACY_EMOJI_TINT[child.emoji];
  const n = Math.abs(String(child.id || '').split('').reduce((a, c) => a + c.charCodeAt(0), 0));
  return CHILD_TINTS[n % CHILD_TINTS.length];
}
const tintWash = (tint) => `color-mix(in oklab, ${tint} 14%, #fdf9f0)`;

// child initial in a tinted circle — the standard avatar
function ChildAvatar({ child, size = 40, style = {} }) {
  const tint = tintFor(child);
  const initial = capName(child?.name || '·').charAt(0) || '·';
  return (
    <span style={{ width: size, height: size, borderRadius: '50%', background: tintWash(tint), border: `1px solid ${tint}`, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, ...style }}>
      <span style={{ fontFamily: "'Fraunces', Georgia, serif", fontWeight: 500, fontSize: Math.round(size * 0.42), color: tint, lineHeight: 1 }}>{initial}</span>
    </span>);

}
// small filled dot in the child's tint — for inline pills/chips
function TintDot({ child, size = 9, style = {} }) {
  return <span style={{ width: size, height: size, borderRadius: '50%', background: tintFor(child), display: 'inline-block', flexShrink: 0, ...style }} />;
}
// tint chooser row — onboarding + child form
function TintPicker({ value, onChange }) {
  return (
    <div className="flex items-center justify-center" style={{ gap: 14, padding: '4px 0' }}>
      {CHILD_TINTS.map((c) =>
      <button key={c} type="button" onClick={() => onChange(c)} aria-label={`avatar color ${c}`}
      style={{ width: 26, height: 26, borderRadius: '50%', background: c, border: 'none', cursor: 'pointer', padding: 0, boxShadow: value === c ? `0 0 0 2px #f7f3ea, 0 0 0 3.5px ${c}` : 'none', opacity: value === c ? 1 : 0.38, transition: 'opacity 120ms, box-shadow 120ms' }} />
      )}
    </div>);

}

// Stable onboarding text field (module-scope so it isn't remounted on every
// keystroke — defining it inside render drops input focus after one char).
function OBField({ label, ...rest }) {
  return (
    <div style={{ marginBottom: 26 }}>
      <div style={{ fontFamily: "'Hanken Grotesk', system-ui, sans-serif", fontSize: 10, fontWeight: 600, letterSpacing: '0.16em', textTransform: 'uppercase', color: '#6e6258', marginBottom: 10 }}>{label}</div>
      <input {...rest} className="ob2-in" style={{ width: '100%', border: 'none', borderBottom: '1px solid #d4c4b7', background: 'transparent', outline: 'none', fontFamily: "'Fraunces', Georgia, serif", fontWeight: 400, fontSize: 25, color: '#1c1c17', padding: '0 0 10px', boxSizing: 'border-box' }} />
    </div>);

}

// --- 6. ONBOARDING SCREEN ---
function OnboardingScreen({ onComplete }) {
  const [step, setStep] = useState(0);
  const [parentName, setParentName] = useState('');
  const [childName, setChildName] = useState('');
  const [childDob, setChildDob] = useState('');
  const [childTint, setChildTint] = useState(CHILD_TINTS[0]);
  const [challenge, setChallenge] = useState(null);

  const OB = { floor: '#f7f3ea', cream: '#fdf9f0', ink: '#1c1c17', muted: '#50453b', hint: '#6e6258', line: '#d4c4b7', ochre: '#7d562d', slate: '#4c6172' };
  const FR = "'Fraunces', Georgia, serif",SS = "'Source Serif 4', Georgia, serif",HK = "'Hanken Grotesk', system-ui, sans-serif";

  const challenges = ['the daily push & pull', 'running on empty', 'carrying it mostly alone', 'the little wins', 'just here to notice'];

  const nextStep = () => setStep((s) => Math.min(5, s + 1));
  const prevStep = () => setStep((s) => Math.max(0, s - 1));
  const completeFlow = () => onComplete({ parentName, childName, childDob, childTint, challenge });

  const eyebrow = { fontFamily: HK, fontSize: 10, fontWeight: 600, letterSpacing: '0.16em', textTransform: 'uppercase', color: OB.hint };
  const Arrow = ({ c = OB.ink }) => <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 6l6 6-6 6" /></svg>;

  const Placard = ({ ch, title }) =>
  <div className="flex items-center" style={{ gap: 10 }}>
      <span style={{ fontFamily: HK, fontSize: 10, fontWeight: 700, letterSpacing: '0.18em', color: OB.ochre }}>{ch}</span>
      <span style={{ width: 16, height: 1, background: OB.line }} />
      <span style={{ fontFamily: HK, fontSize: 10, fontWeight: 600, letterSpacing: '0.18em', color: OB.hint, textTransform: 'uppercase' }}>{title}</span>
    </div>;

  const Display = ({ children, size = 34, mb = 26 }) => <h2 style={{ fontFamily: FR, fontWeight: 500, fontSize: size, lineHeight: 1.12, letterSpacing: '-0.01em', color: OB.ink, margin: `0 0 ${mb}px`, textWrap: 'pretty' }}>{children}</h2>;
  const Continue = ({ label, onClick, disabled }) =>
  <button onClick={disabled ? undefined : onClick} disabled={disabled} style={{ width: '100%', background: 'none', border: 'none', padding: 0, cursor: disabled ? 'default' : 'pointer', opacity: disabled ? 0.32 : 1, transition: 'opacity 200ms' }}>
      <div className="flex items-center justify-between" style={{ borderTop: `1px solid ${OB.ink}`, paddingTop: 16 }}>
        <span style={{ fontFamily: HK, fontSize: 12, fontWeight: 600, letterSpacing: '0.1em', textTransform: 'uppercase', color: OB.ink }}>{label}</span>
        <Arrow />
      </div>
    </button>;


  return (
    <div className="absolute inset-0 flex flex-col" style={{ background: OB.floor, zIndex: 9999 }}>
      <style dangerouslySetInnerHTML={{ __html: `
        .ob2-fade { animation: ob2fade .36s cubic-bezier(0.32,0.08,0.24,1) both; }
        @keyframes ob2fade { from { opacity:0; transform: translateY(6px); } to { opacity:1; transform:none; } }
        .ob2-in::placeholder { color:#d4c4b7; font-style:italic; }
        .ob2-in { caret-color:#7d562d; }
        .ob2-in.ob2-date::-webkit-calendar-picker-indicator { opacity:0.35; cursor:pointer; }
        .no-scrollbar::-webkit-scrollbar { display:none; }
        .no-scrollbar { -ms-overflow-style:none; scrollbar-width:none; }
      ` }} />

      {/* ---------- STEP 0 · WELCOME ---------- */}
      {step === 0 &&
      <div key="s0" className="ob2-fade flex flex-col flex-1" style={{ padding: '0 28px' }}>
        <div className="flex flex-col items-center justify-center flex-1" style={{ position: 'relative' }}>
          <div style={{ position: 'absolute', top: 40, bottom: 24, left: 14, width: 1, background: OB.line, opacity: 0.55 }} />
          <div style={{ position: 'absolute', top: 40, bottom: 24, right: 14, width: 1, background: OB.line, opacity: 0.55 }} />
          <div style={{ ...eyebrow, marginBottom: 32, textAlign: 'center' }}>for the long days</div>
          <div style={{ textAlign: 'center' }}>
            <div style={{ fontFamily: FR, fontWeight: 500, fontSize: 50, lineHeight: 1.0, letterSpacing: '-0.01em', color: OB.ochre }}>one bite</div>
            <div style={{ fontFamily: FR, fontStyle: 'italic', fontWeight: 400, fontSize: 42, color: OB.slate, margin: '2px 0' }}>/</div>
            <div style={{ fontFamily: FR, fontWeight: 500, fontSize: 50, lineHeight: 1.0, letterSpacing: '-0.01em', color: OB.slate }}>enough</div>
          </div>
          <p style={{ fontFamily: SS, fontSize: 16, lineHeight: 1.6, color: OB.muted, textAlign: 'center', margin: '36px 16px 0', maxWidth: 280, textWrap: 'pretty' }}>
            every day holds a little of both. this is where you keep them.
          </p>
        </div>
        <div style={{ padding: '0 0 34px' }}>
          <button onClick={nextStep} className="flex items-center justify-center" style={{ width: '100%', gap: 10, border: `1px solid ${OB.ink}`, borderRadius: 9999, padding: 15, background: 'none', cursor: 'pointer' }}>
            <span style={{ fontFamily: HK, fontSize: 12, fontWeight: 600, letterSpacing: '0.12em', textTransform: 'uppercase', color: OB.ink }}>enter</span>
            <Arrow />
          </button>
        </div>
      </div>}

      {/* ---------- STEPS 1–5 share the chapter frame ---------- */}
      {step > 0 &&
      <div className="flex flex-col flex-1" style={{ padding: '0 28px', minHeight: 0 }}>
        <div className="flex items-center" style={{ gap: 14, paddingTop: 26 }}>
          <button onClick={prevStep} aria-label="back" style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer', display: 'flex' }}>
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={OB.hint} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M15 18l-6-6 6-6" /></svg>
          </button>
          {step === 1 && <Placard ch="CH. 01" title="you" />}
          {step === 2 && <Placard ch="CH. 02" title="the one you watch" />}
          {step === 3 && <Placard ch="CH. 03" title="where it's tender" />}
          {step === 4 && <Placard ch="CH. 04" title="a promise" />}
          {step === 5 && <Placard ch="CH. 05" title="how it works" />}
        </div>

        <div className="flex-1 overflow-y-auto no-scrollbar flex flex-col" style={{ justifyContent: 'flex-start', paddingTop: step === 5 ? 0 : 38, minHeight: 0 }}>
          {step === 1 &&
          <div key="s1" className="ob2-fade">
            <Display size={35}>what should I call you?</Display>
            <OBField label="your name" type="text" value={parentName} onChange={(e) => setParentName(e.target.value)} placeholder="your name" maxLength={40} autoFocus />
            <p style={{ fontFamily: SS, fontSize: 14, lineHeight: 1.55, color: OB.hint, margin: 0 }}>your weekly reflection will greet you by name.</p>
          </div>}

          {step === 2 &&
          <div key="s2" className="ob2-fade">
            <div className="flex items-center" style={{ gap: 18, marginBottom: 28 }}>
              <ChildAvatar child={{ name: childName, tint: childTint }} size={64} />
              <div>
                <div style={{ ...eyebrow, marginBottom: 12 }}>their color</div>
                <div className="flex items-center" style={{ gap: 11 }}>
                  {CHILD_TINTS.map((c) =>
                  <button key={c} type="button" onClick={() => setChildTint(c)} aria-label="avatar color"
                  style={{ width: 22, height: 22, borderRadius: '50%', background: c, border: 'none', padding: 0, cursor: 'pointer', boxShadow: childTint === c ? `0 0 0 2px ${OB.floor}, 0 0 0 3.5px ${c}` : 'none', opacity: childTint === c ? 1 : 0.38, transition: 'opacity 120ms, box-shadow 120ms' }} />
                  )}
                </div>
              </div>
            </div>
            <OBField label="name" type="text" value={childName} onChange={(e) => setChildName(e.target.value)} placeholder="name" maxLength={40} />
            <div style={{ marginBottom: 26 }}>
              <div style={{ ...eyebrow, marginBottom: 10 }}>born</div>
              <input type="date" value={childDob} onChange={(e) => setChildDob(e.target.value)} className="ob2-in ob2-date" style={{ width: '100%', border: 'none', borderBottom: `1px solid ${OB.line}`, background: 'transparent', outline: 'none', fontFamily: FR, fontWeight: 400, fontSize: 22, color: childDob ? OB.ink : OB.line, padding: '0 0 10px', boxSizing: 'border-box' }} />
            </div>
            <p style={{ fontFamily: SS, fontSize: 14, lineHeight: 1.55, color: OB.hint, margin: 0 }}>you can add another child, anytime.</p>
          </div>}

          {step === 3 &&
          <div key="s3" className="ob2-fade">
            <Display size={31} mb={8}>where shall we be gentle?</Display>
            <p style={{ fontFamily: SS, fontSize: 14, color: OB.hint, margin: '0 0 22px' }}>no wrong answer — you can change this anytime.</p>
            <div>
              {challenges.map((o, i) => {
                const on = challenge === o;
                return (
                  <button key={o} type="button" onClick={() => setChallenge(on ? null : o)} className="flex items-baseline" style={{ width: '100%', gap: 14, padding: '12px 0', background: 'none', border: 'none', borderBottom: '0.5px solid rgba(212,196,183,0.55)', textAlign: 'left', cursor: 'pointer' }}>
                    <span style={{ fontFamily: HK, fontSize: 10, fontWeight: 700, letterSpacing: '0.1em', color: on ? OB.ochre : OB.line, width: 18, flexShrink: 0 }}>{`0${i + 1}`}</span>
                    <span style={{ fontFamily: FR, fontWeight: on ? 500 : 400, fontSize: 18, color: on ? OB.ink : OB.muted }}>{o}</span>
                  </button>);

              })}
            </div>
          </div>}

          {step === 4 &&
          <div key="s4" className="ob2-fade">
            <Display size={35} mb={22}>what you write stays yours.</Display>
            <p style={{ fontFamily: SS, fontSize: 16, lineHeight: 1.64, color: OB.muted, margin: 0, textWrap: 'pretty' }}>
              your notes stay on your device. to write your weekly reflection, a quiet assistant reads that week's notes — nothing is shared, nothing is sold. you're always in control.
            </p>
          </div>}

          {step === 5 &&
          <div key="s5" className="ob2-fade flex flex-col" style={{ flex: 1 }}>
            <div style={{ paddingTop: 18, paddingBottom: 22 }}>
              <Display size={27} mb={0}>two words. that's the whole thing.</Display>
            </div>
            <div className="flex" style={{ gap: 22, flex: 1, height: "630px" }}>
              <div style={{ flex: 1 }}>
                <div style={{ fontFamily: FR, fontWeight: 500, fontSize: 23, color: OB.ochre, marginBottom: 12 }}>one bite</div>
                <p style={{ fontFamily: SS, fontSize: 13.5, lineHeight: 1.56, color: OB.muted, margin: 0, textWrap: 'pretty' }}>a small step into the new — a first taste, a new playground, a word never said before. thirty seconds braver than yesterday.</p>
              </div>
              <div style={{ width: 1, background: OB.line }} />
              <div style={{ flex: 1 }}>
                <div style={{ fontFamily: FR, fontWeight: 500, fontSize: 23, color: OB.slate, marginBottom: 12 }}>enough</div>
                <p style={{ fontFamily: SS, fontSize: 13.5, lineHeight: 1.56, color: OB.muted, margin: 0, textWrap: 'pretty' }}>knowing when to stop pushing — putting the plate away, ending the hard moment, letting today be hard and okay.</p>
              </div>
            </div>
          </div>}
        </div>

        <div style={{ padding: '18px 0 34px' }}>
          {step === 1 && <Continue label="continue" onClick={nextStep} disabled={parentName.trim().length === 0} />}
          {step === 2 && <Continue label="continue" onClick={nextStep} disabled={childName.trim().length === 0 || childDob.trim().length === 0} />}
          {step === 3 && <Continue label="continue" onClick={nextStep} />}
          {step === 4 && <Continue label="i understand" onClick={nextStep} />}
          {step === 5 && <Continue label="enter" onClick={completeFlow} />}
        </div>
      </div>}
    </div>);

}

// --- 7. MODALS (CHILD & SETTINGS) ---
function ChildFormModal({ close, saveChild, initialChild }) {
  const isEditing = !!initialChild;
  const dialogRef = useModalA11y(true, close);
  const [name, setName] = useState(initialChild ? initialChild.name : '');
  const [dob, setDob] = useState(initialChild ? initialChild.dob : '');
  const [tint, setTint] = useState(initialChild ? tintFor(initialChild) : CHILD_TINTS[0]);
  const canSave = name.trim() && dob.trim();

  const handleSave = () => {
    saveChild({ id: initialChild?.id || Date.now(), name: capName(name.trim()), dob, tint });
    close();
  };

  const FR = "'Fraunces', Georgia, serif",HK = "'Hanken Grotesk', system-ui, sans-serif";
  const labelS = { fontFamily: HK, fontSize: 10, fontWeight: 600, letterSpacing: '0.14em', textTransform: 'uppercase', color: '#6e6258', marginBottom: 9 };
  const fieldS = { width: '100%', border: 'none', borderBottom: '1px solid #d4c4b7', background: 'transparent', outline: 'none', fontFamily: FR, fontWeight: 400, color: '#3a2f24', padding: '0 0 9px', boxSizing: 'border-box' };

  return (
    <div className="absolute inset-0 z-50 flex flex-col justify-end animate-in fade-in" style={{ zIndex: 10000 }}>
      <div className="absolute inset-0" style={{ background: 'rgba(28,28,23,0.20)' }} onClick={close}></div>
      <div ref={dialogRef} className="relative animate-in slide-in-from-bottom-full duration-300 flex flex-col" role="dialog" aria-modal="true" aria-label={isEditing ? 'edit child' : 'add a child'} style={{ background: '#fdf9f0', borderTopLeftRadius: 18, borderTopRightRadius: 18, boxShadow: '0 -8px 30px rgba(28,28,23,0.10)', height: "500px", padding: "10px 24px 26px" }}>
        <div style={{ width: 44, height: 5, borderRadius: 3, background: '#ece8df', margin: '0 auto 20px' }}></div>
        <div className="flex flex-col items-center" style={{ marginBottom: 24 }}>
          <ChildAvatar child={{ name, tint }} size={58} />
          <div style={{ marginTop: 16 }}><TintPicker value={tint} onChange={setTint} /></div>
        </div>
        <div style={{ marginBottom: 22 }}>
          <div style={labelS}>name</div>
          <input className="ob2-in" type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="name" maxLength={40} autoFocus={!isEditing} style={{ ...fieldS, fontSize: 23 }} />
        </div>
        <div style={{ marginBottom: 22 }}>
          <div style={labelS}>born</div>
          <input className="ob2-in ob2-date" type="date" value={dob} onChange={(e) => setDob(e.target.value)} style={{ ...fieldS, fontSize: 21, color: dob ? '#3a2f24' : '#d4c4b7', padding: "0px 0px 9px" }} />
        </div>
        <button onClick={canSave ? handleSave : undefined} disabled={!canSave} style={{ width: '100%', background: 'none', border: 'none', padding: 0, marginTop: 'auto', cursor: canSave ? 'pointer' : 'default', opacity: canSave ? 1 : 0.32, transition: 'opacity 200ms' }}>
          <div className="flex items-center justify-between" style={{ borderTop: '1px solid #1c1c17', paddingTop: 15 }}>
            <span style={{ fontFamily: HK, fontSize: 12, fontWeight: 600, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#1c1c17' }}>{isEditing ? 'save changes' : 'add child'}</span>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#1c1c17" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
          </div>
        </button>
      </div>
    </div>);

}

function SettingsModal({ close, parentName, setParentName, childrenConfig, openAddChild, openEditChild, deleteChild, clearData, exportEntries }) {
  const [editingName, setEditingName] = useState(false);
  const [tempName, setTempName] = useState(parentName);
  const [confirmClear, setConfirmClear] = useState(false);
  const [confirmRemove, setConfirmRemove] = useState(null);
  const [showPrivacy, setShowPrivacy] = useState(false);
  const settingsRef = useModalA11y(true, close);
  const confirmRemoveRef = useModalA11y(!!confirmRemove, () => setConfirmRemove(null));
  const privacyRef = useModalA11y(showPrivacy, () => setShowPrivacy(false));

  const handleNameSave = () => {
    if (tempName.trim()) setParentName(tempName.trim());
    setEditingName(false);
  };

  const handleClear = () => {
    clearData();
    close();
  };

  const FR = "'Fraunces', Georgia, serif",SS = "'Source Serif 4', Georgia, serif",HK = "'Hanken Grotesk', system-ui, sans-serif";
  const lblS = { fontFamily: HK, fontSize: 13.5, color: '#50453b', fontWeight: 500, whiteSpace: 'nowrap' };
  const valS = { fontFamily: SS, fontSize: 15, color: '#3a2f24' };
  const Placard = ({ children }) =>
  <div style={{ fontFamily: HK, fontSize: 11, fontWeight: 600, letterSpacing: '0.14em', textTransform: 'uppercase', color: '#6e6258', whiteSpace: 'nowrap' }}>{children}</div>;

  const Row = ({ children, last, onClick }) =>
  <div
    onClick={onClick}
    role={onClick ? 'button' : undefined}
    tabIndex={onClick ? 0 : undefined}
    onKeyDown={onClick ? (e) => {
      // Activate on Enter / Space, but only when the row itself is focused —
      // not when the key bubbles up from a nested control (e.g. the per-child
      // remove button), which has its own handler.
      if ((e.key === 'Enter' || e.key === ' ') && e.target === e.currentTarget) {e.preventDefault();onClick(e);}
    } : undefined}
    className="flex items-center justify-between" style={{ padding: '14px 0', borderBottom: last ? 'none' : '0.5px solid #d4c4b7', cursor: onClick ? 'pointer' : 'default' }}>{children}</div>;

  const ChevR = () => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#d4c4b7" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M9 6l6 6-6 6" /></svg>;
  const DownI = () => <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#82756a" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3v12M7 11l5 5 5-5M5 21h14" /></svg>;

  return (
    <div ref={settingsRef} className="absolute inset-0 z-50 flex flex-col animate-in fade-in duration-300" role="dialog" aria-modal="true" aria-label="settings" style={{ background: '#fdf9f0', zIndex: 9999 }}>
      <div className="flex items-baseline justify-between shrink-0" style={{ padding: '58px 22px 16px', borderBottom: '0.5px solid #d4c4b7' }}>
        <span style={{ fontFamily: FR, fontWeight: 500, fontSize: 22, color: '#1c1c17' }}>settings</span>
        <button onClick={close} style={{ fontFamily: HK, fontSize: 12, fontWeight: 600, letterSpacing: '0.04em', color: '#6e6258', background: 'none', border: 'none', cursor: 'pointer' }}>done</button>
      </div>
      <div className="flex-1 overflow-y-auto" style={{ padding: '0 22px 28px' }}>
        {/* profile */}
        <div style={{ marginTop: 22 }}>
          <Placard>your profile</Placard>
          <div style={{ marginTop: 6 }}>
            <Row last onClick={() => {if (!editingName) {setTempName(parentName);setEditingName(true);}}}>
              <span style={lblS}>your name</span>
              {editingName ?
              <input autoFocus type="text" value={tempName} onChange={(e) => setTempName(e.target.value)} onBlur={handleNameSave} onKeyDown={(e) => {if (e.key === 'Enter') handleNameSave();}} maxLength={40} className="ob2-in" style={{ flex: 1, marginLeft: 20, textAlign: 'right', border: 'none', borderBottom: '0.5px solid #d4c4b7', background: 'transparent', outline: 'none', fontFamily: SS, fontSize: 15, color: '#3a2f24', padding: '0 0 2px' }} /> :
              <span className="flex items-center" style={{ gap: 10 }}><span style={{ ...valS, color: parentName ? '#3a2f24' : '#82756a', fontStyle: parentName ? 'normal' : 'italic' }}>{parentName || 'add your name'}</span><span style={{ color: '#6e6258', display: 'flex' }}><Icons.Edit /></span></span>}
            </Row>
          </div>
        </div>
        {/* children */}
        <div style={{ marginTop: 30 }}>
          <Placard>your children</Placard>
          <div style={{ marginTop: 6 }}>
            {childrenConfig.map((c) =>
            <Row key={c.id} onClick={() => openEditChild(c)}>
              <span className="flex items-center" style={{ gap: 12 }}><ChildAvatar child={c} size={28} /><span style={valS}>{capName(c.name)}</span></span>
              <span className="flex items-center" style={{ gap: 14 }}>
                {childrenConfig.length > 1 &&
                <button onClick={(e) => {e.stopPropagation();setConfirmRemove(c);}} aria-label="remove this profile" style={{ background: 'none', border: 0, padding: 0, cursor: 'pointer', display: 'flex' }}>
                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#82756a" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6" /></svg>
                </button>}
                <ChevR />
              </span>
            </Row>)}
            <Row last onClick={openAddChild}>
              <span className="flex items-center" style={{ gap: 8, color: '#7d562d' }}><Icons.Plus /><span style={{ fontFamily: HK, fontSize: 13.5, fontWeight: 600, color: '#7d562d', whiteSpace: 'nowrap' }}>add another child</span></span>
              <span></span>
            </Row>
          </div>
        </div>
        {/* data */}
        <div style={{ marginTop: 30 }}>
          <Placard>your data</Placard>
          <div style={{ marginTop: 6 }}>
            <Row onClick={exportEntries}><span style={lblS}>export entries</span><DownI /></Row>
            <Row last onClick={() => {if (confirmClear) {handleClear();} else {setConfirmClear(true);setTimeout(() => setConfirmClear(false), 3000);}}}>
              <span style={{ fontFamily: HK, fontSize: 13.5, fontWeight: 500, color: '#a8675a' }}>{confirmClear ? 'tap again to clear everything' : 'clear all data'}</span>
              <span></span>
            </Row>
          </div>
        </div>
        {/* privacy */}
        <div style={{ marginTop: 30 }}>
          <Placard>privacy</Placard>
          <div style={{ marginTop: 6 }}>
            <Row last onClick={() => setShowPrivacy(true)}><span style={lblS}>how your notes are used</span><ChevR /></Row>
          </div>
        </div>
        {/* about */}
        <div style={{ marginTop: 30 }}>
          <Placard>about</Placard>
          <div style={{ marginTop: 6 }}>
            <Row last><span style={lblS}>version</span><span style={{ fontFamily: HK, fontSize: 13, color: '#6e6258' }}>1.0.0</span></Row>
          </div>
        </div>
      </div>
      {confirmRemove &&
      <div className="absolute inset-0 flex items-center justify-center animate-in fade-in" style={{ background: 'rgba(28,28,23,0.18)', padding: '24px', zIndex: 10001 }} onClick={() => setConfirmRemove(null)}>
        <div ref={confirmRemoveRef} onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-label="remove this profile?" style={{ background: '#ffffff', borderRadius: 12, border: '0.5px solid #d4c4b7', padding: '22px 20px 18px', width: '100%', maxWidth: 300 }}>
          <p style={{ fontFamily: FR, fontSize: 17, fontWeight: 400, color: '#1c1c17', textAlign: 'center', margin: '0 0 6px', letterSpacing: '-0.01em' }}>remove this profile?</p>
          <p style={{ fontFamily: SS, fontSize: 13, lineHeight: 1.5, color: '#6e6258', textAlign: 'center', margin: '0 0 18px' }}>{capName(confirmRemove.name)}'s entries and patterns are let go too. this can't be undone.</p>
          <div className="flex" style={{ gap: 8 }}>
            <button onClick={() => setConfirmRemove(null)} className="flex-1 transition-all active:scale-[0.98]" style={{ padding: 11, borderRadius: 8, border: '0.5px solid #d4c4b7', background: 'transparent', cursor: 'pointer' }}>
              <span style={{ fontFamily: HK, fontSize: 10, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase', color: '#50453b' }}>keep</span>
            </button>
            <button onClick={() => {deleteChild(confirmRemove.id);setConfirmRemove(null);}} className="flex-1 transition-all active:scale-[0.98]" style={{ padding: 11, borderRadius: 8, border: '0.5px solid rgba(168,103,90,0.5)', background: 'transparent', cursor: 'pointer' }}>
              <span style={{ fontFamily: HK, fontSize: 10, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase', color: '#a8675a' }}>remove</span>
            </button>
          </div>
        </div>
      </div>}
      {showPrivacy &&
      <div className="absolute inset-0 z-50 flex flex-col justify-end animate-in fade-in" style={{ zIndex: 10001 }}>
        <div className="absolute inset-0" style={{ background: 'rgba(28,28,23,0.20)' }} onClick={() => setShowPrivacy(false)}></div>
        <div ref={privacyRef} className="relative animate-in slide-in-from-bottom-full duration-300" role="dialog" aria-modal="true" aria-labelledby="privacy-sheet-title" style={{ background: '#fdf9f0', borderTopLeftRadius: 18, borderTopRightRadius: 18, padding: '10px 24px 30px', boxShadow: '0 -8px 30px rgba(28,28,23,0.10)' }}>
          <div style={{ width: 44, height: 5, borderRadius: 3, background: '#ece8df', margin: '0 auto 22px' }}></div>
          <h2 id="privacy-sheet-title" style={{ fontFamily: FR, fontWeight: 400, fontSize: 24, color: '#1c1c17', margin: '0 0 18px', letterSpacing: '-0.01em' }}>what you write stays yours.</h2>
          <p style={{ fontFamily: SS, fontSize: 15, lineHeight: 1.64, color: '#50453b', margin: '0 0 14px', textWrap: 'pretty' }}>your entries stay on your device — yours to keep, export, or let go, anytime.</p>
          <p style={{ fontFamily: SS, fontSize: 15, lineHeight: 1.64, color: '#50453b', margin: '0 0 14px', textWrap: 'pretty' }}>to write your weekly reflection, a quiet assistant reads that week's notes — only to find the words. nothing is shared, nothing is sold.</p>
          <p style={{ fontFamily: SS, fontSize: 15, lineHeight: 1.64, color: '#50453b', margin: 0, textWrap: 'pretty' }}>you're always in control.</p>
          <button onClick={() => setShowPrivacy(false)} className="w-full transition-all active:scale-[0.98]" style={{ marginTop: 26, padding: 14, borderRadius: 10, border: '0.5px solid #d4c4b7', background: 'transparent', cursor: 'pointer' }}>
            <span style={{ fontFamily: HK, fontSize: 11, fontWeight: 600, letterSpacing: '0.06em', textTransform: 'uppercase', color: '#50453b' }}>close</span>
          </button>
        </div>
      </div>}
    </div>);

}

// --- 8. MAIN APP COMPONENTS ---

function TopBar({ activeChild }) {
  const d = new Date();
  const dateLabel = `${d.toLocaleDateString('en-US', { weekday: 'long' }).toLowerCase()} · ${d.toLocaleDateString('en-US', { month: 'long' }).toLowerCase()} ${d.getDate()}`;
  return (
    <div className="px-5 shrink-0">
      <ScreenHeader
        eyebrow={dateLabel}
        title="one bite / enough"
        caption={`${activeChild.name}'s log`} />
      
    </div>);

}

function Composer({ addEntry, childName, childId, childrenConfig, setActiveChildId }) {
  const [note, setNote] = useState('');

  const post = (type) => {
    if (!note.trim()) return;
    addEntry({
      id: Math.random().toString(36).substr(2, 9),
      childId: childId,
      types: [type], note: note.trim(), timestamp: new Date().toISOString(),
      energy: null, friction: null, sunsMood: [], dayType: null, factors: [], environment: [], socialIntent: null, dailyKindness: false
    });
    setNote('');
  };

  return (
    <div className="p-3 pb-3 border-b" style={{ ...{ borderColor: theme.parchment[300], padding: "20px 15px" }, borderColor: "rgba(212, 196, 183, 0.55)", borderBottomWidth: "0.5px", padding: "0px 20px 20px" }}>
<div className="rounded-xl border overflow-hidden bg-white transition-all duration-300 flex flex-col" style={{ borderColor: "rgba(212, 196, 183, 0.55)", borderWidth: "0.5px", backgroundColor: "rgb(254, 252, 248)", boxShadow: "0 2px 12px rgba(28, 28, 23, 0.06)" }}>

{/* Top Child Switcher */}
{childrenConfig && childrenConfig.length > 1 &&
        <div className="flex items-center gap-2 px-3 py-2 bg-[#fdf9f0]" style={{ backgroundColor: "rgb(254, 252, 248)" }}>
<span className="text-[11px] uppercase tracking-widest font-semibold shrink-0" style={{ color: theme.parchment[600] }}>for</span>
<div className="flex gap-1.5 overflow-x-auto no-scrollbar">
{childrenConfig.map((c) =>
            <button
              key={c.id}
              onClick={() => setActiveChildId(c.id)}
              className={`flex items-center gap-1 px-2 py-1 rounded-full text-[11px] font-medium border transition-colors whitespace-nowrap shrink-0 ${
              c.id === childId ?
              'bg-[#faf2e7] text-[#7d562d] border-[#f0bd8b]' :
              'bg-white text-[#6e6258] border-[#d4c4b7]'}`
              }>
              
<TintDot child={c} size={8} />
<span>{c.name.split(' ')[0]}</span>
</button>
            )}
</div>
</div>
        }

<textarea
          className="w-full text-sm p-3 min-h-[60px] outline-none resize-none bg-transparent"
          style={{ color: theme.parchment[900], borderRadius: "6px", height: "100px" }}
          aria-label={`what just happened with ${childName}?`}
          placeholder={`what just happened with ${childName}?`}
          value={note}
          maxLength={1500}
          onChange={(e) => setNote(e.target.value)} />
        

<div className="border-t" style={{ borderColor: theme.parchment[200] }}>
<div className="flex items-center justify-end p-2 pt-1 flex-wrap" style={{ borderRadius: "0px" }}>
<div className="flex gap-2 mt-1">
<button
                disabled={!note.trim()} onClick={() => post('bite')}
                className="composer-cta composer-cta-bite px-4 py-1.5 text-xs whitespace-nowrap"
                style={{ backgroundColor: note.trim() ? "rgba(240,189,139,0.25)" : "rgba(130,117,106,0.07)", color: note.trim() ? "#623f18" : "#d4c4b7", fontWeight: note.trim() ? 600 : 500, borderRadius: "8px", border: "0", transition: "background 0.2s ease, color 0.2s ease" }}>
                
one bite
</button>
<button
                disabled={!note.trim()} onClick={() => post('enough')}
                className="composer-cta composer-cta-enough px-4 py-1.5 text-xs whitespace-nowrap"
                style={{ backgroundColor: note.trim() ? "rgba(76,97,114,0.18)" : "rgba(130,117,106,0.07)", color: note.trim() ? "#2d4455" : "#d4c4b7", fontWeight: note.trim() ? 600 : 500, borderRadius: "8px", border: "0", transition: "background 0.2s ease, color 0.2s ease" }}>
                
enough
</button>
</div>
</div>
</div>
</div>
</div>);

}

function EntryCard({ entry, onUpdate, onDelete, childrenConfig, isFirst, isLast }) {
  const [expanded, setExpanded] = useState(false);
  const [showOtherFactors, setShowOtherFactors] = useState(false);

  // Swipe-to-delete state
  const [swipeX, setSwipeX] = useState(0);
  const [isSwiping, setIsSwiping] = useState(false);
  const [showDelete, setShowDelete] = useState(false);
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
  const deleteDialogRef = useModalA11y(showDeleteConfirm, () => setShowDeleteConfirm(false));
  const pointerStartX = useRef(null);
  const pointerStartY = useRef(null);

  const isBite = hasType(entry, 'bite');
  const isEnough = hasType(entry, 'enough');
  const isInsightOnly = hasType(entry, 'insight') && !isBite && !isEnough;

  const accent = isBite ? theme.ochre : isEnough ? theme.slate : theme.rose;
  const toggleArr = (arr, val) => (arr || []).includes(val) ? (arr || []).filter((x) => x !== val) : [...(arr || []), val];

  const child = childrenConfig?.length > 1 ? childrenConfig.find((c) => c.id === entry.childId) : null;
  const entryChild = childrenConfig?.find((c) => c.id === entry.childId);

  if (!entry.note && !(entry.sunsMood || []).length && !(entry.factors || []).length && !(entry.environment || []).length) return null;

  return (
    <>
      <div className="relative overflow-hidden">
        {/* Delete background — revealed on swipe */}
        <div className="absolute inset-y-0 right-0 flex items-center justify-center transition-all duration-200" style={{ width: showDelete ? '80px' : '0px', overflow: 'hidden', background: '#e7d3cc' }}>
          <button
            onClick={() => setShowDeleteConfirm(true)}
            className="flex flex-col items-center justify-center gap-1 w-full h-full active:bg-[#dcc0b6]" style={{ color: '#a8675a' }}>
            
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
            <polyline points="3 6 5 6 21 6" /><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" /><path d="M10 11v6" /><path d="M14 11v6" /><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
          </svg>
          <span className="text-[11px] font-medium">delete</span>
        </button>
      </div>

      {/* The row itself — slides left on swipe using Pointer Events for touch + mouse support */}
      <div
          className={'ob-row transition-transform duration-200 relative touch-pan-y ob-' + (isBite ? 'bite' : isEnough ? 'enough' : 'insight') + (isFirst ? ' ob-first' : '') + (isLast ? ' ob-last' : '') + (expanded ? ' ob-expanded' : '')}
          style={{
            transform: `translateX(-${swipeX}px)`,
            touchAction: 'pan-y'
          }}
          onPointerDown={(e) => {
            if (e.target.closest('button')) return;
            pointerStartX.current = e.clientX;
            pointerStartY.current = e.clientY;
            setIsSwiping(false);
          }}
          onPointerMove={(e) => {
            if (pointerStartX.current === null) return;
            const deltaX = pointerStartX.current - e.clientX;
            const deltaY = Math.abs(pointerStartY.current - e.clientY);

            if (deltaY > 10 && !isSwiping) {
              pointerStartX.current = null;
              return;
            }

            if (deltaX > 5 || deltaX < -5 || isSwiping) {
              setIsSwiping(true);
              const clamped = Math.max(0, Math.min(deltaX, 80));
              setSwipeX(clamped);
              setShowDelete(clamped > 40);
            }
          }}
          onPointerUp={(e) => {
            if (pointerStartX.current !== null) {
              if (swipeX > 40) {
                setSwipeX(80);
                setShowDelete(true);
              } else {
                setSwipeX(0);
                setShowDelete(false);
              }
              pointerStartX.current = null;
              setTimeout(() => setIsSwiping(false), 0);
            }
          }}
          onPointerCancel={() => {
            setSwipeX(0);
            setShowDelete(false);
            pointerStartX.current = null;
            setIsSwiping(false);
          }}
          onClickCapture={(e) => {
            if (swipeX > 0) {
              e.preventDefault();
              e.stopPropagation();
              setSwipeX(0);
              setShowDelete(false);
            }
          }}>
          
        {/* Top row: time upper-left, child emoji upper-right — both in the cream, outside the card */}
        <div className="flex justify-between items-center gap-2 mb-2" style={{ margin: "0px 0px 10px" }}>
          <span className="ec-time" style={{ padding: "3px 0px 0px" }}>{formatTime(entry.timestamp)}</span>
          {entryChild && childrenConfig?.length > 1 && <span className="ec-child" style={{ borderStyle: "solid" }}><TintDot child={entryChild} size={7} style={{ marginRight: 1 }} />{capName(entryChild.name)}</span>}
        </div>

        {/* White card: the entry text + its context tags */}
        <div className="ec-card" style={{ borderRadius: "6px", borderWidth: "1px", padding: "20px", margin: "15px 0px 0px", backgroundColor: "rgb(255, 255, 255)" }}>
          {entry.note && (() => {
              const sections = entry.note.split('\n\n');
              const DIARY_LABELS = ['Meals', 'Noticed', 'Check-in'];
              const isStructured = sections.some((s) => {
                const ci = s.indexOf(':');
                return ci > -1 && DIARY_LABELS.includes(s.slice(0, ci).trim());
              });
              if (isStructured) {
                return (
                  <div style={{ padding: "4px 0px" }}>
                  {sections.map((section, i) => {
                      const colonIdx = section.indexOf(':');
                      const rawLabel = colonIdx > -1 ? section.slice(0, colonIdx).trim() : null;
                      const isLabel = rawLabel && DIARY_LABELS.includes(rawLabel);
                      const content = isLabel ? section.slice(colonIdx + 1).trim() : section;
                      return (
                        <div key={i} className={i > 0 ? 'mt-3' : ''}>
                        {isLabel && <div style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: 10, fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase', color: theme.parchment[600], marginBottom: 4 }}>{rawLabel}</div>}
                        <p className="whitespace-pre-wrap" style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: 15, lineHeight: 1.5, color: theme.parchment[900], textWrap: 'pretty', margin: 0 }}>{content}</p>
                      </div>);
                    })}
                </div>);
              }
              return (
                <p className="whitespace-pre-wrap" style={{ fontSize: 15, lineHeight: 1.5, color: theme.parchment[900], textWrap: 'pretty', fontFamily: "'Source Serif 4', Georgia, serif", margin: "0px", padding: "0px 0px 10px" }}>
                {entry.note}
              </p>);
            })()}

          {(entry.dayType || entry.energy || (entry.sunsMood || []).length > 0 || entry.friction || (entry.environment || []).length > 0 || entry.socialIntent || (entry.factors || []).length > 0) &&
            <div className="flex flex-wrap items-center gap-1.5 mt-3" style={{ margin: "10px 0px 0px" }}>
              {entry.energy && <span className="ec-tag"><EnergyLabel level={entry.energy} /></span>}
              {entry.dayType && <span className="ec-tag">{entry.dayType}</span>}
              {entry.friction && <span className="ec-tag">{entry.friction}</span>}
              {entry.sunsMood && entry.sunsMood.map((m) => <span key={m} className="ec-tag"><span aria-hidden="true" style={{ borderRadius: 9999, background: theme.ochre[200], flexShrink: 0, width: "7px", height: "7px" }}></span>{m}</span>)}
              {entry.socialIntent && <span className="ec-tag"><span aria-hidden="true" style={{ borderRadius: 9999, background: theme.ochre[200], flexShrink: 0, width: "7px", height: "7px" }}></span>{entry.socialIntent}</span>}
              {entry.environment && entry.environment.filter((env) => env !== entry.dayType).map((env) => <span key={env} className="ec-tag">{env}</span>)}
              {entry.factors?.map((f) => <span key={f} className="ec-tag">{f}</span>)}
            </div>
            }
        </div>

        <button
            onClick={(e) => {
              if (isSwiping) {e.preventDefault();return;}
              if (expanded) setShowOtherFactors(false); // Resets the nested factors menu when closing
              setExpanded(!expanded);
            }}
            className="ec-add" style={{ margin: "10px 0px 0px 2px" }}>
            
          {expanded ? '– hide context' : '+ add context'}
        </button>

                  {expanded &&
          <div className="mt-2 p-3 rounded-lg border space-y-4 animate-in fade-in" style={{ backgroundColor: theme.parchment[50], borderColor: theme.parchment[200], borderRadius: "6px" }}>
                      <div>
                        <span className="text-[11px] uppercase tracking-widest font-semibold mb-1.5 block" style={{ color: theme.parchment[600] }}>My Energy</span>
                        <div className="flex flex-wrap gap-1">
                          {['Max', 'Functional', 'Low'].map((e) =>
                <button key={e} onClick={() => onUpdate(entry.id, { energy: entry.energy === e ? null : e })}
                className={`px-2 py-1 rounded-md border text-[11px] transition-colors ${entry.energy === e ? '' : 'bg-transparent border-[#d4c4b7] text-[#50453b]'}`} style={entry.energy === e ? SEL_CHIP.energy : undefined}>
                              <EnergyLabel level={e} />
                            </button>
                )}
                        </div>
                      </div>
                      <div>
                        <span className="text-[11px] uppercase tracking-widest font-semibold mb-1.5 block" style={{ color: theme.parchment[600] }}>Child Mood</span>
                        <ChipClusters all={Taxonomy.moods} groups={MOOD_GROUPS} intraGap="4px" interGap="10px" renderChip={(m) =>
                <button key={m} onClick={() => onUpdate(entry.id, { sunsMood: toggleArr(entry.sunsMood, m) })}
                className={`px-2 py-1 rounded-md border text-[11px] transition-colors ${(entry.sunsMood || []).includes(m) ? '' : 'bg-transparent border-[#d4c4b7] text-[#50453b]'}`} style={(entry.sunsMood || []).includes(m) ? SEL_CHIP.mood : undefined}>{m}</button>
                } />
                      </div>
                      <div>
                        <span className="text-[11px] uppercase tracking-widest font-semibold mb-1.5 block" style={{ color: theme.parchment[600] }}>How Did It Feel</span>
                        <div className="flex flex-wrap gap-1">
                          {['Heavy', 'Mixed', 'Light'].map((f) =>
                <button key={f} onClick={() => onUpdate(entry.id, { friction: entry.friction === f ? null : f })}
                className={`px-2 py-1 rounded-md border text-[11px] transition-colors ${entry.friction === f ? '' : 'bg-transparent border-[#d4c4b7] text-[#50453b]'}`} style={entry.friction === f ? SEL_CHIP.feel : undefined}>{f}</button>
                )}
                        </div>
                      </div>

                      <button
              onClick={() => setShowOtherFactors(!showOtherFactors)}
              className="text-[11px] font-bold uppercase tracking-widest flex items-center justify-center gap-1 mt-3 w-full p-2 rounded-lg transition-colors border"
              style={{ backgroundColor: 'transparent', color: theme.parchment[600], borderColor: theme.parchment[200] }}>
              
                        {showOtherFactors ? '- other factors' : '+ other factors'}
                      </button>

                      {showOtherFactors &&
            <div className="space-y-4 pt-1 animate-in fade-in">
                          <div>
                            <span className="text-[11px] uppercase tracking-widest font-semibold mb-1.5 block" style={{ color: theme.parchment[600] }}>Environment</span>
                            <div className="flex flex-wrap gap-1">
                              {Taxonomy.environments.map((env) =>
                  <button key={env} onClick={() => onUpdate(entry.id, { environment: toggleArr(entry.environment, env) })}
                  className={`px-2 py-1 rounded-md border text-[11px] transition-colors ${(entry.environment || []).includes(env) ? '' : 'bg-transparent border-[#d4c4b7] text-[#50453b]'}`} style={(entry.environment || []).includes(env) ? SEL_CHIP.factor : undefined}>{env}</button>
                  )}
                            </div>
                          </div>
                          <div>
                            <span className="text-[11px] uppercase tracking-widest font-semibold mb-1.5 block" style={{ color: theme.parchment[600] }}>Factors</span>
                            <div className="flex flex-wrap gap-1">
                              {Taxonomy.factors.map((f) =>
                  <button key={f} onClick={() => onUpdate(entry.id, { factors: toggleArr(entry.factors || [], f) })} className={`px-3 py-1.5 rounded-md border text-[11px] transition-colors ${(entry.factors || []).includes(f) ? '' : 'bg-transparent border-[#d4c4b7] text-[#50453b]'}`} style={(entry.factors || []).includes(f) ? SEL_CHIP.factor : undefined}>{f}</button>
                  )}
                            </div>
                          </div>
                          <div>
                            <span className="text-[11px] uppercase tracking-widest font-semibold mb-1.5 block" style={{ color: theme.parchment[600] }}>Social Intent</span>
                            <div className="grid grid-cols-2 gap-2">
                              {['High', 'Observer', 'Internal', 'Quiet'].map((s) =>
                  <button key={s} onClick={() => onUpdate(entry.id, { socialIntent: entry.socialIntent === s ? null : s })}
                  className={`p-2 rounded-md border text-left transition-colors flex flex-col gap-0.5 ${entry.socialIntent === s ? '' : 'bg-transparent border-[#d4c4b7]'}`} style={entry.socialIntent === s ? SEL_SOCIAL_CARD : undefined}>
                                  <span className={`text-[11px] ${entry.socialIntent === s ? 'text-[#623f18] font-semibold' : 'text-[#50453b]'}`}>{s}</span>
                                  <span className="text-[11px] leading-tight" style={{ color: entry.socialIntent === s ? '#7d562d' : theme.parchment[600], opacity: entry.socialIntent === s ? 1 : 0.7 }}>
                                    {s === 'High' ? 'actively socializing' : s === 'Observer' ? 'watching, minimal join' : s === 'Internal' ? 'solo play focus' : 'home / no stimulation'}
                                  </span>
                                </button>
                  )}
                            </div>
                          </div>
                        </div>
            }

                {/* Footer: muted trash icon + done (asymmetric, de-emphasized destructive) */}
                <div className="pt-2 mt-2 flex items-center" style={{ gap: "8px" }}>
                  <button
                aria-label="delete entry"
                onClick={(e) => {e.stopPropagation();setShowDeleteConfirm(true);}}
                style={{ width: "40px", height: "40px", flexShrink: 0, display: "flex", alignItems: "center", justifyContent: "center", borderRadius: "8px", border: "0.5px solid #d4c4b7", background: "transparent", color: "#6e6258", cursor: "pointer", transition: "all 200ms cubic-bezier(0.32,0.08,0.24,1)" }}>
                    <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2m2 0v14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V6M10 11v6M14 11v6" /></svg>
                  </button>
                  <button
                onClick={(e) => {e.stopPropagation();setExpanded(false);setShowOtherFactors(false);}}
                className="flex-1 flex items-center justify-center transition-all active:scale-[0.98]"
                style={{ padding: "11px", borderRadius: "8px", border: "0", background: "#7d562d", cursor: "pointer" }}>
                    <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.04em", textTransform: "uppercase", color: "#fff" }}>done</span>
                  </button>
                </div>

              </div>
          }
          </div>
        </div>

      {showDeleteConfirm &&
      <div className="fixed inset-0 z-[200] flex items-center justify-center animate-in fade-in" style={{ background: "rgba(28,28,23,0.18)", padding: "24px" }} onClick={() => setShowDeleteConfirm(false)}>
          <div ref={deleteDialogRef} className="animate-in fade-in" onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-label="delete this entry?" style={{ background: "#ffffff", borderRadius: "12px", border: "0.5px solid #d4c4b7", padding: "22px 20px 18px", width: "100%", maxWidth: "300px" }}>
            <p style={{ fontFamily: "'Fraunces', Georgia, serif", fontSize: "17px", fontWeight: 400, color: "#1c1c17", textAlign: "center", margin: "0 0 6px", letterSpacing: "-0.01em" }}>delete this entry?</p>
            <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "13px", lineHeight: 1.5, color: "#6e6258", textAlign: "center", margin: "0 0 18px" }}>this one can't be brought back.</p>
            <div className="flex" style={{ gap: "8px" }}>
              <button
              onClick={() => setShowDeleteConfirm(false)}
              className="flex-1 transition-all active:scale-[0.98]"
              style={{ padding: "11px", borderRadius: "8px", border: "0.5px solid #d4c4b7", background: "transparent", cursor: "pointer" }}>
                <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: "#50453b" }}>keep</span>
              </button>
              <button
              onClick={() => {onDelete(entry.id);setShowDeleteConfirm(false);}}
              className="flex-1 transition-all active:scale-[0.98]"
              style={{ padding: "11px", borderRadius: "8px", border: "0.5px solid rgba(168,103,90,0.5)", background: "transparent", cursor: "pointer" }}>
                <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: "#a8675a" }}>delete</span>
              </button>
            </div>
          </div>
        </div>
      }
    </>);

}

/* ---------------------------------------------------------------
   ScreenHeader — the unifying masthead (treatment C).
   eyebrow + control over a hairline rule; Fraunces title, optional
   subline + caption below. Used by all five screens.
   --------------------------------------------------------------- */
function ScreenHeader({ eyebrow, title, subline, sublineStyle, caption, control }) {
  return (
    <div className="screen-header" style={{ margin: "0px", padding: "22px 0px 0px" }}>
      <div className="sh-kicker">
        <span className="sh-eyebrow">{eyebrow}</span>
        {control && <div className="sh-control">{control}</div>}
      </div>
      <div className="sh-rule"></div>
      <div className="sh-body" style={{ margin: "0px 0px 10px" }}>
        <h1 className="sh-title">{title}</h1>
        {subline && <p className="sh-subline" style={{ margin: "15px 0px 0px", ...sublineStyle }}>{subline}</p>}
        {caption && <p className="sh-caption" style={{ margin: "15px 0px 0px" }}>{caption}</p>}
      </div>
    </div>);

}

/* Reusable 7d/30d/all segmented filter for the header control slot. */
function SegFilter({ value, onChange }) {
  return (
    <div className="sh-seg">
      {['7d', '30d', 'all'].map((f) =>
      <button key={f} className={value === f ? 'on' : ''} onClick={() => onChange(f)}>{f}</button>
      )}
    </div>);

}

/* Unified multi-child switcher (pill style) — sits under the header, above
   content, on every screen that views a single child's data. Renders nothing
   for single-child accounts. */
function ChildSwitcher({ childrenConfig, activeId, onSelect, trailing }) {
  if (!childrenConfig || childrenConfig.length <= 1) return null;
  return (
    <div className="child-switcher" style={{ padding: "0px 0px 10px" }}>
      {childrenConfig.map((c) =>
      <button
        key={c.id}
        onClick={() => onSelect(c.id)}
        className={`cs-pill ${activeId === c.id ? 'on' : ''}`}>
          <TintDot child={c} size={7} />
          <span>{c.name.split(' ')[0]}</span>
        </button>
      )}
      {trailing}
    </div>);

}

function HomeScreen({ entries, addEntry, updateEntry, deleteEntry, activeChild, childrenConfig, setActiveTab, setActiveChildId }) {
  const [filter, setFilter] = useState('all');
  const [childFilter, setChildFilter] = useState(null);

  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
  const sevenDaysAgo = new Date();
  sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);

  let filtered = entries.filter((e) => new Date(e.timestamp) >= sevenDaysAgo);
  if (filter !== 'all') {
    filtered = filtered.filter((e) => hasType(e, filter));
  }
  if (childFilter !== null) {
    filtered = filtered.filter((e) => e.childId === childFilter);
  }

  const grouped = filtered.reduce((acc, entry) => {
    if (!entry.note && !(entry.factors || []).length && !(entry.sunsMood || []).length) return acc;
    const date = formatDate(entry.timestamp);
    if (!acc[date]) acc[date] = [];
    acc[date].push(entry);
    return acc;
  }, {});

  const isEmpty = Object.keys(grouped).length === 0;
  const isMultiChild = childrenConfig.length > 1;

  return (
    <div className="flex-1 flex flex-col overflow-hidden">
      <TopBar activeChild={activeChild} />
      <Composer
        addEntry={addEntry}
        childName={activeChild.name}
        childId={activeChild.id}
        childrenConfig={childrenConfig}
        setActiveChildId={setActiveChildId} />
      
      <div className="flex items-center gap-3 px-4 py-2 border-b" style={{ ...{ borderColor: theme.parchment[200], backgroundColor: theme.parchment[50], padding: "10px 12px 8px 16px" }, backgroundColor: "rgb(253, 249, 240)", padding: "8px 12px 8px 16px", borderColor: "rgb(236, 232, 223)" }}>
        <span className="text-[11px] uppercase tracking-wider font-medium whitespace-nowrap shrink-0" style={{ color: theme.parchment[600] }}>Last 7 days</span>

        {/* Tag Filters (Scrollable, allowed to shrink so the child selector stays reachable) */}
        <div className="flex items-center gap-1 overflow-x-auto no-scrollbar flex-1 min-w-0">
            {['all', 'bite', 'enough', 'insight'].map((f) =>
          <button key={f} onClick={() => setFilter(f)}
          className="rounded-full transition-colors whitespace-nowrap shrink-0"
          style={{
            fontFamily: "'Hanken Grotesk', sans-serif", fontSize: '10px', fontWeight: 600,
            letterSpacing: '0.07em', textTransform: 'uppercase', lineHeight: 1,
            borderRadius: 9999, padding: '7px 12px', borderWidth: '0.5px', borderStyle: 'solid', cursor: 'pointer', whiteSpace: 'nowrap',
            ...(filter === f ?
            { backgroundColor: 'rgba(76,97,114,0.14)', borderColor: 'rgba(76,97,114,0.40)', color: '#2d4455' } :
            { backgroundColor: 'transparent', borderColor: theme.parchment[300], color: theme.parchment[700] }) }
          }>
                {f === 'bite' ? 'one bite' : f === 'insight' ? 'daily log' : f}
              </button>
          )}
          </div>

        {/* Child Dropdown Menu — pinned, never scrolls out of reach */}
        {isMultiChild &&
        <div className="relative flex items-center pl-3 border-l border-[#d4c4b7] shrink-0">
              <button
            onClick={() => setIsDropdownOpen(!isDropdownOpen)}
            className={`flex items-center gap-1 px-2 py-1 rounded-full text-[10px] font-medium transition-all ${childFilter ? 'bg-[#faf2e7] text-[#7d562d] border-[#f0bd8b]' : 'bg-white text-[#50453b] border-[#d4c4b7]'} border`}>
              
                <span className="flex items-center gap-1">{childFilter ? <><TintDot child={childrenConfig.find((c) => c.id === childFilter)} size={7} />{capName((childrenConfig.find((c) => c.id === childFilter)?.name || '').split(' ')[0])}</> : 'All'}</span>
                <span className="text-[8px] opacity-60 ml-0.5 flex items-center" aria-hidden="true"><svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ transform: isDropdownOpen ? 'rotate(180deg)' : 'none', transition: 'transform 200ms ease' }}><path d="M6 9l6 6 6-6" /></svg></span>
              </button>

              {isDropdownOpen &&
          <>
                  {/* Invisible backdrop to close dropdown when tapping outside */}
                  <div className="fixed inset-0 z-40" onClick={() => setIsDropdownOpen(false)}></div>

                  {/* Dropdown Box */}
                  <div className="absolute right-0 top-full mt-2 w-32 bg-white rounded-xl border border-[#d4c4b7] py-1 z-50 overflow-hidden animate-in fade-in zoom-in-95">
                    <button
                onClick={() => {setChildFilter(null);setIsDropdownOpen(false);}}
                className={`w-full text-left px-3 py-2 text-[11px] transition-colors ${childFilter === null ? 'bg-[#fdf9f0] font-semibold text-[#1c1c17]' : 'text-[#50453b] hover:bg-[#fdf9f0]'}`}>
                  
                      All
                    </button>
                    {childrenConfig.map((c) =>
              <button
                key={c.id}
                onClick={() => {setChildFilter(c.id);setIsDropdownOpen(false);}}
                className={`w-full flex items-center gap-2 px-3 py-2 text-[11px] transition-colors ${childFilter === c.id ? 'bg-[#faf2e7] font-semibold text-[#7d562d]' : 'text-[#50453b] hover:bg-[#fdf9f0]'}`}>
                  
                        <TintDot child={c} size={8} />
                        <span className="truncate">{c.name.split(' ')[0]}</span>
                      </button>
              )}
                  </div>
                </>
          }
            </div>
        }
        </div>
      <div className="flex-1 overflow-y-auto p-3" style={{ ...{ backgroundColor: theme.parchment[100] }, backgroundColor: "rgb(253, 249, 240)", padding: "12px" }}>
        {isEmpty ?
        <div className="animate-in fade-in mt-2 mb-4">
            <h2 className="ob-group-label text-[12px] uppercase tracking-wider mb-1 font-medium" style={{ color: theme.parchment[600], textAlign: "left", fontSize: "12px", margin: "10px 0px 0px 0px" }}>today</h2>
            <div className="ob-row ob-first ob-last ob-firstentry">
              <div className="flex justify-between items-center gap-2" style={{ margin: "0px 0px 10px" }}>
                <span className="text-[11px] uppercase tracking-widest font-semibold block text-[#6e6258]" style={{ padding: "3px 0px 0px", whiteSpace: "nowrap" }}>your first entry</span>
              </div>
              <div className="ec-card" style={{ borderRadius: "6px", borderWidth: "1px", padding: "20px", margin: "15px 0px 0px", backgroundColor: "rgb(255, 255, 255)" }}>
                <p className="text-[13px] leading-relaxed mb-4 text-[#1c1c17]">
                  it doesn't have to be big. just what happened — a bite tried, a hard moment let go, something worth keeping.
                </p>
                <div className="flex gap-2">
                  <button onClick={() => setActiveTab('Diary')} className="rounded-full transition-colors active:scale-95" style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: '10px', fontWeight: 600, letterSpacing: '0.07em', textTransform: 'uppercase', lineHeight: 1, borderRadius: 9999, padding: '8px 14px', border: 'none', cursor: 'pointer', backgroundColor: 'rgba(125,86,45,0.16)', color: '#623f18' }}>daily log</button>
                </div>
              </div>
            </div>
          </div> :

        Object.entries(grouped).map(([date, dayEntries]) =>
        <div key={date} className="ob-feed-group mb-5 animate-in fade-in">
              <h2 className="ob-group-label text-[12px] uppercase tracking-wider mb-1 font-medium" style={{ color: theme.parchment[600], textAlign: "left", fontSize: "12px", margin: "10px 0px 0px 0px" }}>{date}</h2>
              {dayEntries.map((entry, i) =>
          <EntryCard
            key={entry.id}
            entry={entry}
            onUpdate={updateEntry}
            onDelete={deleteEntry}
            isFirst={i === 0}
            isLast={i === dayEntries.length - 1}
            childrenConfig={childrenConfig} />

          )}
            </div>
        )
        }
      </div>
    </div>);

}

function StatCallout({ stats, style }) {
  return (
    <div style={{ display: 'flex', gap: '16px', ...style }}>
      {stats.map((s, i) => {
        const isText = s.type === 'text';
        return (
          <div key={i} style={{ flex: 1, minWidth: 0 }}>
            <div style={{
              fontFamily: "'Fraunces', Georgia, serif",
              fontWeight: 400,
              color: s.color,
              display: 'flex', alignItems: 'center', justifyContent: 'flex-start',
              height: "37px", margin: "0px 0px 12px",
              ...(isText ?
              { fontSize: '22px', lineHeight: 1, maxWidth: '100%', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' } :
              { fontSize: '30px', lineHeight: 1, letterSpacing: '-0.01em' })
            }}>{s.value}</div>
            <div style={{
              fontFamily: "'Hanken Grotesk', sans-serif",
              fontSize: '11px',
              fontWeight: 600,
              letterSpacing: '0.07em',
              textTransform: 'uppercase',
              color: '#6e6258',
              lineHeight: 1.4,
              whiteSpace: 'pre-line'
            }}>{s.label}</div>
          </div>);
      })}
    </div>);
}

function PatternScreen({ entries, childrenConfig, activeChildId, setActiveChildId, navigateHome }) {
  const [timeFilter, setTimeFilter] = useState('7d');
  const [insightLayout, setInsightLayout] = useState(() => {try {return localStorage.getItem('ob-insight-layout') || 'lines';} catch (e) {return 'lines';}});

  // Filter entries down to the selected child and the selected time window.
  const activeEntries = useMemo(() => {
    let filtered = activeChildId !== null ?
    entries.filter((e) => e.childId === activeChildId) :
    entries.filter((e) => e.childId !== null);

    if (timeFilter !== 'all') {
      const limit = new Date();
      limit.setHours(0, 0, 0, 0); // Reset to midnight of today
      limit.setDate(limit.getDate() - (timeFilter === '7d' ? 6 : 29)); // 6 days ago + today = 7 days
      filtered = filtered.filter((e) => new Date(e.timestamp) >= limit);
    }
    return filtered;
  }, [entries, activeChildId, timeFilter]);

  const totalEntriesCount = activeEntries.length;

  const bites = activeEntries.filter((e) => hasType(e, 'bite') && e.note).length;
  const enoughs = activeEntries.filter((e) => hasType(e, 'enough') && e.note).length;
  const kindnessCount = activeEntries.filter((e) => e.dailyKindness).length;

  const dateRangeLabel = (() => {
    const fmt = (d) => `${d.toLocaleDateString('en-US', { month: 'short' }).toLowerCase()} ${d.getDate()}`;
    const today = new Date();
    if (timeFilter === 'all') {
      const ds = activeEntries.map((e) => new Date(e.timestamp)).sort((a, b) => a - b);
      return ds.length ? `since ${fmt(ds[0])}` : 'all time';
    }
    const s = new Date();s.setDate(today.getDate() - (timeFilter === '7d' ? 6 : 29));
    return `${fmt(s)} – ${fmt(today)}`;
  })();

  const [selectedFactor, setSelectedFactor] = useState(null);

  // Derive win streak — consecutive days with at least one bite entry
  const winStreak = React.useMemo(() => {
    let streak = 0;
    for (let i = 0; i < 30; i++) {
      const d = new Date();
      d.setDate(d.getDate() - i);
      const dateStr = d.toDateString();
      const hasBite = activeEntries.some((e) =>
      new Date(e.timestamp).toDateString() === dateStr && hasType(e, 'bite')
      );
      if (hasBite) {
        streak++;
      } else if (i === 0) {
        continue;
      } else {
        break;
      }
    }
    return streak;
  }, [activeEntries]);

  // Derive the longest run of consecutive days with at least one bite within
  // the active window. Used on the 30d / all scopes, where a retrospective
  // "longest run" reads better than a today-anchored current streak.
  const longestRun = React.useMemo(() => {
    const dayset = new Set(
      activeEntries.filter((e) => hasType(e, 'bite')).
      map((e) => new Date(e.timestamp).toDateString())
    );
    if (dayset.size === 0) return 0;
    const DAY = 86400000;
    const days = [...dayset].
    map((s) => {const d = new Date(s);d.setHours(0, 0, 0, 0);return d.getTime();}).
    sort((a, b) => a - b);
    let best = 1,cur = 1;
    for (let i = 1; i < days.length; i++) {
      if (days[i] - days[i - 1] === DAY) {cur++;if (cur > best) best = cur;} else
      cur = 1;
    }
    return best;
  }, [activeEntries]);

  // Derive energy correlation — on high energy days, what % felt light/mixed
  const energyCorrelation = React.useMemo(() => {
    return getEnergyCorrelation(activeEntries);
  }, [activeEntries]);

  // Derive enough insight — how many enough moments had low energy + heavy feeling
  const enoughInsight = React.useMemo(() => {
    const enoughWithNote = activeEntries.filter((e) => hasType(e, 'enough') && e.note);
    if (enoughWithNote.length === 0) return null;
    const lowEnergyBattle = enoughWithNote.filter((e) =>
    e.energy === 'Low' && (e.friction === 'Heavy' || e.friction === 'Mixed')
    ).length;
    const allLowEnergy = enoughWithNote.filter((e) => e.energy === 'Low').length;
    const lightDayEnough = enoughWithNote.filter((e) =>
    e.friction === 'Light' || e.friction === 'Mixed'
    ).length;
    return {
      total: enoughWithNote.length,
      lowEnergyBattle,
      allLowEnergy,
      lightDayEnough,
      allMatched: lowEnergyBattle === enoughWithNote.length && enoughWithNote.length > 0
    };
  }, [activeEntries]);

  // Derive top compounding disruptors — factors that appear together on heavy days
  const compoundingDisruptors = React.useMemo(() => {
    const battleEntries = activeEntries.filter((e) => e.friction === 'Heavy');
    if (battleEntries.length < 2) return null;

    const factorPairs = {};
    battleEntries.forEach((e) => {
      const factors = e.factors || [];
      for (let i = 0; i < factors.length; i++) {
        for (let j = i + 1; j < factors.length; j++) {
          const pair = [factors[i], factors[j]].sort().join(' + ');
          factorPairs[pair] = (factorPairs[pair] || 0) + 1;
        }
      }
    });

    const topPair = Object.entries(factorPairs).
    sort((a, b) => b[1] - a[1])[0];

    if (!topPair) {
      // No pairs — return the single most common battle factor
      const factorCounts = {};
      battleEntries.forEach((e) => {
        (e.factors || []).forEach((f) => {
          factorCounts[f] = (factorCounts[f] || 0) + 1;
        });
      });
      const topFactor = Object.entries(factorCounts).sort((a, b) => b[1] - a[1])[0];
      if (!topFactor) return null;
      return {
        type: 'single',
        label: topFactor[0],
        count: topFactor[1],
        battleTotal: battleEntries.length
      };
    }

    return {
      type: 'pair',
      label: topPair[0],
      count: topPair[1],
      battleTotal: battleEntries.length,
      factorA: topPair[0].split(' + ')[0],
      factorB: topPair[0].split(' + ')[1]
    };
  }, [activeEntries]);

  // Derive factor cloud data from real entries
  const factorCloud = useMemo(() => {
    const factorStats = {};

    activeEntries.forEach((e) => {
      (e.factors || []).forEach((f) => {
        if (!factorStats[f]) {
          factorStats[f] = { count: 0, battleCount: 0, easyCount: 0, biteCount: 0, lowEnergyCount: 0 };
        }
        factorStats[f].count++;
        if (e.friction === 'Heavy') factorStats[f].battleCount++;
        if (e.friction === 'Light') factorStats[f].easyCount++;
        if (hasType(e, 'bite')) factorStats[f].biteCount++;
        if (e.energy === 'Low') factorStats[f].lowEnergyCount++;
      });
    });

    // Score each factor by feeling impact
    const scored = Object.entries(factorStats).map(([name, stats]) => {
      const frictionScore = stats.battleCount * 3 + stats.count;
      const winScore = stats.biteCount * 2 + stats.easyCount;

      let type = 'neutral';
      if (stats.battleCount > stats.biteCount) type = 'hard';else
      if (stats.biteCount >= stats.battleCount && stats.biteCount > 0) type = 'win';

      const color = type === 'hard' ?
      { bg: theme.slate[100], activeBg: theme.slate[200], text: theme.slate[600], activeText: theme.slate[600] } :
      type === 'win' ?
      { bg: theme.ochre[100], activeBg: theme.ochre[200], text: theme.ochre[600], activeText: theme.ochre[600] } :
      { bg: theme.rose[100], activeBg: theme.rose[200], text: theme.rose[400], activeText: theme.rose[400] };

      // Build the insight string from real data
      let impact = type === 'hard' ? 'Felt Heavy' : type === 'win' ? 'Positive Catalyst' : 'Neutral';

      let desc = '';
      if (type === 'hard') {
        desc = `showed up ${stats.count} time${stats.count !== 1 ? 's' : ''} — ${stats.battleCount} of them on heavy days. when this one's in the picture, the day tends to ask more of you.`;
      } else if (type === 'win') {
        desc = `showed up ${stats.count} time${stats.count !== 1 ? 's' : ''} — ${stats.biteCount} of them alongside a One Bite. this one seems to sit on your side.`;
      } else {
        desc = `showed up ${stats.count} time${stats.count !== 1 ? 's' : ''}. no clear shape yet — a few more days and it'll start to show.`;
      }

      return {
        name: name.toUpperCase(),
        rawName: name,
        count: stats.count,
        frictionScore,
        winScore,
        type,
        color,
        impact,
        desc,
        stats
      };
    });

    // Sort by total impact score, take top 8
    const sorted = scored.
    sort((a, b) => b.frictionScore + b.winScore - (a.frictionScore + a.winScore)).
    slice(0, 8);

    // Assign sizes — scaled by impact weight (what actually shaped the week,
    // not just what was frequent), mapped to AREA rather than diameter so
    // the visual hierarchy reads truthfully.
    const maxWeight = Math.max(...sorted.map((f) => f.frictionScore + f.winScore), 1);
    const minSize = 60;
    const maxSize = 152;

    // The force-directed sim (below) computes each bubble's x/y at render time,
    // so no static positions are needed here. These arrays drive the organic
    // blob-wobble (border-radius morph) on the inner fill element.
    const durations = ['6.8s', '8.4s', '7.2s', '7.9s', '6.5s', '8.1s', '7.5s', '8.8s'];
    const shapes = ['wobbleA', 'wobbleB', 'wobbleC', 'wobbleD'];

    return sorted.map((f, i) => {
      return {
        ...f,
        size: Math.round(Math.sqrt(
          minSize * minSize +
          (f.frictionScore + f.winScore) / maxWeight * (maxSize * maxSize - minSize * minSize))),
        animationDuration: durations[i] || '5s',
        animationName: shapes[i % shapes.length]
      };
    });

  }, [activeEntries]);

  // --- Factor cloud motion: a gentle force-directed layout ---
  // Bubbles repel each other and the walls, wander slowly, and a weak
  // centering pull keeps the cluster balanced. The effect: with only a few
  // clouds they drift apart to fill the empty space instead of clumping;
  // with many, they settle into a natural packing. Positions are written
  // straight to the DOM via transform for smoothness (no re-render churn).
  const cloudWrapRef = useRef(null);
  const bubbleEls = useRef([]);
  const simRef = useRef([]);
  const rafRef = useRef(0);
  const selectedRef = useRef(null);
  selectedRef.current = selectedFactor?.rawName || null;

  // Cloud layout mode — driven by the Tweaks panel. 'valence' maps factors
  // that felt heavy to the left and helpful ones to the right; 'drift' is
  // the original centred cluster.
  const [cloudMode, setCloudMode] = React.useState(window.__cloudLayout || 'drift');
  React.useEffect(() => {
    const fn = (e) => setCloudMode(e.detail);
    window.addEventListener('obe-cloud-layout', fn);
    return () => window.removeEventListener('obe-cloud-layout', fn);
  }, []);
  const prefersReducedMotion = React.useMemo(
    () => window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
    []);

  const cloudSig = factorCloud.map((f) => f.rawName + ':' + f.size).join('|');

  React.useLayoutEffect(() => {
    const wrap = cloudWrapRef.current;
    if (!wrap || factorCloud.length < 2) return;

    const W = wrap.clientWidth || 320;
    // Fallback must match the container's CSS height (h-[340px]): the Pattern
    // screen can mount hidden, so clientHeight reads 0 on first layout and we
    // settle against this fallback — a mismatch would overflow the box.
    const H = wrap.clientHeight || 340;
    const n = factorCloud.length;
    const GA = Math.PI * (3 - Math.sqrt(5)); // golden angle — even initial spread

    // In the valence layout, factors that felt heavy settle toward the left,
    // helpful ones toward the right, neutral stays centred. In the drift
    // layout everything pulls to the centre as before.
    const targetX = (f) => cloudMode === 'valence' ?
    f.type === 'hard' ? W * 0.26 : f.type === 'win' ? W * 0.74 : W * 0.5 :
    W / 2;

    // Seed on a phyllotaxis spiral, biased toward each bubble's home zone,
    // so the relaxation below converges quickly and predictably.
    simRef.current = factorCloud.map((f, i) => {
      const r = f.size / 2;
      const t = (i + 0.5) / n;
      const rad = Math.sqrt(t) * Math.min(W, H) * 0.3;
      const ang = i * GA;
      return {
        r,
        x: targetX(f) * 0.6 + W / 2 * 0.4 + Math.cos(ang) * rad * 0.6,
        y: H / 2 + Math.sin(ang) * rad,
        vx: 0, vy: 0, fx: 0, fy: 0
      };
    });

    const inset = 8,GAP = 14;
    // In valence mode the zone labels sit pinned at the top — keep bubbles
    // clear of them with a deeper top inset so they never crowd the labels.
    const topInset = cloudMode === 'valence' ? 34 : inset;
    const K_OVERLAP = 0.045,
      K_CX = cloudMode === 'valence' ? 0.0085 : 0.0030,
      K_CY = 0.0150,
      K_WALL = 0.06,DAMP = 0.93;

    // Settle synchronously — no perpetual rAF loop. The cloud's "life" comes
    // from the CSS border-radius wobble; positions hold still so tap targets
    // never move under the finger.
    const s = simRef.current;
    const cy = H / 2;
    for (let it = 0; it < 420; it++) {
      for (let i = 0; i < s.length; i++) {s[i].fx = 0;s[i].fy = 0;}
      for (let i = 0; i < s.length; i++) {
        for (let j = i + 1; j < s.length; j++) {
          let dx = s[i].x - s[j].x,dy = s[i].y - s[j].y;
          const d = Math.hypot(dx, dy) || 0.001;
          const minD = s[i].r + s[j].r + GAP;
          if (d < minD) {
            const f = (minD - d) * K_OVERLAP;
            const ux = dx / d,uy = dy / d;
            s[i].fx += ux * f;s[i].fy += uy * f;
            s[j].fx -= ux * f;s[j].fy -= uy * f;
          }
        }
      }
      for (let i = 0; i < s.length; i++) {
        const b = s[i];
        b.fx += (targetX(factorCloud[i]) - b.x) * K_CX;
        b.fy += (cy - b.y) * K_CY;
        const minX = b.r + inset,maxX = W - b.r - inset;
        const minY = b.r + topInset,maxY = H - b.r - inset;
        if (b.x < minX) b.fx += (minX - b.x) * K_WALL;
        if (b.x > maxX) b.fx += (maxX - b.x) * K_WALL;
        if (b.y < minY) b.fy += (minY - b.y) * K_WALL;
        if (b.y > maxY) b.fy += (maxY - b.y) * K_WALL;
        b.vx = (b.vx + b.fx) * DAMP;
        b.vy = (b.vy + b.fy) * DAMP;
        b.x += b.vx;b.y += b.vy;
        b.x = Math.max(minX, Math.min(maxX, b.x));
        b.y = Math.max(minY, Math.min(maxY, b.y));
      }
    }

    // Deterministic vertical re-centering. The settle can leave the whole
    // cluster pinned high (the screen may mount hidden, so the height read is
    // unreliable). Measure the cluster's actual vertical extent and shift every
    // bubble so the empty space splits evenly top and bottom — no one-sided gap.
    if (s.length) {
      let clusterTop = Infinity,clusterBot = -Infinity;
      for (const b of s) {
        if (b.y - b.r < clusterTop) clusterTop = b.y - b.r;
        if (b.y + b.r > clusterBot) clusterBot = b.y + b.r;
      }
      const clusterMid = (clusterTop + clusterBot) / 2;
      const boxMid = (topInset + (H - inset)) / 2;
      let shift = boxMid - clusterMid;
      // Don't shift so far that the cluster pokes out of the box.
      const maxDown = topInset - clusterTop;
      const maxUp = H - inset - clusterBot;
      shift = Math.max(Math.min(shift, maxUp), maxDown);
      if (Math.abs(shift) > 0.5) for (const b of s) b.y += shift;
    }

    // Paint. The first paint snaps into place; later paints (mode switch,
    // filter change) glide there with the quiet ease.
    const EASE = 'transform 560ms cubic-bezier(0.32, 0.08, 0.24, 1), opacity 300ms ease';
    for (let i = 0; i < s.length; i++) {
      const b = s[i],el = bubbleEls.current[i];
      if (!el) continue;
      const firstPaint = !el.dataset.painted;
      el.style.transition = firstPaint ? 'none' : EASE;
      el.style.transform =
      `translate(${(b.x - b.r).toFixed(2)}px, ${(b.y - b.r).toFixed(2)}px) scale(${selectedRef.current === factorCloud[i].rawName ? 1.08 : 1})`;
      if (firstPaint) {
        el.dataset.painted = '1';
        requestAnimationFrame(() => {el.style.transition = EASE;});
      }
    }
  }, [cloudSig, cloudMode]);

  // Selection: gently scale the chosen cloud without re-running the layout.
  React.useEffect(() => {
    const s = simRef.current;
    for (let i = 0; i < s.length && i < factorCloud.length; i++) {
      const b = s[i],el = bubbleEls.current[i];
      if (!el || !b) continue;
      el.style.transform =
      `translate(${(b.x - b.r).toFixed(2)}px, ${(b.y - b.r).toFixed(2)}px) scale(${selectedFactor?.rawName === factorCloud[i].rawName ? 1.08 : 1})`;
    }
  }, [selectedFactor, factorCloud]);

  const displayChildName = activeChildId === null ?
  'your children' :
  childrenConfig.find((c) => c.id === activeChildId)?.name || 'your child';

  const patternHeader =
  <>
    <ScreenHeader
      eyebrow="patterns"
      control={<SegFilter value={timeFilter} onChange={setTimeFilter} />}
      title={(() => {
        const uniqueDays = new Set(activeEntries.map((e) => new Date(e.timestamp).toDateString())).size;
        if (timeFilter === '7d') {
          if (uniqueDays === 7) return 'you showed up every single day.';
          if (uniqueDays >= 5) return `you showed up ${uniqueDays} days this week.`;
          if (uniqueDays >= 3) return `you logged ${uniqueDays} days this week.`;
          if (uniqueDays >= 1) return `you logged ${uniqueDays === 1 ? 'one day' : `${uniqueDays} days`} this week.`;
          return 'your patterns are here when you are.';
        } else if (timeFilter === '30d') {
          if (uniqueDays >= 20) return `you showed up ${uniqueDays} days this month.`;
          if (uniqueDays >= 1) return `you logged ${uniqueDays} days this month.`;
          return 'your patterns are here when you are.';
        } else {
          if (uniqueDays >= 1) return `you logged ${uniqueDays} days in total.`;
          return 'your patterns are here when you are.';
        }
      })()}
      subline="the quiet shape of your days, made visible."
      sublineStyle={{ fontSize: "13px", lineHeight: 1.5, color: "#50453b", margin: "15px 0px 0px" }} />
    
    <ChildSwitcher childrenConfig={childrenConfig} activeId={activeChildId} onSelect={setActiveChildId} />
    </>;


  return (
    <div className="flex-1 flex flex-col bg-[#fdf9f0] overflow-hidden">
      <style dangerouslySetInnerHTML={{ __html: `
        @keyframes wobbleA { 0%, 100% { border-radius: 63% 37% 54% 46% / 55% 48% 52% 45%; transform: translate(0px, 0px) rotate(0deg) scale(1); } 33% { border-radius: 40% 60% 39% 61% / 41% 64% 36% 59%; transform: translate(-5px, -8px) rotate(2deg) scale(1.03); } 66% { border-radius: 56% 44% 65% 35% / 65% 38% 62% 35%; transform: translate(6px, 7px) rotate(-2deg) scale(0.97); } }
        @keyframes wobbleB { 0%, 100% { border-radius: 48% 52% 68% 32% / 42% 58% 42% 58%; transform: translate(0px, 0px) rotate(0deg) scale(1); } 33% { border-radius: 64% 36% 45% 55% / 58% 35% 65% 42%; transform: translate(7px, -6px) rotate(-3deg) scale(1.02); } 66% { border-radius: 34% 66% 55% 45% / 52% 62% 38% 48%; transform: translate(-6px, 8px) rotate(2deg) scale(0.98); } }
        @keyframes wobbleC { 0%, 100% { border-radius: 58% 42% 38% 62% / 63% 47% 53% 37%; transform: translate(0px, 0px) rotate(0deg) scale(1); } 33% { border-radius: 45% 55% 62% 38% / 36% 56% 44% 64%; transform: translate(-8px, 5px) rotate(3deg) scale(1.04); } 66% { border-radius: 68% 32% 48% 52% / 56% 41% 59% 44%; transform: translate(5px, -7px) rotate(-2deg) scale(0.96); } }
        @keyframes wobbleD { 0%, 100% { border-radius: 52% 48% 60% 40% / 47% 63% 37% 53%; transform: translate(0px, 0px) rotate(0deg) scale(1); } 33% { border-radius: 37% 63% 52% 48% / 61% 39% 58% 42%; transform: translate(6px, 9px) rotate(-2deg) scale(1.03); } 66% { border-radius: 61% 39% 43% 57% / 41% 57% 43% 60%; transform: translate(-7px, -5px) rotate(3deg) scale(0.97); } }
      ` }} />
      {totalEntriesCount < 3 ?
      <div className="screen-scroll flex-1 overflow-y-auto px-4 pb-6 animate-in fade-in">
           {patternHeader}
           <div style={{ borderTop: "0.5px solid rgba(212,196,183,0.55)", marginTop: "20px", paddingTop: "36px", paddingBottom: "28px", padding: "48px 0px 28px" }}>
              <h2 style={{ fontFamily: "'Fraunces', Georgia, serif", fontWeight: 400, letterSpacing: "-0.01em", lineHeight: 1.25, color: "#1c1c17", margin: "0 0 12px", fontSize: "20px" }}>your patterns are building.</h2>
              <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "14px", lineHeight: 1.6, color: "#50453b", textWrap: "pretty", maxWidth: "30ch", margin: "0px 0px 26px" }}>
                log a few more moments and the shapes will start to show — your energy, the rhythm, the factors behind them.
              </p>
              <button onClick={navigateHome} className="inline-flex items-center transition-opacity active:opacity-60" style={{ background: "none", border: "0", padding: "0", cursor: "pointer", gap: "6px" }}>
                <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: "#7d562d", whiteSpace: "nowrap" }}>go log something</span>
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#7d562d" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
              </button>
           </div>
        </div> :

      <div className="screen-scroll flex-1 overflow-y-auto px-4 pb-6 animate-in fade-in">
          {patternHeader}
          <div style={{ borderTop: "0.5px solid rgba(212,196,183,0.55)", paddingTop: "20px", margin: "26px 0px 0px" }}>
            <h2 className="linear-eyebrow" style={{ margin: "0px 0px 14px" }}>{dateRangeLabel}</h2>
            <StatCallout
            style={{ margin: "0px" }}
            stats={[
            { value: bites, color: '#7d562d', label: 'one bite\nmoments', type: 'numeric' },
            { value: enoughs, color: '#4c6172', label: 'enough\nmoments', type: 'numeric' },
            { value: kindnessCount, color: '#96615f', label: 'kindness\nfor you', type: 'numeric' }]
            } />
          </div>

          <div className="mb-8" style={{ opacity: "1", margin: "26px 0px 30px", borderTop: "0.5px solid rgba(212,196,183,0.55)", paddingTop: "20px" }}>
            <h2 className="linear-eyebrow mb-1">{timeFilter === '7d' ? 'what showed up this week' : timeFilter === '30d' ? 'what showed up this month' : 'everything that showed up'}</h2>
            
            {factorCloud.length < 2 ?
          <p className="text-[13px] text-[#6e6258] py-6">factor patterns will appear here once you've logged a few more context tags.</p> :

          <>
                <div className="flex items-center flex-wrap" style={{ gap: "24px", margin: "0px 0px 12px" }}>
                  <div className="flex items-center flex-wrap" style={{ gap: "12px" }}>
                    <span className="flex items-center" style={{ gap: "5px", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: 500, color: "#6e6258" }}><span className="rounded-full" style={{ width: 8, height: 8, backgroundColor: '#b3c9dd' }}></span>felt heavy</span>
                    <span className="flex items-center" style={{ gap: "5px", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: 500, color: "#6e6258" }}><span className="rounded-full" style={{ width: 8, height: 8, backgroundColor: '#f0bd8b' }}></span>helped</span>
                    <span className="flex items-center" style={{ gap: "5px", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: 500, color: "#6e6258" }}><span className="rounded-full" style={{ width: 8, height: 8, backgroundColor: '#eabbba' }}></span>neutral</span>
                  </div>
                  <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", color: "#6e6258", fontSize: "11px", fontWeight: 500, whiteSpace: "nowrap" }}>larger = bigger influence</span>
                </div>
                <div ref={cloudWrapRef} className="relative h-[340px] w-full overflow-visible" style={{ padding: "0px", margin: "0px" }}>
                  <svg aria-hidden="true" width="0" height="0" style={{ position: 'absolute' }}>
                    <defs>
                      {[0, 1, 2, 3].map((k) => <filter key={k} id={`wc-edge-${k}`} x="-30%" y="-30%" width="160%" height="160%"><feTurbulence type="fractalNoise" baseFrequency={0.010 + k * 0.0022} numOctaves="2" seed={k * 7 + 3} result="n" /><feDisplacementMap in="SourceGraphic" in2="n" scale="11" xChannelSelector="R" yChannelSelector="G" /></filter>)}
                    </defs>
                  </svg>
                  {factorCloud.map((f, i) => {
                const isSelected = selectedFactor?.rawName === f.rawName;
                const wc = { hard: { w: '179,201,221', d: '136,160,179' }, win: { w: '240,189,139', d: '212,153,96' }, neutral: { w: '234,187,186', d: '184,142,141' } }[f.type] || { w: '234,187,186', d: '184,142,141' };
                const A = isSelected ? 1.28 : 1;
                const cap = (v) => Math.min(v, 1).toFixed(3);
                const wcBg = `radial-gradient(circle at 50% 46%, rgba(${wc.w},${cap(0.16 * A)}) 0%, rgba(${wc.w},${cap(0.30 * A)}) 56%, rgba(${wc.w},${cap(0.42 * A)}) 82%, rgba(${wc.d},${cap(0.46 * A)}) 94%, rgba(${wc.w},${cap(0.14 * A)}) 100%)`;
                return (
                  <div
                    key={f.rawName}
                    ref={(el) => {bubbleEls.current[i] = el;}}
                    onClick={() => setSelectedFactor(isSelected ? null : f)}
                    className="absolute cursor-pointer"
                    style={{
                      width: f.size,
                      height: f.size,
                      top: 0,
                      left: 0,
                      transformOrigin: 'center center',
                      willChange: 'transform',
                      opacity: selectedFactor && !isSelected ? 0.4 : 1,
                      zIndex: isSelected ? 50 : 200 - Math.round(f.size)
                    }}>
                    
                        {/* Soft cloud body: solid core, only the outer ~15px dissolves
                      into the cream via the gradient's final stop + a light rim blur. */}
                        <div
                      className="absolute inset-0 rounded-full transition-all duration-300"
                      style={{
                        background: wcBg,
                        filter: `url(#wc-edge-${i % 4}) blur(0.4px)`,
                        opacity: 1,
                        animation: prefersReducedMotion ? 'none' : `${f.animationName} ${f.animationDuration} ease-in-out infinite`
                      }}>
                        </div>
                        {/* Crisp label rides the same drift so it moves with the cloud */}
                        <div
                      className="absolute inset-0 flex items-center justify-center text-center uppercase transition-colors duration-300"
                      style={{
                        color: isSelected ? f.color.activeText : f.color.text,
                        fontSize: Math.max(11, f.size * 0.13),
                        lineHeight: 1.25,
                        letterSpacing: '0.05em',
                        padding: '8px',
                        animation: prefersReducedMotion ? 'none' : `${f.animationName} ${f.animationDuration} ease-in-out infinite`, fontWeight: "400", opacity: "0.76"
                      }}>
                      
                          {f.name.replace('_', ' ')}
                        </div>
                      </div>);

              })}
                </div>

                <div className="pt-3 border-t border-[#ece8df] flex flex-col" style={{ margin: "0px", borderWidth: "0px" }}>
                  {selectedFactor &&
              <div className="animate-in fade-in" style={{ margin: "12px 0px 0px" }}>
                      <div className="flex justify-between items-center mb-1">
                        <span className="text-[11px] uppercase tracking-wider font-semibold" style={{ color: selectedFactor.color.activeText }}>
                          {selectedFactor.rawName}
                        </span>
                        <span className="text-[11px] uppercase tracking-wider font-semibold text-[#6e6258]">
                          {selectedFactor.impact}
                        </span>
                      </div>
                      <p className="text-[13px] leading-relaxed text-[#50453b]">
                        {selectedFactor.desc}
                      </p>
                    </div>
              }
                </div>
              </>
          }
          </div>

          {/* Uncontained caption to the cloud above — what shaped the hard moments. */}
          {compoundingDisruptors && (() => {
          const cd = compoundingDisruptors;
          const cn = { fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "13px", fontWeight: "600", color: "#6e6258" };
          const wk = getWeekKey();
          const nm = displayChildName === 'your children' ? 'your child' : displayChildName;

          // The factor that drives the cluster: the single factor, or the
          // primary (first) factor of a compounding pair.
          const clusterFactor = cd.type === 'pair' ? cd.factorA : cd.label;
          const cluster = FACTOR_CLUSTERS[clusterFactor] || 'routine';

          // Headline rotates from a shared pool (single-factor framing). A
          // compounding pair keeps its existing "landed together" headline.
          const headPool = [
          "{factor} appeared on {n} of your {total} heaviest days this week.",
          "{factor} showed up on {n} of your {total} hardest days.",
          "{n} of your heaviest days this week had {factor} in the picture."];

          const renderHeadline = () => {
            if (cd.type === 'pair') {
              return <><span style={cn}>{cd.factorA}</span> and <span style={cn}>{cd.factorB}</span> landed together on {cd.count} of your heaviest day{cd.count !== 1 ? 's' : ''}.</>;
            }
            // Split the chosen template on {factor} so the name renders as a
            // styled span in place; {n}/{total} fill as plain text.
            const tmpl = pickVariant(headPool, `${wk}|factor-headline`).
            replace(/\{n\}/g, cd.count).replace(/\{total\}/g, cd.battleTotal);
            const [before, after] = tmpl.split('{factor}');
            return <>{before}<span style={cn}>{cd.label}</span>{after}</>;
          };

          // Body rotates within the matched cluster's pool.
          const bodyPools = {
            'child-body': [
            "this is the body doing something — teething, a growth spurt, a virus working through. it lands before it makes sense.",
            "{name}'s system was busy with something physical. the day carried that weight too.",
            "the body was working on something bigger than the moment.",
            "when the body is busy, the rest of the day gets harder. that's just how it goes."],

            'parent-state': [
            "you were running low yourself on those days.",
            "this was a day carried while you weren't at full strength.",
            "your own body was part of the week, not just {name}'s.",
            "harder to pour from a cup that's low. those days asked that of you."],

            'routine': [
            "the rhythm of the day moved, and everything downstream moved with it.",
            "when the usual order goes, the bumps tend to follow.",
            "the structure shifted. the day felt it.",
            "the seams of a changed day land harder on a small body."],

            'social-sensory': [
            "new input — people, places, sensation — asks a lot of a small nervous system.",
            "{name} was taking in more than usual. that takes energy before it turns into growth.",
            "this is what processing something new tends to look like.",
            "a lot of new at once. the day held the cost of that."],

            'food': [
            "food and mood run closer than they're given credit for.",
            "{factor} can tip a day that was already near the edge.",
            "some days the food is the story. most days it's one thread.",
            "{name}'s body was sorting something out. it often shows up at the table first."]

          };
          const body = pickVariant(bodyPools[cluster], `${wk}|factor-body`).
          replace(/\{name\}/g, nm).replace(/\{factor\}/g, clusterFactor).
          replace(/\{n\}/g, cd.count).replace(/\{total\}/g, cd.battleTotal);

          return (
            <div style={{ margin: "0px" }}>
                <div style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontWeight: "600", letterSpacing: "0.1em", textTransform: "uppercase", color: "#6e6258", fontSize: "11px", margin: "0px 0px 11px" }}>what shaped those hard moments</div>
                <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontWeight: "400", lineHeight: "1.5", color: "#1c1c17", margin: "0px", fontSize: "13px" }}>
                  {renderHeadline()}
                </p>
                <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontStyle: "normal", fontWeight: "400", lineHeight: "1.65", fontSize: "12px", color: "#50453b", margin: "5px 0px 0px" }}>{body}</p>
                <div aria-hidden="true" style={{ borderTop: "0.5px solid rgba(212,196,183,0.5)", margin: "25px 0px" }}></div>
              </div>);

        })()}

          {(() => {
          // ── Unified "what it means for you" section (Option C — tinted) ──
          // Shared card anatomy: tinted bg + matching hairline, internal label,
          // headline (data point, key phrase italic / factor name in Hanken),
          // body (the human reframe). Cards self-suppress when data is thin.
          // Each insight category carries a soft-saturated tint, a crisp
          // hairline in its own hue, a saturated dot that echoes the factor
          // cloud bubbles (and the feed's timeline dots), and a solid pastel
          // tag pill — pulling the cards into the same pigment family as the
          // cloud above instead of reading as flat grey washes.
          const INSIGHT = {
            ochre: { bg: "rgba(240,189,139,0.30)", line: "rgba(160,104,58,0.26)", dot: "#edaa66", text: "#623f18", tag: "rgba(240,189,139,0.72)" },
            rose: { bg: "rgba(205,143,141,0.26)", line: "rgba(150,97,95,0.26)", dot: "#d69a98", text: "#8a534f", tag: "rgba(208,148,146,0.5)" },
            slate: { bg: "rgba(124,153,179,0.26)", line: "rgba(76,97,114,0.30)", dot: "#7d9ab4", text: "#2d4455", tag: "rgba(124,153,179,0.48)" }
          };
          const cardShell = (p) => ({
            background: p.bg, border: `1px solid ${p.line}`,
            borderRadius: "12px", padding: "17px 18px 15px"
          });
          const renderLabel = (p, text) =>
          <div style={{ display: "flex", alignItems: "center", gap: "7px", margin: "0px 0px 11px" }}>
              <span style={{ width: "7px", height: "7px", borderRadius: "50%", background: p.dot, flexShrink: 0, boxShadow: `0 0 0 3px ${p.tag}` }}></span>
              <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "9px", fontWeight: "700", letterSpacing: "0.1em", textTransform: "uppercase", color: p.text, whiteSpace: "nowrap" }}>{text}</span>
            </div>;

          const headlineStyle = {
            fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "14.5px", fontWeight: "400",
            lineHeight: "1.5", color: "#241f1a", margin: "0px", textWrap: "pretty"
          };
          const bodyStyle = {
            fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "12px", fontWeight: "400",
            lineHeight: "1.65", color: "#544a40", margin: "7px 0px 0px", textWrap: "pretty"
          };
          const tagStyle = (p) => ({
            display: "inline-block", marginTop: "13px",
            fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "9px", fontWeight: "600",
            letterSpacing: "0.06em", textTransform: "uppercase",
            background: p.tag, color: p.text, borderRadius: "9999px",
            padding: "4px 11px", whiteSpace: "nowrap"
          });

          const cards = [];

          // Two presentations: 'lines' (marginalia — gutter label + Fraunces
          // headline, no container) is the default; 'cards' (the original
          // filled color-wash) is kept as a fallback, toggled by the caption row.
          const capHead = (h) => typeof h === 'string' ? h.charAt(0).toUpperCase() + h.slice(1) : h;
          const renderInsightCard = (key, p, label, headline, body, tag, isHero) => {
            if (insightLayout === 'cards') {
              return (
                <div key={key} style={cardShell(p)}>
                  {renderLabel(p, label)}
                  <p style={headlineStyle}>{headline}</p>
                  <p style={bodyStyle}>{body}</p>
                  {tag && <span style={tagStyle(p)}>{tag}</span>}
                </div>);
            }
            const isFirst = cards.length === 0;
            const sep = isFirst ? {} : { borderTop: "0.5px solid rgba(212,196,183,0.7)" };
            const bodyP = <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "12px", fontWeight: "400", lineHeight: "1.65", color: "#544a40", margin: "9px 0px 0px", textWrap: "pretty" }}>{body}</p>;
            // Hero (win/streak): full-width, no gutter — deliberately a different
            // category from the symmetric energy/enough gutter cards.
            if (isHero) {
              return (
                <div key={key} style={{ ...sep, marginTop: "30px", marginBottom: "30px" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: "9px", margin: "0px 0px 10px" }}>
                    <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "9px", fontWeight: "600", letterSpacing: "0.11em", textTransform: "uppercase", color: p.text, whiteSpace: "nowrap" }}>{label}</span>
                    {tag && <span style={{ width: "5px", height: "5px", borderRadius: "9999px", background: "#c59148", flexShrink: 0 }}></span>}
                    {tag && <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "9px", fontWeight: "700", letterSpacing: "0.13em", textTransform: "uppercase", color: "#7d562d", whiteSpace: "nowrap" }}>{tag}</span>}
                  </div>
                  <p className="ins-fh" style={{ fontFamily: "'Fraunces', Georgia, serif", fontSize: "20px", fontWeight: "400", lineHeight: "1.28", letterSpacing: "-0.01em", color: "#241f1a", margin: "0px", textWrap: "pretty" }}>{capHead(headline)}</p>
                  {bodyP}
                </div>);
            }
            // Standard: symmetric gutter marginalia (energy / enough).
            return (
              <div key={key} style={{ display: "flex", gap: "18px", ...sep, marginTop: "0px", marginBottom: "0px", paddingTop: "20px", paddingBottom: "20px" }}>
                <div style={{ width: "66px", flexShrink: 0, paddingTop: "4px" }}>
                  <div style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontWeight: "600", letterSpacing: "0.11em", textTransform: "uppercase", color: p.text, lineHeight: "1.45", fontSize: "9px" }}>{label}</div>
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <p className="ins-fh" style={{ fontFamily: "'Fraunces', Georgia, serif", fontSize: "17px", fontWeight: "400", lineHeight: "1.34", letterSpacing: "-0.005em", color: "#241f1a", margin: "0px", textWrap: "pretty" }}>{capHead(headline)}</p>
                  {bodyP}
                </div>
              </div>);
          };

          // Win card (ochre). On 7d it celebrates the CURRENT streak (momentum,
          // anchored to today); on 30d/all it celebrates the LONGEST run within
          // the window (a retrospective record), so the card still earns its
          // place when there's no active streak today. Copy rotates weekly.
          const isRunMode = timeFilter !== '7d';
          const streakValue = isRunMode ? longestRun : winStreak;
          if (streakValue >= 2) {
            const nm = displayChildName === 'your children' ? 'your child' : displayChildName;
            const period = timeFilter === '30d' ? 'this month' : 'across all your logs';
            const fill = (s) => s.
            replace(/\{name\}/g, nm).
            replace(/\{n\}/g, streakValue).
            replace(/\{period\}/g, period);
            const wk = getWeekKey();

            let label, tag, headline, body;

            if (isRunMode) {
              // Retrospective "longest run" — past-tense, achievement framing.
              const headPool = [
              "{name}'s longest run was {n} days of reaching for something new.",
              "at one stretch {period}, {name} tried something new {n} days straight.",
              "{name} once met the unfamiliar {n} days in a row {period}."];

              const bodyPool = [
              "{n} days in a row is a rhythm, the kind that builds quietly.",
              "during that run, the new felt safe enough to keep reaching for.",
              "something held steady across those {n} days, worth remembering what."];

              label = 'the longest run';
              tag = `{n}-day run`;
              headline = fill(pickVariant(headPool, `${wk}|run-headline`));
              body = fill(pickVariant(bodyPool, `${wk}|run-body`));
            } else {
              // Current streak — present-tense momentum, tiered by length.
              let headPool, bodyPool;
              if (streakValue <= 2) {
                headPool = [
                "{name} tried something new two days running.",
                "two days in a row, {name} met something unfamiliar.",
                "{name} stepped past the familiar two days straight."];

                bodyPool = [
                "small, but not nothing. two days of reaching is a rhythm starting.",
                "two tries in two days, the start of a pattern worth watching.",
                "a try is a try, whether it's a food, a place, or a feeling. {name} did it twice."];

              } else if (streakValue <= 4) {
                headPool = [
                "{name} tried something new {n} days running.",
                "{n} days in a row, {name} reached past the familiar.",
                "that's {n} days of {name} meeting the new."];

                bodyPool = [
                "{n} days of meeting the new. that's a rhythm, not a fluke.",
                "{n} days running. the unfamiliar is starting to feel familiar to {name}.",
                "a stretch like this is {name} finding their footing with the new."];

              } else {
                headPool = [
                "{name} reached for something new {n} days in a row.",
                "{n} straight days of {name} choosing the unfamiliar."];

                bodyPool = [
                "this is {name} trusting that new things are safe to try.",
                "{n} days in a row. not a coincidence, a rhythm."];

              }
              label = 'a run of small tries';
              tag = `{n}-day streak`;
              headline = fill(pickVariant(headPool, `${wk}|win-headline`));
              body = fill(pickVariant(bodyPool, `${wk}|win-body`));
            }

            cards.push(renderInsightCard("win", INSIGHT.ochre, label, headline, body, fill(tag), true));
          }

          // 3 · Energy — your energy matters (rose)
          if (energyCorrelation && energyCorrelation.hasEnoughData) {
            const { correlationStrength, highEnergyFeelTier, lowEnergyFeelTier, highEnergyEntryCount } = energyCorrelation;

            // Pick the pool by correlation strength. Special case: a genuinely
            // heavy low-energy week (no correlation, heavy low-energy tier,
            // thin high-energy sample) gets its own gentler "harder week" pool,
            // which takes priority over the weak/none pool.
            const heavyWeek =
            correlationStrength === 'none' && (
            lowEnergyFeelTier === 'heavier' || lowEnergyFeelTier === 'heavy') &&
            highEnergyEntryCount < 2;

            let headPool, bodyPool;
            if (correlationStrength === 'strong') {
              headPool = [
              "on your higher-energy days, things tended to feel {highEnergyFeelTier}.",
              "when you had more to give, the day felt {highEnergyFeelTier}, consistently.",
              "your energy and the day's weight moved together this week."];

              bodyPool = [
              "you're not imagining it.",
              "this isn't about doing more. the days simply followed your energy this week.",
              "when you had more to give, the days leaned lighter too."];

            } else if (correlationStrength === 'moderate') {
              headPool = [
              "on your higher-energy days, things leaned {highEnergyFeelTier}.",
              "your energy had some influence on how days felt this week.",
              "there's a thread between how you were doing and how the day went."];

              bodyPool = [
              "it's not the whole story, but your energy ran through how the days felt.",
              "not every high-energy day was light, and not every low one was heavy. but the lean was real.",
              "one factor among many, never the whole of it."];

            } else if (heavyWeek) {
              headPool = [
              "it was a harder week in the feeling department.",
              "the week leaned heavy. that's what the data held.",
              "some weeks just weigh more. this was one of them."];

              bodyPool = [
              "a heavy week, logged honestly. that's all this needs to be.",
              "some weeks are for getting through. this looks like one of them.",
              "the weight was real. it's here on the record, not held alone in your head."];

            } else {
              headPool = [
              "your energy and the day's weight didn't follow each other closely this week.",
              "the lighter moments came regardless of how you were feeling.",
              "something held the week steady, even on the harder days for you."];

              bodyPool = [
              "on the days when you had less, things still found their way to okay.",
              "low-energy days didn't always turn heavy. some just held.",
              "this week the room had its own weather. you were in it, but you weren't causing it."];

            }

            const wk = getWeekKey();
            const fillTier = (s) => s.replace(/\{highEnergyFeelTier\}/g, highEnergyFeelTier);
            const headline = fillTier(pickVariant(headPool, `${wk}|energy-headline`));
            const body = fillTier(pickVariant(bodyPool, `${wk}|energy-body`));

            cards.push(renderInsightCard("energy", INSIGHT.rose, "your energy matters", headline, body, "your energy", false));
          }

          // 4 · Enough — enough is enough (slate)
          if (enoughInsight && enoughInsight.total >= 2) {
            const ei = enoughInsight;
            const wk = getWeekKey();

            // Condition tier — first match wins, in priority order.
            let condition;
            if (ei.allMatched) condition = 'all-low-heavy';else
            if (ei.allLowEnergy > 0) condition = 'some-low';else
            if (ei.lightDayEnough > 0 && ei.allLowEnergy === 0) condition = 'light-day';else
            condition = 'spread';

            // Headline: existing logic for every tier except light-day, which
            // gets its own rotating pool (these calls are the hardest to make).
            let headline;
            if (condition === 'light-day') {
              const headPool = [
              "some of your 'enough' calls came on lighter days. that's the hardest kind to make.",
              "you chose to stop even when the day wasn't forcing you to. that's different."];

              headline = pickVariant(headPool, `${wk}|enough-headline`);
            } else if (ei.allMatched) {
              headline = <>all {ei.total} of your “enough” calls came on <em style={{ fontStyle: "italic" }}>low-energy, heavy days</em>.</>;
            } else if (ei.allLowEnergy > 0) {
              headline = <>{ei.allLowEnergy} of your {ei.total} “enough” calls came on <em style={{ fontStyle: "italic" }}>low-energy days</em>.</>;
            } else {
              headline = <>you made <em style={{ fontStyle: "italic" }}>{ei.total} conscious “enough” call{ei.total !== 1 ? 's' : ''}</em> this period.</>;
            }

            // Body: week-rotating pool chosen by condition tier.
            const bodyPools = {
              'all-low-heavy': [
              "stopping on a low, heavy day is the hardest kind to call.",
              "stopping when you're depleted isn't quitting. it's reading the moment.",
              "a boundary held on a day that gave every reason to push through.",
              "you didn't push through on empty. you noticed, and you let go."],

              'some-low': [
              "the calls made on the low days were the right read.",
              "noticing you're depleted and acting on it, before things tip.",
              "you didn't wait until everything fell apart. you caught it earlier than that.",
              "some of those were harder than pushing through would've been."],

              'light-day': [
              "letting go on a fine day is different from letting go when you're spent. these were the fine-day kind.",
              "stopping on a day that wasn't forcing it, just a quieter kind of call.",
              "not every boundary comes from exhaustion. some just come from knowing."],

              'spread': [
              "reading the room across all kinds of days, not just the hard ones.",
              "you're not waiting until empty to let go.",
              "stopping well on any kind of day still counts as stopping well.",
              "these were decisions to stop, made on purpose."]

            };
            const body = pickVariant(bodyPools[condition], `${wk}|enough-body`);

            cards.push(renderInsightCard("enough", INSIGHT.slate, "enough is enough", headline, body, "rest and release", false));
          }

          return (
            <div style={{ paddingBottom: "40px", padding: "0px 0px 80px" }}>
                <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: "12px", padding: "0px", fontSize: "11px", margin: "1px 0px 0px" }}><p style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontWeight: "600", letterSpacing: "0.1em", textTransform: "uppercase", color: "#6e6258", margin: "2px 0px 0px", fontSize: "12px" }}>{timeFilter === '7d' ? 'based on your logs this week' : timeFilter === '30d' ? 'based on your logs this month' : 'based on everything you\u2019ve logged'}</p>
                </div>

                {cards.length > 0 ?
              <div style={{ display: "flex", flexDirection: "column", gap: insightLayout === 'cards' ? "10px" : "0px" }}>{cards}</div> :

              <div style={{ paddingTop: "28px" }}>
                    <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "14px", color: "#50453b", textWrap: "pretty", maxWidth: "30ch", margin: 0, lineHeight: "1.6" }}>keep logging and the patterns will start to show.</p>
                  </div>
              }
              </div>);

        })()}
        </div>
      }
    </div>);

}

function ChildScreen({ entries, childrenConfig, activeChildId, setActiveChildId, openAddModal, openEditModal, deleteEntry, updateEntry, navigateHome }) {
  const [expandedId, setExpandedId] = useState(null);
  const [timeFilter, setTimeFilter] = useState('7d');
  const [bitePage, setBitePage] = useState(1);

  // Long press to delete state
  const longPressTimer = useRef(null);
  const [deletingTrophyId, setDeletingTrophyId] = useState(null);
  const trophyDeleteRef = useModalA11y(!!deletingTrophyId, () => setDeletingTrophyId(null));

  const [renamingId, setRenamingId] = useState(null);
  const [renameInput, setRenameInput] = useState('');

  const activeChild = childrenConfig.find((c) => c.id === activeChildId) || childrenConfig[0];
  const isMultiChild = childrenConfig.length > 1;

  const childEntries = entries.filter((e) => e.childId === activeChild.id);

  const filterDateLimit = new Date();
  if (timeFilter === '7d') filterDateLimit.setDate(new Date().getDate() - 7);
  if (timeFilter === '30d') filterDateLimit.setDate(new Date().getDate() - 30);

  const biteEntries = childEntries.
  filter((e) => hasType(e, 'bite')).
  filter((e) => timeFilter === 'all' || new Date(e.timestamp) >= filterDateLimit).
  sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));

  useEffect(() => {
    setBitePage(1);
  }, [timeFilter, activeChildId]);

  const BITES_PER_PAGE = 6;
  const totalBitePages = Math.ceil(biteEntries.length / BITES_PER_PAGE);
  const currentBites = biteEntries.slice((bitePage - 1) * BITES_PER_PAGE, bitePage * BITES_PER_PAGE);

  const moodEntries = childEntries.filter((e) => e.sunsMood && e.sunsMood.length > 0);
  const socialEntries = childEntries.filter((e) => e.socialIntent);

  const getTitle = (entry) => {
    if (entry.customTitle) return entry.customTitle;
    if (hasType(entry, 'insight')) {
      const preferred = entry.noticedNote || entry.mealsNote || '';
      if (preferred.trim().length > 0) {
        return preferred.trim().length > 28 ?
        preferred.trim().slice(0, 28) + '…' :
        preferred.trim();
      }
    }
    if (entry.note && entry.note.trim().length > 0) {
      return entry.note.trim().length > 28 ?
      entry.note.trim().slice(0, 28) + '…' :
      entry.note.trim();
    }
    return entry.factors?.length > 0 ? entry.factors[0] : 'a discovery';
  };

  const getTrigger = (entry) => {
    // The label under each trophy — drawn only from real tagged data, never inferred from note text.
    if (entry.factors && entry.factors.length > 0) return entry.factors[0];
    if (entry.environment && entry.environment.length > 0) return entry.environment[0];
    if (entry.sunsMood && entry.sunsMood.length > 0) return entry.sunsMood[0];
    return 'a small try';
  };

  // Derive "Recent Catalyst" from real data
  const recentCatalyst = useMemo(() => {
    if (biteEntries.length === 0) return '-';
    const factorCounts = {};
    biteEntries.forEach((e) => {
      (e.factors || []).forEach((f) => {
        factorCounts[f] = (factorCounts[f] || 0) + 1;
      });
    });

    const sortedFactors = Object.entries(factorCounts).sort((a, b) => b[1] - a[1]);
    if (sortedFactors.length > 0) return sortedFactors[0][0];

    // Fallback to the next real tagged signal (environment / mood) when no factor is tagged
    const tagged = biteEntries.map(getTrigger).filter((t) => t !== 'a small try');
    if (tagged.length > 0) {
      const counts = tagged.reduce((acc, val) => {acc[val] = (acc[val] || 0) + 1;return acc;}, {});
      return Object.entries(counts).sort((a, b) => b[1] - a[1])[0][0];
    }

    return 'a small try';
  }, [biteEntries]);

  const moodPortrait = useMemo(() => {
    if (moodEntries.length < 3) return null;

    // Count frequency of each mood across all child entries
    const moodCounts = {};
    moodEntries.forEach((e) => {
      (e.sunsMood || []).forEach((m) => {
        moodCounts[m] = (moodCounts[m] || 0) + 1;
      });
    });

    // Sort by frequency, take top 3
    const topMoods = Object.entries(moodCounts).
    sort((a, b) => b[1] - a[1]).
    slice(0, 3).
    map(([mood]) => mood);

    if (topMoods.length === 0) return null;

    // Check correlation between each top mood and bite entries
    const moodBiteCorrelation = {};
    topMoods.forEach((mood) => {
      const entriesWithMood = childEntries.filter((e) =>
      (e.sunsMood || []).includes(mood)
      );
      const bitesWithMood = entriesWithMood.filter((e) => hasType(e, 'bite')).length;
      const biteRate = entriesWithMood.length > 0 ?
      Math.round(bitesWithMood / entriesWithMood.length * 100) :
      0;
      moodBiteCorrelation[mood] = { count: moodCounts[mood], bitesWithMood, biteRate };
    });

    // Insight copy — ranked by bite correlation so it matches the roster's ochre lead
    const rankedByBite = [...topMoods].sort((a, b) => moodBiteCorrelation[b].biteRate - moodBiteCorrelation[a].biteRate);
    const lead = rankedByBite[0];
    const leadRate = moodBiteCorrelation[lead].biteRate;
    const leadHasBite = moodBiteCorrelation[lead].bitesWithMood > 0;
    const lq = lead.toLowerCase();

    // Moods that tend to signal openness vs resistance
    const openMoods = ['Curious', 'Playful', 'Stable', 'Assertive', 'Hyper', 'Overstimulated'];
    const leadOpen = openMoods.includes(lead);
    const closedInSet = rankedByBite.find((m) => !openMoods.includes(m));

    let insight = '';

    if (leadHasBite && leadOpen) {
      insight = `${lq} days are when the world opens — ${leadRate}% of them held a one bite.`;
      if (closedInSet) insight += ` ${closedInSet.toLowerCase()} days ask for the familiar instead.`;
    } else if (leadHasBite && !leadOpen) {
      insight = `even on ${lq} days the tries come — ${leadRate}% held a one bite. the mood matters less than the moment.`;
    } else {
      insight = `the moods are starting to gather. give it a few more days and the openings will show.`;
    }

    const primary = topMoods[0];
    const secondary = topMoods[1];

    return { topMoods, moodBiteCorrelation, insight, primary, secondary };
  }, [moodEntries, childEntries, activeChild]);

  const socialArc = useMemo(() => {
    if (socialEntries.length < 3) return null;

    // Count frequency of each social intent
    const intentCounts = { High: 0, Observer: 0, Internal: 0, Quiet: 0 };
    socialEntries.forEach((e) => {
      if (intentCounts[e.socialIntent] !== undefined) {
        intentCounts[e.socialIntent]++;
      }
    });

    // Find dominant intent
    const dominant = Object.entries(intentCounts).
    sort((a, b) => b[1] - a[1])[0][0];

    const total = socialEntries.length;
    const dominantCount = intentCounts[dominant];
    const dominantPct = Math.round(dominantCount / total * 100);

    // Check if there's a meaningful secondary intent
    const sorted = Object.entries(intentCounts).
    filter(([, count]) => count > 0).
    sort((a, b) => b[1] - a[1]);
    const secondary = sorted.length > 1 ? sorted[1][0] : null;
    const secondaryPct = secondary ?
    Math.round(intentCounts[secondary] / total * 100) :
    0;

    // Check if shifting — compare recent vs older entries
    const recentEntries = [...socialEntries].
    sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)).
    slice(0, Math.ceil(socialEntries.length / 2));

    const recentDominant = recentEntries.length > 0 ?
    Object.entries(
      recentEntries.reduce((acc, e) => {
        if (e.socialIntent) acc[e.socialIntent] = (acc[e.socialIntent] || 0) + 1;
        return acc;
      }, {})
    ).sort((a, b) => b[1] - a[1])[0]?.[0] :
    null;

    const isShifting = recentDominant && recentDominant !== dominant;

    // Generate insight copy
    const childName = activeChild.name;
    let insight = '';

    const spectrumOrder = ['Internal', 'Quiet', 'Observer', 'High'];
    const dominantIdx = spectrumOrder.indexOf(dominant);
    const recentIdx = recentDominant ? spectrumOrder.indexOf(recentDominant) : -1;
    const movingToward = isShifting && recentIdx > dominantIdx ? 'more social' :
    isShifting && recentIdx < dominantIdx ? 'more internal' : null;

    if (dominant === 'High') {
      insight = `${childName} is right in the thick of it ${dominantPct}% of the time — joining, starting things, fully there.`;
    } else if (dominant === 'Observer') {
      insight = `${childName} reads the room before stepping in — watching closely is its own way of taking part, not holding back.`;
    } else if (dominant === 'Internal') {
      insight = `${childName} spends most of the time in their own world right now. the quieter the setting, the easier the day tends to go.`;
    } else if (dominant === 'Quiet') {
      insight = `${childName} is in a quiet stretch ${dominantPct}% of the time — low and unhurried. a place to rest, not a step back.`;
    }

    if (isShifting && movingToward) {
      insight += ` lately they've been drifting ${movingToward}, worth noticing.`;
    }

    if (secondary && secondaryPct >= 25 && secondaryPct < dominantPct) {
      insight += ` ${secondary.toLowerCase()} is there too, about ${secondaryPct}% of the days.`;
    }

    return {
      dominant,
      dominantPct,
      intentCounts,
      total,
      secondary,
      secondaryPct,
      isShifting,
      movingToward,
      recentDominant,
      insight
    };
  }, [socialEntries, activeChild]);

  return (
    <div className="flex-1 flex flex-col bg-[#fdf9f0] overflow-hidden">
      
      <div key={activeChild.id} className="screen-scroll flex-1 overflow-y-auto px-5 pb-32 animate-in fade-in duration-300">
        <ScreenHeader
          eyebrow="a gentle record"
          control={<>
            <SegFilter value={timeFilter} onChange={setTimeFilter} />
            <button onClick={openAddModal} className="sh-iconbtn" aria-label="add a moment"><Icons.Plus /></button>
          </>}
          title="the rhythm of discovery."
          subline="every small try is a bridge to the new. not a test to pass, but a moment to notice. quiet, gentle, meaningful."
          caption={childrenConfig.length <= 1 ? <><span className="capitalize">{activeChild.name}</span><button onClick={() => openEditModal(activeChild)} className="sh-cap-edit" aria-label="edit child"><Icons.Edit /></button></> : null} />
        

        <ChildSwitcher childrenConfig={childrenConfig} activeId={activeChildId} onSelect={setActiveChildId} trailing={<button onClick={() => openEditModal(activeChild)} className="cs-edit" aria-label="edit child"><Icons.Edit /></button>} />

        {childEntries.length === 0 ?
        <div className="animate-in fade-in" style={{ borderTop: "0.5px solid rgba(212,196,183,0.55)", marginTop: "26px", padding: "48px 0px 28px" }}>
          <h2 style={{ fontFamily: "'Fraunces', Georgia, serif", fontWeight: 400, letterSpacing: "-0.01em", lineHeight: 1.25, color: "#1c1c17", margin: "0 0 12px", fontSize: "20px" }}>{capName(activeChild.name)}'s first discovery and patterns are still ahead.</h2>
          <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "14px", lineHeight: 1.6, color: "#50453b", textWrap: "pretty", maxWidth: "32ch", margin: "0px 0px 26px" }}>
            log a moment from the home screen to begin. as the days gather, the tries and the rhythm of them will start to show here.
          </p>
          <button onClick={navigateHome} className="inline-flex items-center transition-opacity active:opacity-60" style={{ background: "none", border: "0", padding: "0", cursor: "pointer", gap: "6px" }}>
            <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: "#7d562d", whiteSpace: "nowrap" }}>go log something</span>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#7d562d" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
          </button>
        </div> :
        <React.Fragment>
        <div style={{ borderTop: "0.5px solid rgba(212,196,183,0.55)", paddingTop: "20px", padding: "20px 0px 0px", margin: "26px 0px 0px" }}>
        <StatCallout
              style={{ margin: "0px" }}
              stats={[
              { value: biteEntries.length, color: '#7d562d', label: `discoveries\n${timeFilter === 'all' ? 'since start' : 'in last ' + timeFilter}`, type: 'numeric' },
              { value: new Set(biteEntries.flatMap((e) => e.factors || [])).size || (biteEntries.length ? 1 : 0), color: '#1c1c17', label: 'top\ncatalysts', type: 'numeric' },
              { value: recentCatalyst, color: '#1c1c17', label: 'recent\ninfluence', type: 'text' }]
              } />
        </div>

        <div className="min-h-[140px]" style={{ borderTop: "0.5px solid rgba(212,196,183,0.55)", paddingTop: "32px", marginTop: "40px", margin: "26px 0px 0px" }}>
          <h2 className="text-[11px] uppercase tracking-widest font-semibold mb-3 text-[#6e6258]" style={{ margin: "0px 0px 30px" }}>The Collection of Try</h2>
          {biteEntries.length === 0 ?
            <div className="animate-in fade-in" style={{ paddingTop: "2px" }}>
               <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "14px", lineHeight: 1.65, color: "#50453b", textWrap: "pretty", margin: 0 }}>
                 {capName(activeChild.name)}'s first discovery is still ahead. when it happens, it'll live here — a shelf that fills one small try at a time.
               </p>
            </div> :

            <>
              <div style={{ position: "relative", paddingLeft: "30px" }}>
                 <div aria-hidden="true" style={{ position: "absolute", left: "5px", top: "8px", bottom: "16px", width: "0.5px", background: "linear-gradient(180deg, #d9a268 0%, rgba(199,162,112,0.55) 58%, rgba(212,196,183,0.28) 100%)" }}></div>
                 {currentBites.map((e) =>
                <div
                  key={e.id}
                  style={{ position: "relative", paddingBottom: "26px", cursor: "pointer" }}
                  onPointerDown={() => {
                    longPressTimer.current = setTimeout(() => setDeletingTrophyId(e.id), 600);
                  }}
                  onPointerUp={() => clearTimeout(longPressTimer.current)}
                  onPointerMove={() => clearTimeout(longPressTimer.current)}
                  onClick={() => {
                    if (renamingId !== e.id) {
                      setExpandedId(expandedId === e.id ? null : e.id);
                    }
                  }}>
                
                     <span aria-hidden="true" style={{ position: "absolute", left: "-30px", top: "1px", width: "13px", height: "13px", display: "flex", alignItems: "center", justifyContent: "center", background: "#fdf9f0" }}>
                       <svg width="13" height="13" viewBox="0 0 24 24" fill="#eccba2" stroke="#d49960" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M12 3.5 14.3 9.7 20.5 12 14.3 14.3 12 20.5 9.7 14.3 3.5 12 9.7 9.7 Z" /></svg>
                     </span>
                     <div style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "#6e6258", marginBottom: "5px" }}>
                         {formatDate(e.timestamp)}
                     </div>

                     {renamingId === e.id ?
                  <input
                    autoFocus
                    type="text"
                    value={renameInput}
                    maxLength={60}
                    onChange={(ev) => setRenameInput(ev.target.value)}
                    onBlur={() => {
                      if (renameInput.trim()) {
                        updateEntry(e.id, { customTitle: renameInput.trim() });
                      }
                      setRenamingId(null);
                      setRenameInput('');
                    }}
                    onKeyDown={(ev) => {
                      if (ev.key === 'Enter') ev.target.blur();
                      if (ev.key === 'Escape') {setRenamingId(null);setRenameInput('');}
                    }}
                    onClick={(ev) => ev.stopPropagation()}
                    placeholder="name this moment…"
                    style={{ width: "100%", fontFamily: "'Fraunces', Georgia, serif", fontSize: "16px", fontWeight: 400, lineHeight: 1.3, color: "#241f1a", border: "0", borderBottom: "0.5px solid #e1b877", outline: "none", paddingBottom: "2px", marginBottom: "6px", background: "transparent" }} /> :

                  <div
                    style={{ fontFamily: "'Fraunces', Georgia, serif", fontSize: "16px", fontWeight: 400, lineHeight: 1.3, letterSpacing: "-0.005em", color: "#241f1a", marginBottom: "6px", textWrap: "pretty", paddingRight: "40px" }}>
                         {getTitle(e)}
                       </div>
                  }

                     <span style={{ display: "inline-block", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "#7d562d", whiteSpace: "nowrap" }}>
                       {getTrigger(e)}
                     </span>

                     <span aria-hidden="true" style={{ position: "absolute", right: "16px", top: "14px", color: expandedId === e.id ? "#a88a52" : "#c9b9a4", display: "flex", transition: "transform 200ms ease, color 200ms ease", transform: expandedId === e.id ? "rotate(180deg)" : "none" }}>
                       <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={{ width: "17px", height: "17px" }}><path d="M6 9l6 6 6-6" /></svg>
                     </span>

  {expandedId === e.id &&
                  <div
                    className="mt-3 pt-3 animate-in fade-in"
                    style={{ borderTop: "0.5px solid rgba(212,196,183,0.6)" }}>
                  
    {(e.note || e.mealsNote || e.noticedNote || e.checkinNote) &&
                    <div style={{ marginBottom: "13px" }}>
    {hasType(e, 'insight') && (e.mealsNote || e.noticedNote || e.checkinNote) ?
                      <div className="space-y-2">
        {e.mealsNote &&
                        <div>
            <span className="text-[8px] uppercase tracking-widest font-semibold block mb-0.5" style={{ color: theme.parchment[500] }}>meals</span>
            <p className="text-[13px] font-serif leading-relaxed text-[#4a4440] italic">{e.mealsNote}</p>
          </div>
                        }
        {e.noticedNote &&
                        <div>
            <span className="text-[8px] uppercase tracking-widest font-semibold block mb-0.5" style={{ color: theme.parchment[500] }}>noticed</span>
            <p className="text-[13px] font-serif leading-relaxed text-[#4a4440] italic">{e.noticedNote}</p>
          </div>
                        }
        {e.checkinNote &&
                        <div>
            <span className="text-[8px] uppercase tracking-widest font-semibold block mb-0.5" style={{ color: theme.parchment[500] }}>check-in</span>
            <p className="text-[13px] font-serif leading-relaxed text-[#4a4440] italic">{e.checkinNote}</p>
          </div>
                        }
      </div> :

                      <p className="text-[13px] font-serif leading-relaxed text-[#4a4440] italic">
        "{e.note}"
      </p>
                      }
                  </div>
                    }
                  <div style={{ display: "flex", gap: "18px" }}>
                    <button onClick={(ev) => {ev.stopPropagation();setRenamingId(e.id);setRenameInput(e.customTitle || '');}} style={{ background: "none", border: "0", padding: "0", cursor: "pointer", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "#7d562d" }}>rename</button>
                    <button onClick={(ev) => {ev.stopPropagation();setDeletingTrophyId(e.id);}} style={{ background: "none", border: "0", padding: "0", cursor: "pointer", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "#a8675a" }}>delete</button>
                  </div>
  </div>
                  }
                   </div>
                )}
              </div>
              
              {totalBitePages > 1 &&
              <div className="flex items-center justify-center gap-[14px]" style={{ marginTop: "4px", padding: "10px 0px 4px" }}>
                  <button
                  onClick={() => setBitePage((p) => Math.max(1, p - 1))}
                  disabled={bitePage === 1}
                  aria-label="newer moments"
                  className="transition-opacity disabled:opacity-30 disabled:cursor-default"
                  style={{ fontFamily: "'Hanken Grotesk', sans-serif", background: "none", border: "0", color: "#8a6a3d", fontSize: "16px", lineHeight: "1", cursor: "pointer", padding: "4px 6px" }}>‹</button>
                  <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: "600", letterSpacing: "0.22em" }}>
                    <span style={{ color: "#7d562d" }}>{String(bitePage).padStart(2, '0')}</span>
                    <span style={{ color: "#cdbfa8" }}>{'\u2002\u2014\u2002'}</span>
                    <span style={{ color: "#9c8a72" }}>{String(totalBitePages).padStart(2, '0')}</span>
                  </span>
                  <button
                  onClick={() => setBitePage((p) => Math.min(totalBitePages, p + 1))}
                  disabled={bitePage === totalBitePages}
                  aria-label="older moments"
                  className="transition-opacity disabled:opacity-30 disabled:cursor-default"
                  style={{ fontFamily: "'Hanken Grotesk', sans-serif", background: "none", border: "0", color: "#8a6a3d", fontSize: "16px", lineHeight: "1", cursor: "pointer", padding: "4px 6px" }}>›</button>
                </div>
              }
            </>
            }
        </div>

        <div style={{ borderTop: "0.5px solid rgba(212,196,183,0.55)", paddingTop: "32px", marginTop: "40px", margin: "0px", padding: "36px 0px 0px" }}>
          {!moodPortrait ?
            <React.Fragment>
              <h2 className="text-[11px] uppercase tracking-widest font-semibold text-[#6e6258]" style={{ margin: "0 0 16px" }}>The Prevailing Moods</h2>
              <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "14px", lineHeight: 1.65, color: "#50453b", textWrap: "pretty", margin: 0 }}>
                a few more logs and the moods will start to show.
              </p>
            </React.Fragment> :

            (() => {
              // Frequency order is the portrait composition; bite-rate drives the data line
              const composed = moodPortrait.topMoods.slice(0, 3);
              const moodStyles = [
              { fontSize: "40px", fontStyle: "italic", color: "#7d562d", lineHeight: 1.05, letterSpacing: "-0.01em", marginBottom: "14px", opacity: 1 },
              { fontSize: "26px", fontStyle: "normal", color: "#50453b", lineHeight: 1.15, letterSpacing: "0", marginBottom: "11px", opacity: 1 },
              { fontSize: "17px", fontStyle: "normal", color: "#ab9d8f", lineHeight: 1.2, letterSpacing: "0", marginBottom: "0px", opacity: 1 }];

              const tick = (pos) => {
                const base = { position: "absolute", width: "11px", height: "11px" };
                if (pos === 'tl') return { ...base, top: 0, left: 0, borderTop: "0.5px solid #d4c4b7", borderLeft: "0.5px solid #d4c4b7" };
                if (pos === 'tr') return { ...base, top: 0, right: 0, borderTop: "0.5px solid #d4c4b7", borderRight: "0.5px solid #d4c4b7" };
                if (pos === 'bl') return { ...base, bottom: 0, left: 0, borderBottom: "0.5px solid #d4c4b7", borderLeft: "0.5px solid #d4c4b7" };
                return { ...base, bottom: 0, right: 0, borderBottom: "0.5px solid #d4c4b7", borderRight: "0.5px solid #d4c4b7" };
              };
              return (
                <React.Fragment>
                {/* The framed work */}
                <div style={{ position: "relative", padding: "28px 24px 20px", margin: "4px 6px 0", textAlign: "center" }}>
                  <span style={{ ...tick('tl'), color: "rgb(28, 28, 23)", borderTop: "0.5px solid rgb(130, 117, 106)", borderLeft: "0.5px solid rgb(130, 117, 106)", borderRightColor: "rgb(130, 117, 106)", borderBottomColor: "rgb(130, 117, 106)" }}></span><span style={{ ...tick('tr'), borderTop: "0.5px solid rgb(130, 117, 106)", borderRight: "0.5px solid rgb(130, 117, 106)", borderBottomColor: "rgb(130, 117, 106)", borderLeftColor: "rgb(130, 117, 106)" }}></span>
                  <span style={{ ...tick('bl'), borderBottom: "0.5px solid rgb(130, 117, 106)", borderLeft: "0.5px solid rgb(130, 117, 106)", borderTopColor: "rgb(130, 117, 106)", borderRightColor: "rgb(130, 117, 106)" }}></span><span style={{ ...tick('br'), borderBottom: "0.5px solid rgb(130, 117, 106)", borderRight: "0.5px solid rgb(130, 117, 106)", borderTopColor: "rgb(130, 117, 106)", borderLeftColor: "rgb(130, 117, 106)" }}></span>

                  {composed.map((mood, i) =>
                    <div key={mood} style={{ fontFamily: "'Fraunces', Georgia, serif", fontWeight: 300, ...moodStyles[i] }}>
                      {mood.toLowerCase()}
                    </div>
                    )}

                  {/* Data line */}
                  <div style={{ borderTop: "0.5px solid rgba(212,196,183,0.6)", paddingTop: "11px", marginTop: "14px" }}>
                    <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "9px", color: "#6e6258", letterSpacing: "0.03em", lineHeight: 1.5 }}>
                      {composed.map((mood, i) => {
                          const data = moodPortrait.moodBiteCorrelation[mood];
                          const n = data ? data.count : 0;
                          return (
                            <React.Fragment key={mood}>
                            {i > 0 && <span style={{ opacity: 0.55 }}> · </span>}
                            {mood.toLowerCase()} <span style={{ color: "#623f18", fontWeight: 500 }}>{n}</span>
                            {i === composed.length - 1 ? " days" : ""}
                          </React.Fragment>);

                        })}
                    </span>
                  </div>
                </div>

                {/* The wall text */}
                <div style={{ textAlign: "left", padding: "25px 8px 4px" }}>
                  <div style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontWeight: 600, letterSpacing: "0.12em", textTransform: "uppercase", color: "#6e6258", marginBottom: "10px", fontSize: "11px" }}>The Prevailing Moods</div>
                  <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "13px", lineHeight: 1.65, color: "#50453b", textWrap: "pretty", margin: 0 }}>
                    {moodPortrait.insight}
                  </p>
                </div>
              </React.Fragment>);

            })()
            }
        </div>

        <div style={{ margin: "72px 0px 76px" }}>
          {!socialArc ?
            <React.Fragment>
              <h2 className="text-[11px] uppercase tracking-widest font-semibold text-[#6e6258]" style={{ margin: "0 0 14px" }}>The Social Arc</h2>
              <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "14px", lineHeight: 1.65, color: "#50453b", textWrap: "pretty", margin: 0 }}>
                the social rhythm surfaces as the days add up.
              </p>
            </React.Fragment> :

            (() => {
              const spectrumOrder = ['Internal', 'Quiet', 'Observer', 'High'];
              const labels = ['internal', 'quiet', 'observer', 'high'];
              const idx = spectrumOrder.indexOf(socialArc.dominant);
              const phrase = {
                High: 'in the thick of it',
                Observer: 'observer',
                Internal: 'in their own world',
                Quiet: 'in the quiet'
              }[socialArc.dominant] || socialArc.dominant.toLowerCase();

              // Arc geometry — semicircle, four points along it
              const VW = 300,cx = 150,cy = 132,r = 120;
              const pts = spectrumOrder.map((k, i) => {
                const ang = Math.PI - i / (spectrumOrder.length - 1) * Math.PI;
                return { k, x: cx + r * Math.cos(ang), y: cy - r * Math.sin(ang), xPct: (cx + r * Math.cos(ang)) / VW * 100 };
              });
              const dom = pts[idx];
              return (
                <div style={{ margin: "4px 0px 0px", padding: "34px 0px 0px" }}>
                {/* The arc */}
                <div style={{ position: "relative", margin: "0 auto", maxWidth: "300px" }}>
                  <svg width="100%" viewBox={`0 0 ${VW} 150`} style={{ display: "block", overflow: "visible" }}>
                    <defs>
                      <linearGradient id="socialArcGrad" x1="0" y1="0" x2="1" y2="0">
                        <stop offset="0%" stopColor="#e3d4bd" />
                        <stop offset="42%" stopColor="#dcc6a8" />
                        <stop offset="74%" stopColor="#ecc699" />
                        <stop offset="100%" stopColor="#e0a86f" />
                      </linearGradient>
                    </defs>
                    <path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`} fill="none" stroke="url(#socialArcGrad)" strokeWidth="1.5" strokeLinecap="round" />
                    {pts.map((p, i) => {
                        const d = i === idx;
                        return <circle key={p.k} cx={p.x} cy={p.y} r={d ? 6 : 3} fill={d ? "#f0bd8b" : "#c9b9a4"} />;
                      })}
                    <circle cx={dom.x} cy={dom.y} r="11" fill="none" stroke="#7d562d" strokeWidth="1.2" />
                  </svg>
                  {/* Labels aligned under each point */}
                  <div style={{ position: "relative", height: "12px", marginTop: "2px" }}>
                    {pts.map((p, i) => {
                        const d = i === idx;
                        const edge = i === 0 ? { left: 0, transform: "none" } : i === pts.length - 1 ? { right: 0, left: "auto", transform: "none" } : { left: p.xPct + "%", transform: "translateX(-50%)" };
                        return (
                          <span key={p.k} style={{ position: "absolute", top: 0, ...edge, fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "9px", fontWeight: d ? 700 : 500, letterSpacing: "0.06em", textTransform: "uppercase", color: d ? "#7d562d" : "#82756a", whiteSpace: "nowrap" }}>{labels[i]}</span>);

                      })}
                  </div>
                </div>

                {/* The mode, named */}
                <div style={{ fontFamily: "'Fraunces', Georgia, serif", fontWeight: 300, fontStyle: "italic", fontSize: "32px", color: "#7d562d", lineHeight: 1.1, letterSpacing: "-0.01em", textAlign: "center", margin: "30px 0px 0px" }}>
                  {phrase}
                </div>
                {socialArc.isShifting && socialArc.movingToward &&
                  <p style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: 600, letterSpacing: "0.05em", textTransform: "uppercase", color: "#a8895f", textAlign: "center", margin: "8px 0 0" }}>— drifting {socialArc.movingToward}</p>
                  }

                {/* Placard + insight */}
                <div style={{ textAlign: "left", padding: "25px 8px 4px" }}>
                  <div style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: 600, letterSpacing: "0.12em", textTransform: "uppercase", color: "#6e6258", marginBottom: "10px" }}>The Social Arc</div>
                  <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "13px", lineHeight: 1.65, color: "#50453b", textWrap: "pretty", margin: 0 }}>
                    {socialArc.insight}
                  </p>
                </div>
              </div>);

            })()
            }
        </div>
        </React.Fragment>
        }
      </div>
      
      {deletingTrophyId &&
      <div className="fixed inset-0 z-[200] flex items-center justify-center animate-in fade-in" style={{ background: "rgba(28,28,23,0.18)", padding: "24px" }} onClick={() => setDeletingTrophyId(null)}>
          <div ref={trophyDeleteRef} className="animate-in fade-in" onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-label="remove this record?" style={{ background: "#ffffff", borderRadius: "12px", border: "0.5px solid #d4c4b7", padding: "22px 20px 18px", width: "100%", maxWidth: "300px" }}>
            <p style={{ fontFamily: "'Fraunces', Georgia, serif", fontSize: "17px", fontWeight: 400, color: "#1c1c17", textAlign: "center", margin: "0 0 6px", letterSpacing: "-0.01em" }}>remove this record?</p>
            <p style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "13px", lineHeight: 1.5, color: "#6e6258", textAlign: "center", margin: "0 0 18px" }}>this moment will leave your collection.</p>
            <div className="flex" style={{ gap: "8px" }}>
              <button
              onClick={() => setDeletingTrophyId(null)}
              className="flex-1 transition-all active:scale-[0.98]"
              style={{ padding: "11px", borderRadius: "8px", border: "0.5px solid #d4c4b7", background: "transparent", cursor: "pointer" }}>
                <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: "#50453b" }}>keep it</span>
              </button>
              <button
              onClick={() => {deleteEntry(deletingTrophyId);setDeletingTrophyId(null);}}
              className="flex-1 transition-all active:scale-[0.98]"
              style={{ padding: "11px", borderRadius: "8px", border: "0.5px solid rgba(168,103,90,0.5)", background: "transparent", cursor: "pointer" }}>
                <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: "#a8675a" }}>remove</span>
              </button>
            </div>
          </div>
        </div>
      }
    </div>);

}

function MeScreen({ setActiveTab, entries, childrenConfig, openSettings, addEntry, updateEntry, deleteEntry }) {
  const [reflectionText, setReflectionText] = useState(null);
  const [newWeekReady, setNewWeekReady] = useState(false);
  const [isGenerating, setIsGenerating] = useState(false);
  const [bottleIdx, setBottleIdx] = useState(0);
  const [kindnessPage, setKindnessPage] = useState(1);


  useEffect(() => {
    try {
      const stored = JSON.parse(localStorage.getItem('onebite_reflection') || 'null');
      if (stored?.text && stored?.weekKey) {
        if (stored.weekKey === getWeekKey()) {
          setReflectionText(stored.text);
        } else {
          setNewWeekReady(true);
        }
      }
    } catch {}
  }, []);

  const bottleQuotes = React.useMemo(() => {
    const eligible = entries.
    filter((e) =>
    hasType(e, 'enough') &&
    !hasType(e, 'insight') &&
    e.note &&
    e.note.trim().length > 10 &&
    e.energy !== null &&
    e.friction !== null
    ).
    sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));

    if (eligible.length === 0) return [];

    return eligible.map((e) => ({
      quote: e.note.trim(),
      date: new Date(e.timestamp).toLocaleDateString('en-US', {
        month: 'short',
        day: 'numeric'
      }).toLowerCase()
    }));
  }, [entries]);

  useEffect(() => {
    setBottleIdx(0);
  }, [bottleQuotes.length]);

  const [isLoggingKindness, setIsLoggingKindness] = useState(false);
  const [confirmingKindnessId, setConfirmingKindnessId] = useState(null);
  const [pressedKindnessId, setPressedKindnessId] = useState(null);
  const removeKindness = (id) => {
    const e = entries.find((x) => x.id === id);
    if (e && (e.types && e.types.length > 0 || e.note && e.note.trim())) {
      // kindness is attached to a real bite/enough entry — clear only the kindness
      updateEntry(id, { dailyKindness: false, dailyKindnessNote: '' });
      announce('Kindness note deleted.');
    } else {
      deleteEntry(id); // pure kindness note — announces "Kindness note deleted." centrally
    }
    setConfirmingKindnessId(null);
  };
  const [kindnessInput, setKindnessInput] = useState('');

  // Derive last 7 days of energy data from real entries
  const last7DaysEnergy = useMemo(() => {
    const days = [];
    for (let i = 6; i >= 0; i--) {
      const d = new Date();
      d.setDate(d.getDate() - i);
      const dateStr = d.toDateString();
      const dayLabel = i === 0 ? 'today' : d.toLocaleDateString('en-US', { weekday: 'short' }).toLowerCase();

      const dayEntries = entries.filter((e) =>
      new Date(e.timestamp).toDateString() === dateStr &&
      e.energy !== null &&
      e.energy !== undefined
      );

      // Determine dominant energy for the day
      // Priority: if any Max logged, use Max. Else most frequent.
      let dominantEnergy = null;
      if (dayEntries.length > 0) {
        const counts = { Max: 0, Functional: 0, Low: 0 };
        dayEntries.forEach((e) => {if (counts[e.energy] !== undefined) counts[e.energy]++;});
        if (counts.Max > 0) dominantEnergy = 'Max';else
        if (counts.Functional >= counts.Low) dominantEnergy = 'Functional';else
        dominantEnergy = 'Low';
      }

      const colorMap = {
        Max: { color: '#f0bd8b', height: '90%' },
        Functional: { color: 'rgba(130,117,106,0.35)', height: '60%' },
        Low: { color: 'rgba(184,142,141,0.5)', height: '32%' },
        null: { color: '#ece8df', height: '8%' }
      };

      const { color, height } = colorMap[dominantEnergy] || colorMap[null];

      days.push({ label: dayLabel, color, height, dominantEnergy });
    }
    return days;
  }, [entries]);

  const allNamesStr = childrenConfig.map((c) => c.name).join(' & ');

  // How much is there to reflect on? The mirror reflects on the WEEK, so it
  // gates on logs from the last 7 days; we also track whether anything has
  // ever been logged to soften the copy for brand-new vs. quiet weeks.
  const weekLogCount = useMemo(() => {
    const sevenDaysAgo = new Date();
    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
    return entries.filter((e) => e.note && new Date(e.timestamp) >= sevenDaysAgo).length;
  }, [entries]);
  const hasEverLogged = useMemo(() => entries.some((e) => e.note), [entries]);

  const weeklyPresence = useMemo(() => {
    const daysWithEntries = last7DaysEnergy.filter((d) => d.dominantEnergy !== null).length;

    let headline = '';
    let subline = '';

    if (daysWithEntries === 7) {
      headline = `you were here through all of it.`;
      subline = "that's the whole job, some weeks. you logged, you let go when you needed to, and you kept going.";
    } else if (daysWithEntries >= 5) {
      headline = `most of this week, you held it together.`;
      subline = "most of the week is here, and the gaps are okay too.";
    } else if (daysWithEntries >= 3) {
      headline = `some weeks are just about getting through.`;
      subline = "the days you did log still tell a story worth reading.";
    } else if (daysWithEntries >= 1) {
      headline = `you're still here. that counts.`;
      subline = "log when you can, rest when you need to.";
    } else {
      headline = "a fresh start.";
      subline = "nothing logged yet this week. no pressure — when something happens worth keeping, it'll be here waiting.";
    }

    return { daysWithEntries, headline, subline };
  }, [last7DaysEnergy, allNamesStr]);

  const kindnessEntries = entries.filter((e) => e.dailyKindness && e.dailyKindnessNote);
  const KINDNESS_PER_PAGE = 6;
  const totalKindnessPages = Math.ceil(kindnessEntries.length / KINDNESS_PER_PAGE);
  const currentKindness = kindnessEntries.slice((kindnessPage - 1) * KINDNESS_PER_PAGE, kindnessPage * KINDNESS_PER_PAGE);
  const enoughEntries = entries.filter((e) => hasType(e, 'enough') && e.note);

  const handleGenerateReflection = async () => {
    setIsGenerating(true);
    announce('Generating your reflection.');

    // Filter to last 7 days only
    const sevenDaysAgo = new Date();
    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
    const weekEntries = entries.filter((e) => new Date(e.timestamp) >= sevenDaysAgo);

    // Derive counts
    const bites = weekEntries.filter((e) => hasType(e, 'bite') && e.note).length;
    const enoughs = weekEntries.filter((e) => hasType(e, 'enough') && e.note).length;
    const kindnessDays = weekEntries.filter((e) => e.dailyKindness).length;
    const totalLogs = weekEntries.filter((e) => e.note).length;

    // Energy breakdown
    const energyLow = weekEntries.filter((e) => e.energy === 'Low').length;
    const energyFunctional = weekEntries.filter((e) => e.energy === 'Functional').length;
    const energyMax = weekEntries.filter((e) => e.energy === 'Max').length;

    // Friction breakdown
    const frictionHeavy = weekEntries.filter((e) => e.friction === 'Heavy').length;
    const frictionMixed = weekEntries.filter((e) => e.friction === 'Mixed').length;
    const frictionLight = weekEntries.filter((e) => e.friction === 'Light').length;

    // Top factors — count occurrences across all entries
    const factorCounts = {};
    weekEntries.forEach((e) => {
      (e.factors || []).forEach((f) => {
        factorCounts[f] = (factorCounts[f] || 0) + 1;
      });
    });
    const topFactors = Object.entries(factorCounts).
    sort((a, b) => b[1] - a[1]).
    slice(0, 5).
    map(([name, count]) => `${name} × ${count}`).
    join(', ') || 'none logged';

    // Entry notes — every note from the last 7 days, with type and energy context.
    // When there's more than one child, tag each line with whose moment it was —
    // otherwise the model can't tell the children's notes apart and invents attributions.
    const isMulti = childrenConfig.length > 1;
    const childNameById = {};
    childrenConfig.forEach((c) => {childNameById[c.id] = capName(c.name);});

    const entryLines = weekEntries.
    filter((e) => e.note).
    map((e, i) => {
      const type = hasType(e, 'bite') ? 'one bite' : 'enough';
      const who = isMulti ? `(${childNameById[e.childId] || 'unspecified'}) ` : '';
      const energy = e.energy ? ` · energy: ${e.energy}` : '';
      const friction = e.friction ? ` · friction: ${e.friction}` : '';
      const factors = e.factors?.length ? ` · factors: ${e.factors.join(', ')}` : '';
      return `${i + 1}. ${who}[${type}] ${e.note}${energy}${friction}${factors}`;
    }).
    join('\n');

    const systemPrompt = `You are the reflective voice inside "One Bite / Enough" — a parent wellness app for parents tracking their child's growth, daily moments, and their own wellbeing. Write a short, deeply personal weekly mirror reflection (3 to 4 short paragraphs, under 180 words). Voice rules — non-negotiable: Warm but never generic. Use the data to understand the shape of the week — don't recite numbers back. If the week was heavy, name that honestly before offering perspective. If there were wins, hold them gently alongside whatever else happened. No filler affirmations — comfort should feel earned, not automatic. Frame "enough" moments as conscious choices, not failures — without over-praising them. Never use the construction 'that wasn't X' or 'these weren't X' to reassure (e.g. 'not a failure,' 'wasn't giving up'). Naming the fear plants it. State only what the choice was, not what it wasn't. Don't raise a fear in order to deny it — avoid telling the parent they didn't fail or shouldn't feel guilty; name what happened instead. Notice what the parent actually wrote, not just how many times they wrote. Reference specific moments from their notes when possible. Write in second person ("you"). End with one quiet, resonant sentence that the parent might carry into the next week. Format: Plain prose only, no headers or bullet points.`;
    const userPrompt = `Here is my week in One Bite / Enough.

    ${isMulti ? `Children: ${allNamesStr}. Each note below is tagged with which child it's about — keep them distinct; never attribute one child's moment to another.` : `Child: ${allNamesStr}`}

    What I wrote this week (most recent first):
    ${entryLines || 'No notes logged this week.'}

    The numbers behind the week:
    ${totalLogs} total logs · ${bites} one bite moments · ${enoughs} enough moments · ${kindnessDays} of 7 days with daily kindness
    Energy: Low × ${energyLow}, Functional × ${energyFunctional}, Max × ${energyMax}
    How it felt: Light × ${frictionLight}, Mixed × ${frictionMixed}, Heavy × ${frictionHeavy}
    Top factors: ${topFactors}

    Please write my weekly mirror reflection.`;

    const response = await generateAIContent(systemPrompt, userPrompt);
    const weekKey = getWeekKey();

    // Check if the response is one of our fallback error messages
    const isError = response === "the words aren't coming just now. try again in a little while." ||
    response === "the reflection didn't come together this time. take a breath, and try again.";

    // ONLY save to local storage if it was a successful generation
    if (!isError) {
      localStorage.setItem('onebite_reflection', JSON.stringify({ text: response, weekKey }));
      announce('Your reflection is ready.');
    } else {
      announce("Reflection couldn't be generated. Please try again.");
    }

    setReflectionText(response);
    setNewWeekReady(false);
    setIsGenerating(false);
  };
  const saveKindness = () => {
    if (!kindnessInput.trim()) {
      setIsLoggingKindness(false);
      return;
    }
    addEntry({
      id: Math.random().toString(36).substr(2, 9),
      childId: null,
      types: [], dayType: null, note: '', timestamp: new Date().toISOString(),
      energy: null, friction: null, sunsMood: [], environment: [], factors: [], socialIntent: null,
      dailyKindness: true, dailyKindnessNote: kindnessInput.trim()
    });
    setKindnessInput('');
    setIsLoggingKindness(false);
  };

  const isErrorState = reflectionText === "the words aren't coming just now. try again in a little while." || reflectionText === "the reflection didn't come together this time. take a breath, and try again.";

  return (
    <div className="screen-scroll flex-1 overflow-y-auto px-4 pb-4 bg-[#f7f3ea]" style={{ backgroundColor: "rgb(253, 249, 240)" }}>
      <ScreenHeader
        eyebrow="your quiet hour"
        control={<button onClick={openSettings} className="sh-iconbtn" aria-label="settings"><Icons.Settings /></button>}
        title={weeklyPresence.headline}
        subline={weeklyPresence.subline} />
      

      <div className="mb-8" style={{ borderTop: "0.5px solid rgba(212,196,183,0.55)", padding: "22px 0px 0px", margin: "26px 0px 54px" }}>
        <div className="flex justify-between items-baseline mb-3" style={{ margin: "0px 0px 15px" }}>
          <h2 className="text-[11px] uppercase tracking-widest font-semibold text-[#6e6258]">Your Energy This Week</h2>
          <span className="text-[10px] text-[#82756a]" style={{ fontFamily: "'Hanken Grotesk', sans-serif", letterSpacing: "0.04em", textTransform: "uppercase", whiteSpace: "nowrap" }}>{(() => {const fmt = (d) => `${d.toLocaleDateString('en-US', { month: 'short' }).toLowerCase()} ${d.getDate()}`;const t = new Date();const s = new Date();s.setDate(t.getDate() - 6);return `${fmt(s)} – ${fmt(t)}`;})()}</span>
        </div>
        <div style={{ margin: "4px -2px 0" }}>
           {!last7DaysEnergy.some((d) => d.dominantEnergy) ?
          <p className="italic" style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "14px", lineHeight: "1.65", color: "#9a7c77", padding: "6px 0 2px 14px" }}>the week fills in as you go.</p> :
          last7DaysEnergy.map((day, i) => {
            const widthFor = { Max: '100%', Functional: '60%', Low: '30%' };
            const barColor = { Max: '#e0a86a', Functional: 'rgba(120,107,96,0.55)', Low: 'rgba(184,142,141,0.75)' };
            const today = day.label === 'today';
            const de = day.dominantEnergy;
            return (
              <div key={i} style={{ display: "flex", alignItems: "center", gap: "12px", padding: "9px 0", borderTop: i === 0 ? "none" : "0.5px solid rgba(212,196,183,0.55)" }}>
                <span style={{ width: "44px", flexShrink: 0, fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", letterSpacing: "0.08em", textTransform: "uppercase", color: today ? "#82756a" : "#a99a8c", fontWeight: today ? 600 : 500 }}>{day.label}</span>
                <span style={{ flex: 1, height: "7px", borderRadius: "9999px", background: "rgba(212,196,183,0.18)", position: "relative" }}>
                  {de && <span className="transition-all duration-500" style={{ position: "absolute", left: 0, top: 0, bottom: 0, width: widthFor[de], borderRadius: "9999px", backgroundColor: barColor[de] }}></span>}
                </span>
                <span style={{ width: "70px", flexShrink: 0, textAlign: "right", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", letterSpacing: "0.04em", fontWeight: 500, color: de ? "#b0a294" : "#cabdad" }}>{de ? de.toLowerCase() : '\u2014'}</span>
              </div>);

          })}
        </div>
      </div>
      
      <div className="mb-0" style={{ marginLeft: "-20px", marginRight: "-20px", paddingLeft: "20px", paddingRight: "20px", position: "relative", zIndex: 1, display: "flow-root", background: "linear-gradient(to bottom, rgba(184,142,141,0) 0%, rgba(184,142,141,0) 52%, rgba(184,142,141,0.05) 100%)" }}>
        <div className="flex justify-between items-baseline mb-3" style={{ gap: "12px" }}>
          <h2 className="text-[11px] uppercase tracking-widest font-semibold text-[#6e6258]" style={{ whiteSpace: "nowrap" }}>The Week's Mirror</h2>
          <span className="text-[11px] text-[#82756a] italic" style={{ fontWeight: "300", whiteSpace: "nowrap" }}>refreshes sunday</span>
        </div>
        <div style={{ fontWeight: "400" }}>
          {isGenerating &&
          <div style={{ position: "relative", paddingLeft: "14px", paddingTop: "2px", paddingBottom: "10px" }}>
              <div aria-hidden="true" style={{ position: "absolute", left: 0, top: 0, bottom: "-28px", width: "1.5px", background: "linear-gradient(to bottom, #eabbba 0%, #eabbba 58%, rgba(184,142,141,0) 100%)" }}></div>
              <p className="font-serif italic animate-pulse" style={{ fontSize: "14px", lineHeight: "1.65", color: "#9a7c77", marginTop: "20px", marginBottom: "10px", maxWidth: "290px" }}>synthesizing your invisible labor…</p>
            </div>
          }
          {!isGenerating && reflectionText &&
          <div style={{ position: "relative", paddingLeft: "14px", padding: "12px 0px 0px 14px" }}>
              <div aria-hidden="true" style={{ position: "absolute", left: 0, top: 0, bottom: "-92px", width: "1.5px", background: "linear-gradient(to bottom, #eabbba 0%, #eabbba 50%, rgba(184,142,141,0) 100%)" }}></div>
              {reflectionText.split('\n\n').map((p, idx) =>
            <p key={idx} className={`font-serif leading-[1.75] mb-4 ${isErrorState ? 'text-[#a09890] text-center italic' : 'text-[#735a55]'}`} style={{ color: "rgb(115, 90, 85)", fontWeight: "350", fontSize: "16px", margin: "8px 0px 18px" }}>
                  {p}
                </p>
            )}
              
              {!isErrorState &&
            <p style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", letterSpacing: "0.1em", textTransform: "uppercase", color: "#b08785", fontWeight: 600, margin: "14px 0 0" }}>{(() => {const fmt = (d) => `${d.toLocaleDateString('en-US', { month: 'short' }).toLowerCase()} ${d.getDate()}`;const t = new Date();const s = new Date();s.setDate(t.getDate() - 6);return `the week of ${fmt(s)} – ${fmt(t)}`;})()}</p>
            }
              {isErrorState &&
            <div className="flex flex-col items-center justify-center py-2">
                  <button onClick={handleGenerateReflection} className="ws-generate inline-flex items-center transition-all active:scale-[0.98]" style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: "600", letterSpacing: "0.05em", color: "#fff", backgroundColor: "#a86a68", border: "0", borderRadius: "999px", padding: "10px 18px", cursor: "pointer", whiteSpace: "nowrap" }}>
                    try again
                  </button>
                </div>
            }
            </div>
          }
          {!isGenerating && !reflectionText && weekLogCount === 0 &&
          <div style={{ position: "relative", paddingLeft: "14px", paddingTop: "2px", paddingBottom: "10px" }}>
              <div aria-hidden="true" style={{ position: "absolute", left: 0, top: 0, bottom: "-28px", width: "1.5px", background: "linear-gradient(to bottom, #eabbba 0%, #eabbba 58%, rgba(184,142,141,0) 100%)" }}></div>
              <p className="font-serif italic" style={{ fontSize: "14px", lineHeight: "1.65", color: "#9a7c77", marginTop: "20px", marginBottom: "16px", maxWidth: "290px" }}>
                {hasEverLogged ?
              'a quiet week here so far. the mirror reflects the days you log \u2014 add a moment when one comes, and it will have something to show you on sunday.' :
              'the mirror reflects your week back to you \u2014 but it needs a few moments first. log a day or two, and it will have something to hold.'}
              </p>
              <button onClick={() => setActiveTab('Diary')} className="inline-flex items-center gap-2 transition-all active:scale-[0.98]" style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: "600", letterSpacing: "0.05em", color: "#a86a68", background: "none", border: "0", padding: "2px 0", cursor: "pointer", whiteSpace: "nowrap" }}>
                <span>log a moment</span>
                <span aria-hidden="true" style={{ fontSize: "13px", lineHeight: "1" }}>→</span>
              </button>
            </div>
          }
          {!isGenerating && !reflectionText && weekLogCount > 0 && newWeekReady &&
          <div style={{ position: "relative", paddingLeft: "14px", paddingTop: "2px", paddingBottom: "10px" }}>
              <div aria-hidden="true" style={{ position: "absolute", left: 0, top: 0, bottom: "-28px", width: "1.5px", background: "linear-gradient(to bottom, #eabbba 0%, #eabbba 58%, rgba(184,142,141,0) 100%)" }}></div>
              <p className="font-serif italic" style={{ fontSize: "14px", lineHeight: "1.65", color: "#9a7c77", marginTop: "20px", marginBottom: "16px", maxWidth: "290px" }}>a new week, a new mirror.</p>
              <button onClick={handleGenerateReflection} className="ws-generate inline-flex items-center gap-2 transition-all active:scale-[0.98]" style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: "600", letterSpacing: "0.05em", color: "#fff", backgroundColor: "#a86a68", border: "0", borderRadius: "999px", padding: "10px 18px", cursor: "pointer", whiteSpace: "nowrap" }}>
                <span>open this week's reflection</span>
                <span aria-hidden="true" style={{ fontSize: "13px", lineHeight: "1" }}>→</span>
              </button>
            </div>
          }
          {!isGenerating && !reflectionText && weekLogCount > 0 && !newWeekReady &&
          <div style={{ position: "relative", paddingLeft: "14px", paddingTop: "2px", paddingBottom: "10px" }}>
              <div aria-hidden="true" style={{ position: "absolute", left: 0, top: 0, bottom: "-28px", width: "1.5px", background: "linear-gradient(to bottom, #eabbba 0%, #eabbba 58%, rgba(184,142,141,0) 100%)" }}></div>
              <p className="font-serif italic" style={{ fontSize: "14px", lineHeight: "1.6", color: "#9a7c77", marginTop: "20px", marginBottom: "18px", maxWidth: "280px" }}>
                The mirror is ready.
              </p>
              <button onClick={handleGenerateReflection} className="ws-generate inline-flex items-center gap-2 transition-all active:scale-[0.98]" style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: "600", letterSpacing: "0.05em", color: "#fff", backgroundColor: "#a86a68", border: "0", borderRadius: "999px", padding: "10px 18px", cursor: "pointer", whiteSpace: "nowrap" }}>
                <span>generate my reflection</span>
                <span aria-hidden="true" style={{ fontSize: "13px", lineHeight: "1" }}>→</span>
              </button>
            </div>
          }
        </div>
      </div>

      {/* Inward-journey transition: a long, gradual fade from the warmed mirror into the rose self-care zone */}
      <div aria-hidden="true" style={{ marginLeft: "-20px", marginRight: "-20px", background: "linear-gradient(to bottom, rgba(184,142,141,0.05) 0%, rgba(184,142,141,0.09) 100%)", height: "60px" }}></div>

      {/* Rose wash zone — Kindness Archive + Bottle + behind the floating nav.
                                                                                                                                                                                                                                                                                                                                                                                         Full-bleed via negative side margins; extends past the scroll's
                                                                                                                                                                                                                                                                                                                                                                                         108px bottom padding so the wash sits continuously behind the nav. */}
      <div style={{ marginLeft: "-20px", marginRight: "-20px", paddingLeft: "20px", paddingRight: "20px", paddingTop: "24px", background: "rgba(184,142,141,0.09)", marginBottom: "-108px", paddingBottom: "212px" }}>

      <div className="mb-8">
        <h2 className="kindness-eyebrow text-[11px] uppercase tracking-widest font-semibold mb-3">Kindness Archive</h2>
        {kindnessEntries.length === 0 ?
          <div style={{ padding: "6px 0 26px" }}>
             <p className="italic" style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "14px", lineHeight: "1.65", color: "#9a7c77", maxWidth: "300px", paddingLeft: "14px" }}>nothing yet — something small counts.</p>
          </div> :

          <div className="relative flex flex-col gap-[7px]">
            {currentKindness.map((e) => {
              const dt = new Date(e.timestamp);
              const mon = dt.toLocaleDateString('en-US', { month: 'short' }).toUpperCase();
              const day = dt.getDate();
              const pressed = pressedKindnessId === e.id;
              const confirming = confirmingKindnessId === e.id;
              return (
                <div
                  key={e.id}
                  onClick={() => {if (!confirming) setPressedKindnessId(pressed ? null : e.id);}}
                  style={{ position: "relative", background: "rgba(184,142,141,0.10)", borderRadius: "10px", padding: "14px", cursor: "pointer" }}>
                <div style={{ display: "flex", gap: "14px", alignItems: "flex-start" }}>
                  <div style={{ width: "28px", flexShrink: 0, textAlign: "left", paddingTop: "2px" }}>
                    <span style={{ display: "block", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "8px", fontWeight: 600, letterSpacing: "0.05em", textTransform: "uppercase", color: "#96615f", lineHeight: 1 }}>{mon}</span>
                    <span style={{ display: "block", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "13px", fontWeight: 600, color: "#96615f", lineHeight: 1, marginTop: "1px" }}>{day}</span>
                  </div>
                  <p className="ka-note" style={{ flex: 1, minWidth: 0, fontSize: "13px", lineHeight: 1.55, color: "#1c1c17", paddingRight: "24px" }}>{e.dailyKindnessNote}</p>
                </div>
                {confirming ?
                  <div className="animate-in fade-in" style={{ position: "absolute", top: "14px", right: "12px", display: "flex", alignItems: "center", gap: "14px" }}>
                    <button onClick={(ev) => {ev.stopPropagation();removeKindness(e.id);setPressedKindnessId(null);}} style={{ background: "none", border: "0", padding: "0", cursor: "pointer", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "#a8675a" }}>remove</button>
                    <button onClick={(ev) => {ev.stopPropagation();setConfirmingKindnessId(null);setPressedKindnessId(null);}} style={{ background: "none", border: "0", padding: "0", cursor: "pointer", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "#a99692" }}>keep</button>
                  </div> :
                  <button onClick={(ev) => {ev.stopPropagation();setConfirmingKindnessId(e.id);}} aria-label="remove this kindness" tabIndex={pressed ? 0 : -1} style={{ position: "absolute", top: "14px", right: "12px", background: "none", border: "0", padding: "0", cursor: "pointer", color: "#b88e8d", display: "flex", opacity: pressed ? 0.7 : 0, pointerEvents: pressed ? "auto" : "none" }}>
                    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6" /><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" /><path d="M10 11v6" /><path d="M14 11v6" /><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" /></svg>
                  </button>
                  }
              </div>);

            })}
          </div>
          }

{totalKindnessPages > 1 &&
          <div className="flex items-center justify-center gap-[14px] mt-3" style={{ borderTop: "0.5px solid rgba(160,122,120,0.25)", borderBottom: "0.5px solid rgba(160,122,120,0.25)", padding: "14px 0" }}>
            <button
              onClick={() => setKindnessPage((p) => Math.max(1, p - 1))}
              disabled={kindnessPage === 1}
              aria-label="newer entries"
              className="ka-pg-arrow transition-opacity disabled:opacity-30 disabled:cursor-default"
              style={{ fontFamily: "'Hanken Grotesk', sans-serif", background: "none", border: "0", color: "#8a6b69", fontSize: "16px", lineHeight: "1", cursor: "pointer", padding: "4px 6px" }}>‹</button>
            <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: "600", letterSpacing: "0.22em" }}>
              <span style={{ color: "#7a544f" }}>{String(kindnessPage).padStart(2, '0')}</span>
              <span style={{ color: "#c2b0a8" }}>{'\u2002\u2014\u2002'}</span>
              <span style={{ color: "#9c8a82" }}>{String(totalKindnessPages).padStart(2, '0')}</span>
            </span>
            <button
              onClick={() => setKindnessPage((p) => Math.min(totalKindnessPages, p + 1))}
              disabled={kindnessPage === totalKindnessPages}
              aria-label="older entries"
              className="ka-pg-arrow transition-opacity disabled:opacity-30 disabled:cursor-default"
              style={{ fontFamily: "'Hanken Grotesk', sans-serif", background: "none", border: "0", color: "#8a6b69", fontSize: "16px", lineHeight: "1", cursor: "pointer", padding: "4px 6px" }}>›</button>
          </div>
          }
        
        {/* Stable wrapper: the hairline + top spacing live here so they DON'T
            move when the trigger morphs into the input. Both inner states are 54px tall and start at the same Y, so there is no jump. */}
        <div style={{ borderTop: totalKindnessPages > 1 ? "0" : "0.5px solid rgba(160,122,120,0.25)", marginTop: totalKindnessPages > 1 ? "10px" : "12px", paddingTop: totalKindnessPages > 1 ? "0px" : "12px", paddingBottom: "10px", padding: "0px 0px 30px" }}>
        {isLoggingKindness ?
            <div className="animate-in fade-in">
            <div className="flex items-center gap-[11px]" style={{ height: "42px", padding: "0 2px" }}>
              <span className="ka-seed" style={{ width: "6px", height: "6px", borderRadius: "9999px", background: "var(--tertiary-strong, #b88e8d)", flex: "0 0 auto" }}></span>
              <input
                  autoFocus
                  type="text"
                  value={kindnessInput}
                  maxLength={500}
                  onChange={(e) => setKindnessInput(e.target.value)}
                  onKeyDown={(e) => {if (e.key === 'Enter' && kindnessInput.trim()) {saveKindness();} else if (e.key === 'Escape') {setIsLoggingKindness(false);}}}
                  placeholder="what did you do for yourself today?"
                  className="ka-log-input flex-1 min-w-0"
                  style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontStyle: "italic", fontSize: "14px", color: "#3a2f29", lineHeight: "1.4", padding: "0", margin: "0", border: "0", outline: "none", boxShadow: "none", background: "transparent", appearance: "none", WebkitAppearance: "none", borderRadius: "0" }} />
            </div>
          
            <div className="flex justify-end gap-2" style={{ marginTop: "2px", paddingBottom: "2px" }}>
              <button onClick={() => setIsLoggingKindness(false)} className="px-3 py-1.5 text-[11px] font-medium transition-colors active:text-[#3a2f24]" style={{ fontFamily: "'Hanken Grotesk', sans-serif", color: "#9b7e79" }}>cancel</button>
              <button disabled={!kindnessInput.trim()} onClick={saveKindness} className="px-4 py-1.5 text-[11px] font-semibold transition-all active:scale-95" style={{ borderRadius: "8px", fontFamily: "'Hanken Grotesk', sans-serif", letterSpacing: "0.04em", backgroundColor: kindnessInput.trim() ? "var(--tertiary-strong, #b88e8d)" : "rgba(184,142,141,0.20)", color: kindnessInput.trim() ? "#fff" : "#a98f8a", cursor: kindnessInput.trim() ? "pointer" : "default" }}>save</button>
            </div>
          </div> :

            <button onClick={() => setIsLoggingKindness(true)} className="kindness-add w-full flex items-center justify-center gap-2 transition-colors active:scale-[0.98]" style={{ padding: "12px", borderRadius: "8px", border: "0.5px solid rgba(184,142,141,0.45)", background: "transparent", marginTop: "6px", cursor: "pointer", WebkitTapHighlightColor: "transparent" }}>
            <span style={{ display: "flex" }} aria-hidden="true"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#96615f" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M12 5v14M5 12h14" /></svg></span>
            <span className="kindness-add-label" style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "#96615f", whiteSpace: "nowrap" }}>log a kindness</span>
          </button>
            }
        </div>
      </div>

      {bottleQuotes.length >= 3 &&
        <div className="animate-in fade-in" style={{ margin: "0px 0px 34px" }}>
          <h2 className="mb-3" style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontStyle: "italic", fontWeight: "400", color: "#96615f", textTransform: "none", letterSpacing: "0", marginBottom: "8px", fontSize: "18px", margin: "0px 0px 15px" }}>you've been here before.</h2>
          <div className="relative">
            <span aria-hidden="true" style={{ position: "absolute", top: "24px", right: "0", fontFamily: "'Fraunces', Georgia, serif", fontSize: "52px", fontWeight: "400", color: "rgba(184,142,141,0.25)", lineHeight: "0.8", pointerEvents: "none", userSelect: "none" }}>”</span>
            <p className="tracking-widest uppercase" style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", color: "#96615f", fontWeight: "600", margin: "0px 0px 10px" }}>proof that the heavy days pass</p>
            <p className="bottle-quote whitespace-pre-wrap" style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontStyle: "italic", color: "#735a55", fontSize: "15px", lineHeight: "1.7", margin: "0px 0px 16px", paddingRight: "28px", fontWeight: "400" }}>
              {bottleQuotes[bottleIdx % bottleQuotes.length]?.quote}
            </p>
            <p className="text-[11px] mb-5" style={{ color: "#b08785", fontWeight: "500" }}>
              — you, {bottleQuotes[bottleIdx % bottleQuotes.length]?.date}
            </p>
            <button
              onClick={() => setBottleIdx((i) => (i + 1) % bottleQuotes.length)}
              className="bottle-next inline-flex items-center gap-2 text-[11px] transition-all"
              style={{ backgroundColor: "rgba(184,142,141,0.18)", color: "#96615f", borderRadius: "999px", padding: "10px 18px", letterSpacing: "0.04em", fontWeight: "600", position: "relative", zIndex: 1, border: "0.5px solid rgba(184, 142, 141, 0.4)" }}>
                another one
                <span className="bn-arrow" aria-hidden="true" style={{ fontWeight: "400", opacity: "0.75" }}>→</span>
            </button>
          </div>
        </div>
        }

      </div>
    </div>);

}

function DiaryScreen({ entries, addEntry, updateEntry, deleteEntry, childrenConfig, activeChildId }) {
  const activeChild = childrenConfig.find((c) => c.id === activeChildId) || childrenConfig[0] || {};
  const [confirmingDelete, setConfirmingDelete] = useState(false);

  const [selectedChildId, setSelectedChildId] = useState(activeChildId || childrenConfig[0]?.id);
  const isMultiChild = childrenConfig.length > 1;

  const [types, setTypes] = useState(['insight']);
  const [dayType, setDayType] = useState(null);
  const [energy, setEnergy] = useState(null);
  const [sunsMood, setSunsMood] = useState([]);
  const [friction, setFriction] = useState(null);
  const [showContext, setShowContext] = useState(false);
  const [environment, setEnvironment] = useState([]);
  const [factors, setFactors] = useState([]);
  const [socialIntent, setSocialIntent] = useState(null);
  const [mealsNote, setMealsNote] = useState('');
  const [noticedNote, setNoticedNote] = useState('');
  const [checkinNote, setCheckinNote] = useState('');
  const [kindness, setKindness] = useState(false);
  const [kindnessNote, setKindnessNote] = useState('');
  const [savedEntryId, setSavedEntryId] = useState(null);
  const [isSaved, setIsSaved] = useState(false);

  const toggleArr = (arr, val) => arr.includes(val) ? arr.filter((x) => x !== val) : [...arr, val];

  // Check if there is an existing diary entry for today
  const todayEntry = useMemo(() => {
    const todayStr = new Date().toDateString();
    return entries.find((e) =>
    hasType(e, 'insight') &&
    e.childId === selectedChildId &&
    new Date(e.timestamp).toDateString() === todayStr
    ) || null;
  }, [entries, selectedChildId]);

  const [isEditing, setIsEditing] = useState(false);
  const [showForm, setShowForm] = useState(false);

  // When today's entry exists, show it; when none exists show blank form
  useEffect(() => {
    if (todayEntry && !isEditing) {
      setShowForm(false);
    } else if (!todayEntry) {
      setShowForm(true);
      setIsEditing(false);
    }
  }, [todayEntry, selectedChildId]);

  // Populate form when editing existing entry
  useEffect(() => {
    if (isEditing && todayEntry) {
      setTypes(todayEntry.types || ['insight']);
      setDayType(todayEntry.dayType || null);
      setEnergy(todayEntry.energy || null);
      setSunsMood(todayEntry.sunsMood || []);
      setFriction(todayEntry.friction || null);
      setEnvironment(todayEntry.environment || []);
      setFactors(todayEntry.factors || []);
      setSocialIntent(todayEntry.socialIntent || null);
      setMealsNote(todayEntry.mealsNote || '');
      setNoticedNote(todayEntry.noticedNote || '');
      setCheckinNote(todayEntry.checkinNote || '');
      setKindness(todayEntry.dailyKindness || false);
      setKindnessNote(todayEntry.dailyKindnessNote || '');
      setShowForm(true);
    }
  }, [isEditing, todayEntry]);

  const hasContent = mealsNote.trim() || noticedNote.trim() || checkinNote.trim() ||
  energy || friction || sunsMood.length > 0 || dayType ||
  factors.length > 0 || environment.length > 0 || socialIntent;

  const save = () => {
    if (!hasContent) return;

    const combinedNote = [
    mealsNote.trim() ? `Meals: ${mealsNote.trim()}` : '',
    noticedNote.trim() ? `Noticed: ${noticedNote.trim()}` : '',
    checkinNote.trim() ? `Check-in: ${checkinNote.trim()}` : ''].
    filter(Boolean).join('\n\n');

    if (isEditing && todayEntry) {
      updateEntry(todayEntry.id, {
        types,
        note: combinedNote,
        mealsNote: mealsNote.trim(),
        noticedNote: noticedNote.trim(),
        checkinNote: checkinNote.trim(),
        dayType, energy, friction, sunsMood, environment, factors, socialIntent,
        dailyKindness: kindness,
        dailyKindnessNote: kindnessNote
      });
      announce(savedMsg(types)); // edit branch; the new-entry branch announces via addEntry
    } else {
      addEntry({
        id: Math.random().toString(36).substr(2, 9),
        childId: selectedChildId,
        types,
        note: combinedNote,
        mealsNote: mealsNote.trim(),
        noticedNote: noticedNote.trim(),
        checkinNote: checkinNote.trim(),
        timestamp: new Date().toISOString(),
        dayType, energy, friction, sunsMood, environment, factors, socialIntent,
        dailyKindness: kindness,
        dailyKindnessNote: kindnessNote
      });
    }

    setIsEditing(false);
    setShowForm(false);
    setIsSaved(true);
    setTimeout(() => setIsSaved(false), 2000);
  };

  const socialIntents = [
  { id: 'High', desc: 'actively socializing' },
  { id: 'Observer', desc: 'watching, minimal join' },
  { id: 'Internal', desc: 'solo play focus' },
  { id: 'Quiet', desc: 'home / no stimulation' }];


  return (
    <div className="flex-1 flex flex-col bg-[#fdf9f0] overflow-hidden">

      <div className="screen-scroll flex-1 overflow-y-auto px-4 pb-8" style={{ opacity: "1" }}>

        {/* Screen header */}
        <ScreenHeader
          eyebrow="your quiet moment"
          control={<span className="sh-kicker-meta">{(() => {const d = new Date();return `${d.toLocaleDateString('en-US', { weekday: 'short' }).toLowerCase()} · ${d.toLocaleDateString('en-US', { month: 'short' }).toLowerCase()} ${d.getDate()}`;})()}</span>}
          title="the day, in your own words."
          subline="some days ask to be held a little longer. this is that space." />
        

        {/* Multi-child selector */}
        <ChildSwitcher childrenConfig={childrenConfig} activeId={selectedChildId} onSelect={setSelectedChildId} />

        {/* Existing entry view */}
        {todayEntry && !showForm &&
        <div className="mt-4 animate-in fade-in">
            <div className="flex justify-between items-center mb-3">
              <span className="text-[11px] uppercase tracking-widest font-semibold whitespace-nowrap" style={{ color: theme.parchment[600] }}>
                today's entry
              </span>
              {confirmingDelete ?
            <div className="flex items-center gap-[14px] animate-in fade-in">
                <button onClick={() => {deleteEntry(todayEntry.id);setConfirmingDelete(false);}} style={{ background: "none", border: "0", padding: "0", cursor: "pointer", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "#a8675a" }}>delete</button>
                <button onClick={() => setConfirmingDelete(false)} style={{ background: "none", border: "0", padding: "0", cursor: "pointer", fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "11px", fontWeight: 600, letterSpacing: "0.1em", textTransform: "uppercase", color: "#a99a8c" }}>keep</button>
              </div> :
            <div className="flex items-center gap-1">
                <button
                onClick={() => setIsEditing(true)}
                aria-label="edit today's entry"
                className="transition-colors active:scale-95"
                style={{ background: "none", border: 0, padding: "6px", cursor: "pointer", color: "#6e6258", display: "flex" }}>
                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M12 20h9" /><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" /></svg>
                </button>
                <button onClick={() => setConfirmingDelete(true)} aria-label="delete today's entry" className="transition-colors active:scale-95" style={{ background: "none", border: 0, padding: "6px", cursor: "pointer", color: "#6e6258", display: "flex" }}>
                  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6" /><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" /><path d="M10 11v6" /><path d="M14 11v6" /><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" /></svg>
                </button>
              </div>
            }
            </div>

            <div className="p-5 rounded-[16px] border space-y-4" style={{ borderColor: "rgba(212,196,183,0.55)", borderWidth: "0.5px", backgroundColor: "#ffffff", borderRadius: "6px" }}>
              {/* Context chips */}
              <div className="flex flex-wrap gap-1.5">
                {todayEntry.energy && <span className="ec-tag"><EnergyLabel level={todayEntry.energy} /></span>}
                {todayEntry.dayType && <span className="ec-tag">{todayEntry.dayType}</span>}
                {(todayEntry.sunsMood || []).map((m) => <span key={m} className="ec-tag"><span aria-hidden="true" style={{ borderRadius: 9999, background: theme.ochre[200], flexShrink: 0, width: "7px", height: "7px" }}></span>{m}</span>)}
                {todayEntry.friction && <span className="ec-tag">{todayEntry.friction}</span>}
                {hasType(todayEntry, 'bite') && <span className="ec-tag">one bite</span>}
                {hasType(todayEntry, 'enough') && <span className="ec-tag">enough</span>}
              </div>

              {todayEntry.mealsNote &&
            <div>
                  <div className="text-[9px] uppercase tracking-widest font-semibold mb-1" style={{ color: theme.parchment[500] }}>How did meals go</div>
                  <p className="text-[13px] leading-relaxed" style={{ color: theme.parchment[800] }}>{todayEntry.mealsNote}</p>
                </div>
            }
              {todayEntry.noticedNote &&
            <div>
                  <div className="text-[9px] uppercase tracking-widest font-semibold mb-1" style={{ color: theme.parchment[500] }}>What I Noticed</div>
                  <p className="text-[13px] leading-relaxed" style={{ color: theme.parchment[800] }}>{todayEntry.noticedNote}</p>
                </div>
            }
              {todayEntry.checkinNote &&
            <div>
                  <div className="text-[9px] uppercase tracking-widest font-semibold mb-1" style={{ color: theme.parchment[500] }}>Self Check-in</div>
                  <p className="text-[13px] leading-relaxed" style={{ color: theme.parchment[800] }}>{todayEntry.checkinNote}</p>
                </div>
            }
              {todayEntry.dailyKindness && todayEntry.dailyKindnessNote &&
            <div>
                  <div className="text-[9px] uppercase tracking-widest font-semibold mb-1" style={{ color: theme.parchment[500] }}>Daily Kindness</div>
                  <p className="text-[13px] leading-relaxed" style={{ color: theme.parchment[800], overflowWrap: 'anywhere' }}>{todayEntry.dailyKindnessNote}</p>
                </div>
            }
            </div>

            {/* Write another entry option */}
            <button
            onClick={() => {setIsEditing(false);setShowForm(true);setTypes(['insight']);setDayType(null);setEnergy(null);setSunsMood([]);setFriction(null);setEnvironment([]);setFactors([]);setSocialIntent(null);setMealsNote('');setNoticedNote('');setCheckinNote('');setKindness(false);setKindnessNote('');}}
            className="w-full mt-4 flex items-center justify-center gap-2 transition-colors active:scale-[0.98] hover:bg-[#f7f3ea]"
            style={{ padding: "12px", borderRadius: "8px", border: "0.5px solid #d4c4b7", background: "transparent", cursor: "pointer" }}>
            
              <span style={{ display: "flex" }} aria-hidden="true"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#82756a" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M12 5v14M5 12h14" /></svg></span>
              <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontSize: "10px", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase", color: "#50453b", whiteSpace: "nowrap" }}>write another entry</span>
            </button>
          </div>
        }

        {/* Saved confirmation */}
        {isSaved &&
        <div className="mt-4 p-4 rounded-[12px] bg-[#faf2e7] border border-[#f5e4cb] text-center animate-in fade-in">
            <p className="text-[13px] font-medium text-[#7d562d]">saved.</p>
          </div>
        }

        {/* Diary form */}
        {showForm && !isSaved &&
        <div className="mt-4 space-y-5 animate-in fade-in">

            {/* Core Context card */}
            <div>
              <p className="mb-3" style={{ color: '#6e6258' }}>
                <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontWeight: 600, fontSize: '11px', letterSpacing: '0.09em', textTransform: 'uppercase' }}>add some context</span>
                <span style={{ fontFamily: "'Hanken Grotesk', sans-serif", fontWeight: 400, fontSize: '11px', letterSpacing: '0.01em', opacity: 0.65 }}> — as much or as little as you want</span>
              </p>
              <div className="border-t mb-5" style={{ borderColor: '#e0d5c7' }}></div>
              <div className="space-y-5">

                {/* 1. Categorize */}
                <div>
                  <span className="text-[10px] uppercase tracking-widest font-semibold mb-2 block" style={{ color: theme.parchment[600] }}>
                    looking back on today <span className="font-normal normal-case tracking-normal text-[9px]" style={{ color: theme.parchment[600] }}>(optional)</span>
                  </span>
                  <div className="flex gap-2">
                    <button
                    onClick={() => setTypes((prev) => {
                      const base = ['insight'];
                      const hasBite = prev.includes('bite');
                      return hasBite ? base : [...base, 'bite'];
                    })}
                    className={`flex-1 py-2.5 rounded-xl border font-medium text-sm transition-all ${
                    types.includes('bite') ?
                    'bg-[#faf2e7] border-[#f0bd8b] text-[#623f18]' :
                    'bg-transparent border-[#d4c4b7] text-[#50453b]'}`
                    } style={{ borderRadius: "8px" }}>
                    
                      one bite
                    </button>
                    <button
                    onClick={() => setTypes((prev) => {
                      const base = ['insight'];
                      const hasEnough = prev.includes('enough');
                      return hasEnough ? base : [...base, 'enough'];
                    })}
                    className={`flex-1 py-2.5 rounded-xl border font-medium text-sm transition-all ${
                    types.includes('enough') ?
                    'bg-[#eef3f8] border-[#b3c9dd] text-[#34495a]' :
                    'bg-transparent border-[#d4c4b7] text-[#50453b]'}`
                    } style={{ borderRadius: "8px" }}>
                    
                      enough
                    </button>
                  </div>
                </div>

                {/* 2. Core Variables */}
                <div>
                  <span className="text-[10px] uppercase tracking-widest font-semibold mb-2 block" style={{ color: theme.parchment[600] }}>My Energy</span>
                  <div className="flex flex-wrap gap-2">
                    {['Max', 'Functional', 'Low'].map((e) => <button key={e} onClick={() => setEnergy(energy === e ? null : e)} className={`px-3 py-1.5 rounded-full border text-[11px] transition-colors ${energy === e ? '' : 'bg-transparent border-[#d4c4b7] text-[#50453b]'}`} style={energy === e ? SEL_CHIP.energy : undefined}><EnergyLabel level={e} /></button>)}
                  </div>
                </div>
                <div>
                  <span className="text-[10px] uppercase tracking-widest font-semibold mb-2 block" style={{ color: theme.parchment[600] }}>Day Type</span>
                  <div className="flex flex-wrap gap-2">
                    {Taxonomy.dayTypes.map((d) => <button key={d} onClick={() => setDayType(dayType === d ? null : d)} className={`px-3 py-1.5 rounded-full border text-[11px] transition-colors ${dayType === d ? '' : 'bg-transparent border-[#d4c4b7] text-[#50453b]'}`} style={dayType === d ? SEL_CHIP.dayType : undefined}>{d}</button>)}
                  </div>
                </div>
                <div>
                  <span className="text-[10px] uppercase tracking-widest font-semibold mb-2 block" style={{ color: theme.parchment[600] }}>Child Mood</span>
                  <ChipClusters all={Taxonomy.moods} groups={MOOD_GROUPS} intraGap="8px" interGap="10px" renderChip={(m) => <button key={m} onClick={() => setSunsMood(toggleArr(sunsMood, m))} className={`px-3 py-1.5 rounded-full border text-[11px] transition-colors ${sunsMood.includes(m) ? '' : 'bg-transparent border-[#d4c4b7] text-[#50453b]'}`} style={sunsMood.includes(m) ? SEL_CHIP.mood : undefined}>{m}</button>} />
                </div>
                <div>
                  <span className="text-[10px] uppercase tracking-widest font-semibold mb-2 block" style={{ color: theme.parchment[600] }}>How Did It Feel</span>
                  <div className="flex flex-wrap gap-2">
                    {['Heavy', 'Mixed', 'Light'].map((f) => <button key={f} onClick={() => setFriction(friction === f ? null : f)} className={`px-3 py-1.5 rounded-full border text-[11px] transition-colors ${friction === f ? '' : 'bg-transparent border-[#d4c4b7] text-[#50453b]'}`} style={friction === f ? SEL_CHIP.feel : undefined}>{f}</button>)}
                  </div>
                </div>

                {/* 3. Expander — quiet text affordance */}
                <div className="flex justify-center" style={{ padding: "10px 0px 5px" }}>
                  <button
                  onClick={() => setShowContext(!showContext)}
                  className="flex items-center gap-1"
                  style={{ background: 'none', border: 0, padding: 0, cursor: 'pointer', fontFamily: "'Hanken Grotesk', sans-serif", fontWeight: 500, letterSpacing: '0.02em', color: '#6e6258', fontSize: "11px" }}>
                    <span aria-hidden="true" style={{ fontSize: '13px', lineHeight: 1, marginTop: '-1px' }}>{showContext ? '−' : '+'}</span>
                    other factors
                  </button>
                </div>

                {/* 4. Expanded Content */}
                {showContext &&
              <div className="space-y-5 pt-2 animate-in fade-in">
                    <div>
                      <span className="text-[10px] uppercase tracking-widest font-semibold mb-2 block" style={{ color: theme.parchment[600] }}>Environment</span>
                      <div className="flex flex-wrap gap-2">
                        {Taxonomy.environments.map((env) => <button key={env} onClick={() => setEnvironment(toggleArr(environment, env))} className={`px-3 py-1.5 rounded-full border text-[11px] transition-colors ${environment.includes(env) ? '' : 'bg-transparent border-[#d4c4b7] text-[#50453b]'}`} style={environment.includes(env) ? SEL_CHIP.factor : undefined}>{env}</button>)}
                      </div>
                    </div>
                    <div>
                      <span className="text-[10px] uppercase tracking-widest font-semibold mb-2 block" style={{ color: theme.parchment[600] }}>Factors</span>
                      <div className="flex flex-wrap gap-2">
                        {Taxonomy.factors.map((f) => <button key={f} onClick={() => setFactors(toggleArr(factors, f))} className={`px-3 py-1.5 rounded-full border text-[11px] transition-colors ${factors.includes(f) ? '' : 'bg-transparent border-[#d4c4b7] text-[#50453b]'}`} style={factors.includes(f) ? SEL_CHIP.factor : undefined}>{f}</button>)}
                      </div>
                    </div>
                    <div>
                      <span className="text-[10px] uppercase tracking-widest font-semibold mb-2 block" style={{ color: theme.parchment[600] }}>Social Intent</span>
                      <div className="grid grid-cols-2 gap-2">
                        {socialIntents.map((s) =>
                    <button key={s.id} onClick={() => setSocialIntent(socialIntent === s.id ? null : s.id)} className={`p-2 rounded-md border text-left transition-colors flex flex-col gap-0.5 ${socialIntent === s.id ? '' : 'bg-transparent border-[#d4c4b7]'}`} style={socialIntent === s.id ? SEL_SOCIAL_CARD : undefined}>
                            <span className={`text-[11px] ${socialIntent === s.id ? 'text-[#623f18] font-semibold' : 'text-[#50453b]'}`}>{s.id}</span>
                            <span className="text-[11px] leading-tight" style={{ color: socialIntent === s.id ? '#7d562d' : theme.parchment[600], opacity: socialIntent === s.id ? 1 : 0.7 }}>{s.desc}</span>
                          </button>
                    )}
                      </div>
                    </div>
                  </div>
              }

                <div className="border-t" style={{ borderColor: '#d4c4b7', borderTopWidth: '0.5px', margin: "10px 0px 0px" }}></div>
              </div>
            </div>
            

            {/* Diary sections — notebook ruled lines */}
            <div className="space-y-6" style={{ paddingTop: '22px', padding: "25px 0px 0px" }}>
              <div>
                <label className="block text-[10px] uppercase tracking-widest font-semibold mb-2" style={{ color: theme.parchment[600] }}>How did meals go</label>
                <textarea
                value={mealsNote}
                maxLength={1500}
                onChange={(e) => setMealsNote(e.target.value)}
                aria-label="how did meals go"
                className="diary-rule w-full outline-none resize-none"
                rows={1}
                ref={(el) => {if (el) {el.style.height = 'auto';el.style.height = el.scrollHeight + 'px';}}}
                onInput={(e) => {e.target.style.height = 'auto';e.target.style.height = e.target.scrollHeight + 'px';}}
                style={{ color: '#1c1c17', fontFamily: "'Source Serif 4', Georgia, serif", fontSize: '13px', lineHeight: 1.7, paddingBottom: '8px', minHeight: '30px', overflow: 'hidden' }}
                placeholder="the good, the bad, the crackers..." />
              
              </div>
              <div>
                <label className="block text-[10px] uppercase tracking-widest font-semibold mb-2" style={{ color: theme.parchment[600] }}>What I Noticed</label>
                <textarea
                value={noticedNote}
                maxLength={1500}
                onChange={(e) => setNoticedNote(e.target.value)}
                aria-label="what I noticed"
                className="diary-rule w-full outline-none resize-none"
                rows={1}
                ref={(el) => {if (el) {el.style.height = 'auto';el.style.height = el.scrollHeight + 'px';}}}
                onInput={(e) => {e.target.style.height = 'auto';e.target.style.height = e.target.scrollHeight + 'px';}}
                style={{ color: '#1c1c17', fontFamily: "'Source Serif 4', Georgia, serif", fontSize: '13px', lineHeight: 1.7, paddingBottom: '8px', minHeight: '30px', overflow: 'hidden' }}
                placeholder="anything worth remembering — a moment, a behaviour, something new..." />
              
              </div>
              <div>
                <label className="block text-[10px] uppercase tracking-widest font-semibold mb-2" style={{ color: theme.parchment[600] }}>Self Check-in</label>
                <textarea
                value={checkinNote}
                maxLength={1500}
                onChange={(e) => setCheckinNote(e.target.value)}
                aria-label="self check-in"
                className="diary-rule w-full outline-none resize-none"
                rows={1}
                ref={(el) => {if (el) {el.style.height = 'auto';el.style.height = el.scrollHeight + 'px';}}}
                onInput={(e) => {e.target.style.height = 'auto';e.target.style.height = e.target.scrollHeight + 'px';}}
                style={{ color: '#1c1c17', fontFamily: "'Source Serif 4', Georgia, serif", fontSize: '13px', lineHeight: 1.7, paddingBottom: '8px', minHeight: '30px', overflow: 'hidden' }}
                placeholder="how are you today? one line is enough..." />
              
              </div>
            </div>

            {/* Self Care */}
            <div style={{ margin: "20px 0px 1px" }}>
              <label className="block text-[10px] uppercase tracking-widest font-semibold mb-3" style={{ color: theme.parchment[600] }}>Self Care</label>
              <div>
                <button type="button" onClick={() => setKindness(!kindness)} className="flex items-center gap-3" style={{ background: "none", border: 0, padding: 0, margin: 0, cursor: "pointer", width: "100%", textAlign: "left" }}>
                  <span aria-hidden="true" style={{ width: "18px", height: "18px", flexShrink: 0, borderRadius: "5px", border: "1px solid #b88e8d", background: kindness ? "rgba(184,142,141,0.25)" : "transparent", display: "flex", alignItems: "center", justifyContent: "center", transition: "all 160ms cubic-bezier(0.32,0.08,0.24,1)" }}>
                    {kindness && <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#96615f" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M5 12l5 5L20 6" /></svg>}
                  </span>
                  <span style={{ fontFamily: "'Source Serif 4', Georgia, serif", fontSize: "13px", color: "#1c1c17" }}>log a daily kindness</span>
                </button>
                {kindness &&
              <textarea
                value={kindnessNote}
                maxLength={500}
                onChange={(e) => setKindnessNote(e.target.value)}
                aria-label="a kindness you showed yourself today"
                className="diary-rule w-full outline-none resize-none mt-3"
                rows={1}
                ref={(el) => {if (el) {el.style.height = 'auto';el.style.height = el.scrollHeight + 'px';}}}
                onInput={(e) => {e.target.style.height = 'auto';e.target.style.height = e.target.scrollHeight + 'px';}}
                placeholder="I showed myself kindness today by..."
                style={{ color: '#1c1c17', fontFamily: "'Source Serif 4', Georgia, serif", fontSize: '13px', lineHeight: 1.7, paddingBottom: '8px', minHeight: '30px', overflow: 'hidden' }} />

              }
              </div>
            </div>

            {/* Save button */}
            <div className="pt-6 pb-16">
              <button
              disabled={!hasContent}
              onClick={save}
              className="w-full transition-all active:scale-[0.98] disabled:active:scale-100"
              style={{
                padding: '14px',
                borderRadius: '10px',
                fontFamily: "'Hanken Grotesk', sans-serif",
                fontSize: '11px',
                fontWeight: 600,
                letterSpacing: '0.04em',
                cursor: hasContent ? 'pointer' : 'default',
                backgroundColor: !hasContent ?
                'rgba(130,117,106,0.25)' :
                types.includes('bite') && !types.includes('enough') ?
                '#7d562d' :
                types.includes('enough') && !types.includes('bite') ?
                '#4c6172' :
                'rgba(130,117,106,0.55)',
                color: hasContent ? '#fff' : 'rgba(255,255,255,0.7)',
                transition: 'background-color 0.2s ease, color 0.2s ease'
              }}>
              
                {isEditing ? 'save changes' : 'save entry'}
              </button>
            </div>

          </div>
        }
      </div>
    </div>);

}

function App() {
  const [hasOnboarded, setHasOnboarded] = useState(false);
  const [parentName, setParentName] = useState('');

  // Central state for children configs
  const [childrenConfig, setChildrenConfig] = useState([]);
  const [activeChildId, setActiveChildId] = useState(null);

  // Modals state
  const [editingChild, setEditingChild] = useState(null);
  const [isSettingsOpen, setIsSettingsOpen] = useState(false);

  const [activeTab, setActiveTab] = useState('Home');
  const [entries, setEntries] = useState([]);

  // Load persisted data on mount
  useEffect(() => {
    try {
      const saved = localStorage.getItem('onebite_data');
      if (saved) {
        const data = JSON.parse(saved);
        setParentName(data.parentName || '');
        setChildrenConfig((data.childrenConfig || []).map((c) => {const { emoji, ...rest } = c;return { ...rest, tint: c.tint || tintFor(c) };}));
        setHasOnboarded(data.hasOnboarded || false);
        setActiveChildId(data.activeChildId || null);
        setEntries(data.entries || []);
      }
    } catch (e) {
      console.error('Failed to load saved data', e);
    }
  }, []);

  // Persist all state whenever it changes
  useEffect(() => {
    if (!hasOnboarded && entries.length === 0 && childrenConfig.length === 0) return;
    try {
      localStorage.setItem('onebite_data', JSON.stringify({
        parentName,
        childrenConfig,
        hasOnboarded,
        activeChildId,
        entries
      }));
    } catch (e) {
      console.error('Failed to save data', e);
      announce("Couldn't save. Please try again.");
    }
  }, [parentName, childrenConfig, hasOnboarded, activeChildId, entries]);

  const addEntry = (entry) => {
    setEntries((prev) => [entry, ...prev]);
    announce(savedMsg(entry && entry.types));
  };
  const updateEntry = (id, patch) => setEntries((prev) => prev.map((e) => e.id === id ? { ...e, ...patch } : e));
  const deleteEntry = (id) => {
    // Classify before removal so the announcement can name what left: a pure
    // kindness note (no entry note, no bite/enough type) vs a regular entry.
    const target = entries.find((e) => e.id === id);
    const isKindness = !!target && target.dailyKindness && !target.note && !(target.types && target.types.length);
    setEntries((prev) => prev.filter((e) => e.id !== id));
    announce(isKindness ? 'Kindness note deleted.' : 'Entry deleted.');
  };

  const saveChild = (childData) => {
    setChildrenConfig((prev) => {
      if (prev.find((c) => c.id === childData.id)) {
        return prev.map((c) => c.id === childData.id ? childData : c);
      }
      setActiveChildId(childData.id);
      return [...prev, childData];
    });
  };

  const deleteChild = (id) => {
    setChildrenConfig((prev) => prev.filter((c) => c.id !== id));
    setEntries((prev) => prev.filter((e) => e.childId !== id));
    setActiveChildId((prev) => {
      if (prev !== id) return prev;
      const remaining = childrenConfig.filter((c) => c.id !== id);
      return remaining.length ? remaining[0].id : null;
    });
  };

  const handleSetParentName = (name) => setParentName(name);

  const handleClearData = () => {
    localStorage.removeItem('onebite_data');
    localStorage.removeItem('onebite_reflection');
    setEntries([]);
    setChildrenConfig([]);
    setParentName('');
    setHasOnboarded(false);
    setActiveTab('Home');
  };

  const exportEntries = () => {
    const headers = ['id', 'timestamp', 'childId', 'note', 'energy', 'friction', 'types'];
    const rows = entries.map((e) => [e.id, e.timestamp, e.childId, `"${(e.note || '').replace(/"/g, '""')}"`, e.energy, e.friction, (e.types || []).join('|')].join(','));
    const csv = [headers.join(','), ...rows].join('\n');
    const blob = new Blob([csv], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'one-bite-enough-export.csv';
    a.click();
  };

  const activeChildObj = childrenConfig.find((c) => c.id === activeChildId) || childrenConfig[0] || {};

  if (!hasOnboarded) {
    return (
      <div className="max-w-md mx-auto h-screen relative overflow-hidden font-sans bg-[#f7f3ea] shadow-2xl sm:border-x sm:border-gray-200">
         <OnboardingScreen onComplete={(data) => {
          const firstChild = { id: 1, name: capName((data.childName || 'Sun').trim()), dob: data.childDob, tint: data.childTint || CHILD_TINTS[0] };
          setParentName(data.parentName || 'Friend');
          setChildrenConfig([firstChild]);
          setActiveChildId(firstChild.id);
          setEntries([]);
          setHasOnboarded(true);
        }} />
      </div>);

  }

  return (
    <div className="max-w-md mx-auto h-screen bg-[#fdf9f0] flex flex-col shadow-2xl relative sm:border-x sm:border-gray-200 overflow-hidden font-sans">
      {/* 1. ADD THIS STYLE BLOCK HERE */}
      <style dangerouslySetInnerHTML={{ __html: `
        .no-scrollbar::-webkit-scrollbar {
          display: none;
        }
        .no-scrollbar {
          -ms-overflow-style: none;
          scrollbar-width: none;
        }
      ` }} />

      {/* Polite status region — visually hidden but present in the DOM (clipped,
          not display:none, so screen readers still announce updates). One
          instance for the whole app; announce() writes here. */}
      <div
        id="ob-live-region"
        aria-live="polite"
        aria-atomic="true"
        style={{ position: 'absolute', width: '1px', height: '1px', padding: 0, margin: '-1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', clipPath: 'inset(50%)', whiteSpace: 'nowrap', border: 0 }} />

      {activeTab === 'Home' && <HomeScreen entries={entries} addEntry={addEntry} updateEntry={updateEntry} deleteEntry={deleteEntry} activeChild={activeChildObj} childrenConfig={childrenConfig} setActiveTab={setActiveTab} setActiveChildId={setActiveChildId} />}
      {activeTab === 'Pattern' && <PatternScreen entries={entries} activeChildId={activeChildId} setActiveChildId={setActiveChildId} childrenConfig={childrenConfig} navigateHome={() => setActiveTab('Home')} />}
      {activeTab === 'Diary' && <DiaryScreen entries={entries} addEntry={addEntry} updateEntry={updateEntry} deleteEntry={deleteEntry} childrenConfig={childrenConfig} activeChildId={activeChildId} />}
      {activeTab === 'Child' && <ChildScreen entries={entries} activeChildId={activeChildId} setActiveChildId={setActiveChildId} childrenConfig={childrenConfig} openAddModal={() => setEditingChild({})} openEditModal={(c) => setEditingChild(c)} deleteEntry={deleteEntry} updateEntry={updateEntry} navigateHome={() => setActiveTab('Home')} />}
      {activeTab === 'Me' && <MeScreen setActiveTab={setActiveTab} entries={entries} childrenConfig={childrenConfig} openSettings={() => setIsSettingsOpen(true)} addEntry={addEntry} updateEntry={updateEntry} deleteEntry={deleteEntry} />}

      {editingChild !== null && <ChildFormModal initialChild={Object.keys(editingChild).length ? editingChild : null} close={() => setEditingChild(null)} saveChild={saveChild} />}
      {isSettingsOpen && <SettingsModal close={() => setIsSettingsOpen(false)} parentName={parentName} setParentName={handleSetParentName} childrenConfig={childrenConfig} openAddChild={() => setEditingChild({})} openEditChild={(c) => setEditingChild(c)} deleteChild={deleteChild} clearData={handleClearData} exportEntries={exportEntries} />}

      <nav className="ob-nav">
        {[
        { key: 'Home', label: 'home', icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12 12 3l9 9" /><path d="M5 10v10h14V10" /></svg> },
        { key: 'Pattern', label: 'pattern', icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 20V10" /><path d="M9 20V4" /><path d="M15 20v-8" /><path d="M21 20v-6" /></svg> },
        { key: 'Diary', label: 'diary', icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M4 19V5a2 2 0 0 1 2-2h12v18l-3-2-3 2-3-2-3 2Z" /></svg> },
        { key: 'Child', label: 'child', icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="8" r="4" /><path d="M4 21a8 8 0 0 1 16 0" /></svg> },
        { key: 'Me', label: 'me', icon: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="5" r="2.8" /><path d="M5 21v-3a7 7 0 0 1 14 0v3" /><path d="M12 16.5c-1-1.3-3-.8-3 .8 0 1.5 3 3 3 3s3-1.5 3-3c0-1.6-2-2.1-3-.8z" /></svg> }].
        map(({ key, label, icon }) =>
        <button
          key={key}
          onClick={() => setActiveTab(key)}
          className={'ob-tab' + (activeTab === key ? ' ob-tab-active' : '')}
          aria-current={activeTab === key ? 'page' : undefined}>
          
            {icon}
            <span>{label}</span>
          </button>
        )}
      </nav>
    </div>);

}

// Seed demo data so the app boots straight into the home feed.
// (Onboarding is still reachable by clearing data from Settings.)
// Skipped in the production build (window.OBE_PRODUCTION set by index.html) so
// real testers start at onboarding with their own name/child instead of the
// "Em / Sun" demo seed.
if (typeof window !== 'undefined' && !window.OBE_PRODUCTION && !localStorage.getItem('onebite_data')) {
  try {
    localStorage.setItem('onebite_data', JSON.stringify({
      parentName: 'Em',
      childrenConfig: [{ id: 1, name: 'Sun', dob: '2022-04-15', emoji: '☀️' }],
      hasOnboarded: true,
      activeChildId: 1,
      entries: getInitialEntries('Sun')
    }));
  } catch (e) {/* private mode, fall through to onboarding */}
}

window.App = App;
window.getInitialEntries = getInitialEntries;