// Solari · Landing · Animated Plan loop (self-contained)
// Empty week → tiles populate → tap Wed → audible sheet → swap → restart.
// No external React component dependencies. Reads CSS vars from the landing page.

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

// ─── Animation timeline ────────────────────────────────────────
const PLAN_SEQUENCE = [
  { t: 0,     state: 'empty',       caption: 'A blank week.' },
  { t: 1100,  state: 'fill-1',      caption: 'Solari drafts a week\u2026' },
  { t: 1450,  state: 'fill-2' },
  { t: 1800,  state: 'fill-3' },
  { t: 2150,  state: 'fill-4' },
  { t: 2500,  state: 'fill-5' },
  { t: 2850,  state: 'fill-6' },
  { t: 3300,  state: 'full',        caption: 'Done. Seven nights, planned.' },
  { t: 5000,  state: 'tap-wed',     caption: 'Not feeling salmon Wednesday?' },
  { t: 5500,  state: 'sheet-open',  caption: 'Tap the night. Swap it.' },
  { t: 7300,  state: 'pick-pasta',  caption: 'Pick the pantry meal.' },
  { t: 8200,  state: 'sheet-close', caption: 'Wednesday updated.' },
  { t: 9200,  state: 'rest',        caption: 'Wednesday updated.' },
  { t: 10800, state: 'drag-press',  caption: 'Hold a meal to move it…' },
  { t: 11400, state: 'drag-lift',   caption: 'Hold a meal to move it…' },
  { t: 12000, state: 'drag-move',   caption: 'Drop it on another day to swap.' },
  { t: 13400, state: 'drag-drop',   caption: 'Drop it on another day to swap.' },
  { t: 13900, state: 'drag-done',   caption: 'Wednesday ↔ Thursday. Done.' },
];
const LOOP_DURATION = 16500;

const useChoreography = (sequence, duration) => {
  const [t, setT] = useState(0);
  const startRef = useRef(performance.now());
  useEffect(() => {
    let raf;
    const tick = (now) => {
      const elapsed = (now - startRef.current) % duration;
      setT(elapsed);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [duration]);
  let current = sequence[0];
  for (const ev of sequence) {
    if (ev.t <= t) current = ev;
    else break;
  }
  return { t, state: current.state, caption: current.caption || '' };
};

// ─── Color tokens (read from landing page CSS, with fallbacks) ──
const C = {
  ink:      'var(--ink-900, #1f1f1c)',
  ink2:     'var(--ink-700, #3b3b35)',
  ink3:     'var(--ink-500, #76736a)',
  ink4:     'var(--ink-400, #a39f93)',
  paper:    'var(--paper-50, #faf6e9)',
  paper2:   'var(--paper-100, #f3ecd6)',
  paper3:   'var(--paper-200, #e8dfcc)',
  paper4:   'var(--paper-300, #d8cfb8)',
  sage:     'var(--sage-700, #4f7a3a)',
  sageWash: 'var(--sage-100, #e8efd9)',
  amberD:   'var(--amber-700, #c77a1f)',
  amberS:   'var(--amber-100, #f5e3c2)',
  clay:     'var(--terra-600, #b8542e)',
  moment:   'var(--terra-700, #9a3f24)',
};

const TILE_TYPES = {
  cook:   { word: 'Cook',     bg: C.sage,    fg: '#FAF6E9', tone: 'dark' },
  batch:  { word: 'Batch',    bg: C.amberD,  fg: '#FAF6E9', tone: 'dark' },
  encore: { word: 'Repeat',   bg: C.amberS,  fg: C.amberD,  tone: 'light' },
  order:  { word: 'Order In', bg: C.clay,    fg: '#FAF6E9', tone: 'dark' },
  out:    { word: 'Going Out',bg: C.ink,     fg: '#FAF6E9', tone: 'dark' },
  easy:   { word: 'Easy',     bg: '#dde7c8', fg: C.sage,    tone: 'light' },
  moment: { word: 'Moment',   bg: C.moment,  fg: '#FAF6E9', tone: 'dark' },
};

const TypeTile = ({ kind = 'cook', size = 56 }) => {
  const s = TILE_TYPES[kind] || TILE_TYPES.cook;
  const isTwoWord = s.word.includes(' ');
  const radius = Math.max(6, Math.round(size * 0.22));
  // Scale font to tile size; reference is size=56 → 15px (1-word) or 12px (2-word)
  const baseFs = isTwoWord ? 12 : (s.word.length >= 6 ? 13 : 15);
  const fs = Math.max(8, Math.round(baseFs * (size / 56)));
  return (
    <div style={{
      width: size, height: size, flexShrink: 0,
      borderRadius: radius,
      background: s.bg,
      boxShadow: s.tone === 'dark'
        ? 'inset 0 0 0 1px rgba(255,255,255,.18), 0 1px 2px rgba(31,31,28,.06)'
        : 'inset 0 0 0 1px rgba(31,31,28,.10), 0 1px 2px rgba(31,31,28,.04)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      position: 'relative', overflow: 'hidden',
    }}>
      <div style={{
        position: 'absolute', inset: 0,
        background: s.tone === 'dark'
          ? 'radial-gradient(circle at 30% 20%, rgba(255,255,255,.10), transparent 60%)'
          : 'radial-gradient(circle at 30% 20%, rgba(255,255,255,.45), transparent 60%)',
        pointerEvents: 'none',
      }} />
      <div style={{
        fontFamily: 'var(--font-display, Georgia, serif)',
        fontSize: fs,
        lineHeight: 1.0,
        color: s.fg,
        letterSpacing: '-0.015em',
        textAlign: 'center', padding: '0 2px', position: 'relative',
      }}>
        {isTwoWord ? s.word.split(' ').map((w, i) => <div key={i}>{w}</div>) : s.word}
      </div>
    </div>
  );
};

// ─── The week being drafted ────────────────────────────────────
const WEEK = [
  { day: 'SUN', date: '04', kind: 'batch',  name: 'Sunday roast chicken',     time: '40 min', sub: 'feeds Mon too' },
  { day: 'MON', date: '05', kind: 'encore', name: 'Roast wraps',              time: '10 min', sub: 'from Sunday' },
  { day: 'TUE', date: '06', kind: 'cook',   name: 'Cacio e pepe',             time: '25 min', sub: 'pasta night' },
  { day: 'WED', date: '07', kind: 'cook',   name: 'Sheet-pan salmon',         time: '30 min', sub: 'sage & lemon' },
  { day: 'THU', date: '08', kind: 'easy',   name: 'Stir-fry & rice',          time: '20 min', sub: 'pantry meal' },
  { day: 'FRI', date: '09', kind: 'order',  name: 'Pizza night',              time: '35 min', sub: 'the usual' },
  { day: 'SAT', date: '10', kind: 'moment', name: 'Mom\u2019s birthday dinner', time: '7pm',  sub: 'candles + cake' },
];

const WED_SWAP = { kind: 'easy', name: 'Pasta with pantry pesto', time: '15 min', sub: 'fridge raid' };

// ─── Audible sheet options ─────────────────────────────────────
const AUDIBLE_OPTIONS = [
  { ic: 'simpler', t: 'Cook something else', s: 'browse a different recipe' },
  { ic: 'batch',   t: 'Eat the batch',       s: 'Sunday\u2019s chicken' },
  { ic: 'pantry',  t: 'Pantry / pasta night', s: 'pesto from the freezer' },
  { ic: 'order',   t: 'Order in',            s: 'mark dinner handled' },
  { ic: 'eatout',  t: 'Eat out',             s: 'going out tonight' },
];

const AudIcon = ({ kind, size = 22 }) => {
  const p = { width: size, height: size, viewBox: '0 0 32 32', fill: 'none', stroke: 'currentColor', strokeWidth: 1.6, strokeLinecap: 'round', strokeLinejoin: 'round' };
  switch (kind) {
    case 'simpler': return <svg {...p}><path d="M5 17h16a3 3 0 0 1 0 6h-13a3 3 0 0 1-3-3z"/><path d="M21 20h6"/><path d="M11 12q1-2 0-4 q-1-2 0-4"/><path d="M16 12q1-2 0-4 q-1-2 0-4"/></svg>;
    case 'batch':   return <svg {...p}><rect x="6" y="8" width="20" height="5" rx="1.5"/><rect x="7" y="13" width="18" height="5" rx="1"/><rect x="8" y="18" width="16" height="6" rx="1"/><path d="M14 5v3 M18 5v3"/></svg>;
    case 'pantry':  return <svg {...p}><rect x="7" y="5" width="18" height="22" rx="2"/><path d="M7 14h18"/><circle cx="11" cy="10" r="1" fill="currentColor"/><circle cx="11" cy="19" r="1" fill="currentColor"/></svg>;
    case 'order':   return <svg {...p}><path d="M8 13l2 13h12l2-13z"/><path d="M7 13h18"/><path d="M11 13l2-5h6l2 5"/></svg>;
    case 'eatout':  return <svg {...p}><path d="M11 5v10 a2 2 0 0 0 2 2 v10"/><path d="M11 5v6 M14 5v6"/><path d="M21 5c0 4-2 6-2 9v13"/><path d="M19 14a2 2 0 0 0 4 0 v-9 z"/></svg>;
    default: return null;
  }
};

// ─── Animated week row ─────────────────────────────────────────
const VISIBLE_AT = {
  empty: 0, 'fill-1': 1, 'fill-2': 2, 'fill-3': 3, 'fill-4': 4,
  'fill-5': 5, 'fill-6': 6, full: 7, 'tap-wed': 7, 'sheet-open': 7,
  'pick-pasta': 7, 'sheet-close': 7, rest: 7,
  'drag-press': 7, 'drag-lift': 7, 'drag-move': 7, 'drag-drop': 7, 'drag-done': 7,
};

const PlanWeekRow = ({ idx, state, isSwapped, isTapped, justSwapped, dragPhase, isDragSource, isDropTarget, dragSwapped, dragJustDone, isLast }) => {
  // Resolve which meal renders in this row based on swap/drag state
  let meal = WEEK[idx];
  if (idx === 3 && isSwapped && !dragSwapped) meal = { ...WEEK[3], ...WED_SWAP };
  else if (idx === 3 && dragSwapped) meal = WEEK[4]; // Wed shows old Thu (stir-fry)
  else if (idx === 4 && dragSwapped) meal = { ...WEEK[3], ...WED_SWAP }; // Thu shows pasta
  else if (idx === 3 && isSwapped) meal = { ...WEEK[3], ...WED_SWAP };

  const visibleCount = VISIBLE_AT[state] ?? 0;
  const isVisible = idx < visibleCount;
  const isFresh = isVisible && idx === visibleCount - 1 && state.startsWith('fill-');

  // Drop-target highlight (Thu when dragging over)
  const dropHighlight = isDropTarget;
  // Source row goes "ghost" while being dragged
  const isGhost = isDragSource && (dragPhase === 'lift' || dragPhase === 'move');

  let bg = 'transparent';
  let ring = 'none';
  if (dragJustDone && (idx === 3 || idx === 4)) { bg = '#f5e3c2'; ring = `inset 0 0 0 1.5px ${C.amberD}`; }
  else if (dropHighlight) { bg = C.sageWash; ring = `inset 0 0 0 1.5px ${C.sage}`; }
  else if (justSwapped) { bg = '#f5e3c2'; ring = `inset 0 0 0 1.5px ${C.amberD}`; }
  else if (isTapped) { bg = C.sageWash; }

  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 10,
      padding: '10px 8px',
      borderTop: idx === 0 ? 0 : `1px solid ${C.paper3}`,
      borderRadius: (justSwapped || isTapped || dropHighlight || dragJustDone) ? 10 : 0,
      position: 'relative',
      transition: 'background 360ms ease, box-shadow 360ms ease, opacity 220ms ease',
      background: bg,
      boxShadow: ring,
      opacity: isGhost ? 0.25 : 1,
    }}>
      {/* Day / date */}
      <div style={{ width: 28, textAlign: 'center', flexShrink: 0 }}>
        <div style={{
          fontFamily: 'var(--font-sans, system-ui)', fontSize: 9, fontWeight: 700,
          letterSpacing: '0.12em', color: C.ink3,
        }}>{WEEK[idx].day}</div>
        <div style={{
          fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 16, lineHeight: 1, color: C.ink,
          marginTop: 2,
        }}>{WEEK[idx].date}</div>
      </div>

      {/* Tile slot — dashed placeholder until visible */}
      <div style={{ width: 42, height: 42, position: 'relative', flexShrink: 0 }}>
        <div style={{
          position: 'absolute', inset: 0, borderRadius: 9,
          border: `1.5px dashed ${C.paper4}`,
          opacity: isVisible ? 0 : 1,
          transition: 'opacity 240ms ease',
        }} />
        <div style={{
          position: 'absolute', inset: 0,
          opacity: isVisible ? 1 : 0,
          transform: isVisible ? (isFresh ? 'scale(1.06)' : 'scale(1)') : 'scale(0.85)',
          transition: 'opacity 320ms ease, transform 380ms cubic-bezier(.34,1.5,.64,1)',
        }}>
          <TypeTile kind={meal.kind} size={42} />
        </div>
      </div>

      {/* Name / sub */}
      <div style={{
        minWidth: 0, flex: 1,
        opacity: isVisible ? 1 : 0,
        transform: isVisible ? 'translateX(0)' : 'translateX(-6px)',
        transition: 'opacity 280ms ease 60ms, transform 280ms ease 60ms',
      }}>
        <div style={{
          fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 13.5, fontWeight: 600,
          color: C.ink, lineHeight: 1.2,
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
        }}>{meal.name || '—'}</div>
        <div style={{
          fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 11, fontStyle: 'italic',
          color: C.ink3, marginTop: 2,
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
        }}>
          {meal.time} · {meal.sub}
        </div>
      </div>
    </div>
  );
};

// ─── Audible sheet ─────────────────────────────────────────────
const AudibleSheet = ({ open, pickedIdx }) => (
  <div style={{
    position: 'absolute', left: 0, right: 0, bottom: 0,
    background: C.paper,
    borderTop: `1px solid ${C.paper3}`,
    borderTopLeftRadius: 22, borderTopRightRadius: 22,
    padding: '14px 18px 22px',
    boxShadow: '0 -10px 32px rgba(31,31,28,0.10)',
    transform: open ? 'translateY(0)' : 'translateY(102%)',
    transition: 'transform 520ms cubic-bezier(.32,.72,0,1)',
    zIndex: 5,
  }}>
    <div style={{
      width: 38, height: 4, borderRadius: 2,
      background: C.paper4, margin: '0 auto 12px',
    }} />
    <div style={{
      fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 19, lineHeight: 1.15,
      color: C.ink, letterSpacing: '-0.015em', marginBottom: 4,
    }}>
      Wednesday. Call an <em style={{ color: C.sage, fontStyle: 'italic' }}>audible</em>?
    </div>
    <div style={{
      fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 12, fontStyle: 'italic',
      color: C.ink3, marginBottom: 12,
    }}>swap the salmon for…</div>

    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      {AUDIBLE_OPTIONS.map((opt, i) => {
        const isPicked = pickedIdx === i;
        return (
          <div key={opt.t} style={{
            display: 'grid', gridTemplateColumns: '34px 1fr 14px',
            gap: 10, alignItems: 'center',
            padding: '8px 10px', borderRadius: 10,
            background: isPicked ? C.sageWash : 'transparent',
            border: `1px solid ${isPicked ? C.sage : 'transparent'}`,
            transition: 'background 240ms ease, border-color 240ms ease',
          }}>
            <div style={{
              width: 34, height: 34, borderRadius: 9,
              background: isPicked ? C.sage : C.paper2,
              color: isPicked ? '#FAF6E9' : C.ink2,
              display: 'flex', alignItems: 'center', justifyContent: 'center',
              transition: 'background 240ms ease, color 240ms ease',
            }}>
              <AudIcon kind={opt.ic} size={18} />
            </div>
            <div>
              <div style={{
                fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 13.5, fontWeight: 600,
                color: isPicked ? C.sage : C.ink, lineHeight: 1.15,
              }}>{opt.t}</div>
              <div style={{
                fontFamily: 'var(--font-sans, system-ui)', fontSize: 10.5, color: C.ink3,
                marginTop: 2, fontStyle: 'italic',
              }}>{opt.s}</div>
            </div>
            {isPicked && (
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke={C.sage} strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
                <polyline points="20 6 9 17 4 12"/>
              </svg>
            )}
          </div>
        );
      })}
    </div>
  </div>
);

// ─── Tap indicator (no arrow cursor — just a clean ripple at the touch point) ──
const Cursor = ({ x, y, visible, tapping }) => (
  <div style={{
    position: 'absolute', left: 0, top: 0,
    transform: `translate(${x}px, ${y}px)`,
    transition: 'transform 600ms cubic-bezier(.5,0,.2,1), opacity 240ms ease',
    opacity: visible ? 1 : 0,
    pointerEvents: 'none', zIndex: 10,
  }}>
    {/* Outer ripple */}
    <div style={{
      position: 'absolute', left: -28, top: -28,
      width: 56, height: 56, borderRadius: '50%',
      border: `1.5px solid ${C.sage}`,
      opacity: tapping ? 0 : 0.55,
      transform: tapping ? 'scale(1.6)' : 'scale(0.5)',
      transition: 'transform 520ms ease-out, opacity 520ms ease-out',
    }} />
    {/* Solid touch dot */}
    <div style={{
      position: 'absolute', left: -10, top: -10,
      width: 20, height: 20, borderRadius: '50%',
      background: C.sage,
      opacity: tapping ? 0.95 : 0.7,
      transform: tapping ? 'scale(0.85)' : 'scale(1)',
      transition: 'transform 220ms ease, opacity 220ms ease',
      boxShadow: `0 0 0 4px ${C.sage}25`,
    }} />
  </div>
);

// ─── Phone shell (custom for landing) ──────────────────────────
const PhoneShell = ({ children, w = 340, h = 720 }) => (
  <div style={{
    width: w, height: h,
    borderRadius: 44,
    background: '#1c1c19',
    padding: 8,
    boxShadow: '0 30px 60px rgba(31,31,28,0.18), 0 6px 20px rgba(31,31,28,0.10), inset 0 0 0 1.5px rgba(255,255,255,.06)',
    position: 'relative',
  }}>
    <div style={{
      width: '100%', height: '100%',
      borderRadius: 36,
      background: C.paper,
      overflow: 'hidden',
      position: 'relative',
    }}>
      {/* Notch */}
      <div style={{
        position: 'absolute', top: 10, left: '50%', transform: 'translateX(-50%)',
        width: 96, height: 26, borderRadius: 14, background: '#1c1c19', zIndex: 20,
      }} />
      {/* Status time */}
      <div style={{
        position: 'absolute', top: 14, left: 24, zIndex: 20,
        fontFamily: 'var(--font-sans, system-ui)', fontSize: 13, fontWeight: 600, color: C.ink,
      }}>9:41</div>
      {children}
    </div>
  </div>
);

// ─── Hero · Plan Steady (mirrors PlanSteady from A-plan.jsx) ───
const SteadyDayRow = ({ day, date, kind, name, time, sub, past, today, rating, first }) => (
  <div style={{
    display: 'flex', alignItems: 'center', gap: 10,
    padding: '10px 8px',
    borderTop: first ? 0 : `1px solid ${C.paper3}`,
    opacity: past ? 0.55 : 1,
    background: today ? 'rgba(79,122,58,0.06)' : 'transparent',
    borderRadius: 0,
    margin: 0,
    position: 'relative',
  }}>
    {today && <div style={{ position: 'absolute', left: 0, top: 6, bottom: 6, width: 2, borderRadius: 1, background: C.sage }} />}
    <div style={{ width: 28, textAlign: 'center', flexShrink: 0 }}>
      <div style={{ fontFamily: 'var(--font-sans, system-ui)', fontSize: 9, fontWeight: 700, letterSpacing: '0.12em', color: C.ink3 }}>{day}</div>
      <div style={{ fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 16, lineHeight: 1, color: C.ink, marginTop: 2 }}>{date}</div>
    </div>
    <TypeTile kind={kind} size={42} />
    <div style={{ minWidth: 0, flex: 1 }}>
      <div style={{ fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 13.5, fontWeight: 600, color: C.ink, lineHeight: 1.2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{name}</div>
      <div style={{ fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 11, fontStyle: 'italic', color: C.ink3, marginTop: 2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
        {past && <span style={{ color: C.sage, fontStyle: 'normal', marginRight: 4 }}>✓</span>}
        {time} · {sub}
        {rating && <span style={{ color: C.amberD, fontStyle: 'normal', marginLeft: 5 }}>· {rating}</span>}
      </div>
    </div>
  </div>
);

const SteadyStackedDay = ({ day, date, items, past }) => (
  <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, padding: '10px 8px', opacity: past ? 0.55 : 1 }}>
    <div style={{ width: 28, textAlign: 'center', flexShrink: 0, paddingTop: 3 }}>
      <div style={{ fontFamily: 'var(--font-sans, system-ui)', fontSize: 9, fontWeight: 700, letterSpacing: '0.12em', color: C.ink3 }}>{day}</div>
      <div style={{ fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 16, lineHeight: 1, color: C.ink, marginTop: 2 }}>{date}</div>
    </div>
    <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 8 }}>
      {items.map((it, i) => (
        <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
          <TypeTile kind={it.kind} size={42} />
          <div style={{ minWidth: 0, flex: 1 }}>
            <div style={{ fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 13, fontWeight: 600, color: C.ink, lineHeight: 1.2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{it.name}</div>
            <div style={{ fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 10.5, fontStyle: 'italic', color: C.ink3, marginTop: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
              {past && <span style={{ color: C.sage, fontStyle: 'normal', marginRight: 3 }}>✓</span>}
              {it.time} · {it.sub}
            </div>
          </div>
        </div>
      ))}
    </div>
  </div>
);

const HeroPlanSteadyView = () => (
  <div style={{ paddingTop: 50, height: '100%', overflow: 'hidden', position: 'relative' }}>
    <div style={{ padding: '8px 18px 4px' }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 2 }}>
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={C.ink3} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
        <div style={{ fontFamily: 'var(--font-sans, system-ui)', fontSize: 10, fontWeight: 700, letterSpacing: '0.16em', color: C.ink3 }}>WEEK · MAY 4 → MAY 10</div>
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={C.ink3} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
      </div>
      <div style={{ fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 28, lineHeight: 1.05, textAlign: 'center', letterSpacing: '-0.02em', color: C.ink, marginTop: 4 }}>
        This <em style={{ color: C.sage, fontStyle: 'italic' }}>week</em>.
      </div>
      <div style={{ fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 11.5, fontStyle: 'italic', textAlign: 'center', color: C.ink3, marginTop: 4 }}>
        4 cook · 1 batch · 1 encore · 1 order in · 1 going out
      </div>
    </div>
    <div style={{ padding: '10px 14px 0' }}>
      <div style={{ background: C.paper2, borderRadius: 14, boxShadow: `inset 0 0 0 1px ${C.paper3}`, padding: '4px 10px' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 2px 4px' }}>
          <span style={{ fontFamily: 'var(--font-sans, system-ui)', fontSize: 9.5, fontWeight: 700, letterSpacing: '0.16em', color: C.sage }}>SUN → SAT</span>
          <span style={{ fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 10.5, fontStyle: 'italic', color: C.ink3 }}>seven nights, planned</span>
        </div>
        <SteadyStackedDay day="SUN" date="04" items={[
          { kind: 'batch',  name: 'Sunday roast chicken',     time: '50 min', sub: 'feeds Mon too' },
          { kind: 'encore', name: 'Roast wraps for lunch',    time: '0 min',  sub: 'pack-ahead' },
        ]} />
        <SteadyDayRow day="MON" date="05" kind="encore" name="Chicken & rice bowls" time="15 min" sub="from Sunday's roast" />
        <SteadyDayRow day="TUE" date="06" kind="cook"   name="Cacio e pepe"        time="15 min" sub="Lily's favorite" />
        <SteadyDayRow day="WED" date="07" kind="easy"   name="Stir-fry & rice"     time="20 min" sub="pantry meal" />
        <SteadyDayRow day="THU" date="08" kind="cook"   name="Sheet-pan salmon"    time="30 min" sub="sage & lemon" />
        <SteadyDayRow day="FRI" date="09" kind="order"  name="Pizza night"         time="35 min" sub="order in · usual" />
        <SteadyDayRow day="SAT" date="10" kind="moment" name="Out with friends"    time="7pm"    sub="Lucia's" />
      </div>
    </div>
    <div style={{ textAlign: 'center', padding: '10px 18px 0', fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 11, fontStyle: 'italic', color: C.ink3 }}>
      tap any day to swap · plans are alive
    </div>
    {window.LandingTabBar && <window.LandingTabBar active="plan" />}
  </div>
);

// ─── Main animated phone ───────────────────────────────────────
const PlanAnimatedPhone = ({ hero = false }) => {
  if (hero) return <PhoneShell><HeroPlanSteadyView /></PhoneShell>;
  const live = useChoreography(PLAN_SEQUENCE, LOOP_DURATION);
  const { state, caption } = hero
    ? { state: 'full', caption: 'Done. Seven nights, planned.' }
    : live;

  const sheetOpen = state === 'sheet-open' || state === 'pick-pasta';
  const isPicked = state === 'pick-pasta' || state === 'sheet-close' || state === 'rest'
                || state === 'drag-press' || state === 'drag-lift' || state === 'drag-move'
                || state === 'drag-drop' || state === 'drag-done';
  const isSwapped = state === 'sheet-close' || state === 'rest'
                 || state === 'drag-press' || state === 'drag-lift' || state === 'drag-move'
                 || state === 'drag-drop' || state === 'drag-done';
  const justSwapped = state === 'sheet-close'; // brief warm highlight when row updates
  const wedTapped = state === 'tap-wed' || state === 'sheet-open' || state === 'pick-pasta';

  // Drag-and-drop demo state
  const dragPhase = state === 'drag-press' ? 'press'
                  : state === 'drag-lift' ? 'lift'
                  : state === 'drag-move' ? 'move'
                  : state === 'drag-drop' ? 'drop'
                  : state === 'drag-done' ? 'done' : null;
  const dragSwapped = state === 'drag-done';
  const dragJustDone = state === 'drag-done';

  // Row Y positions inside the week list (measured from live DOM)
  const WED_Y = 390;
  const THU_Y = 454;
  const TILE_X = 75; // tile center x

  // Floating ghost tile coords (drag pickup → drop on Thu)
  let ghostY = WED_Y;
  let ghostScale = 1;
  let ghostOpacity = 0;
  if (dragPhase === 'press') { ghostY = WED_Y; ghostScale = 1; ghostOpacity = 0; }
  else if (dragPhase === 'lift') { ghostY = WED_Y - 4; ghostScale = 1.06; ghostOpacity = 1; }
  else if (dragPhase === 'move') { ghostY = THU_Y; ghostScale = 1.06; ghostOpacity = 1; }
  else if (dragPhase === 'drop') { ghostY = THU_Y; ghostScale = 1; ghostOpacity = 0; }

  // Cursor positions (relative to phone screen, ~324x704)
  // Wed (row idx 3) center‑y ≈ 390. Phone center‑x ≈ 162. Tile center‑x ≈ 75.
  let cursor = { x: 0, y: 0, visible: false, tapping: false };
  if (state === 'full' || state === 'tap-wed') {
    cursor = { x: 162, y: 390, visible: true, tapping: state === 'tap-wed' };
  } else if (state === 'sheet-open') {
    cursor = { x: 162, y: 390, visible: true, tapping: false };
  } else if (state === 'pick-pasta') {
    // Pantry option (index 2) inside the sheet
    cursor = { x: 110, y: 565, visible: true, tapping: true };
  } else if (dragPhase === 'press') {
    cursor = { x: TILE_X, y: WED_Y, visible: true, tapping: true };
  } else if (dragPhase === 'lift') {
    cursor = { x: TILE_X, y: WED_Y - 4, visible: true, tapping: true };
  } else if (dragPhase === 'move') {
    cursor = { x: TILE_X, y: THU_Y, visible: true, tapping: true };
  } else if (dragPhase === 'drop') {
    cursor = { x: TILE_X, y: THU_Y, visible: true, tapping: false };
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 28 }}>
    <PhoneShell>
      <div style={{ paddingTop: 50, height: '100%', overflow: 'hidden', position: 'relative' }}>
        {/* Header */}
        <div style={{ padding: '8px 18px 6px' }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 2 }}>
            <div style={{ width: 22, height: 22, display: 'flex', alignItems: 'center', justifyContent: 'center', color: C.ink3 }}>
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
            </div>
            <div style={{ fontFamily: 'var(--font-sans, system-ui)', fontSize: 10, fontWeight: 700, letterSpacing: '0.16em', color: C.sage }}>
              WEEK · MAY 4 → MAY 10
            </div>
            <div style={{ width: 22, height: 22, display: 'flex', alignItems: 'center', justifyContent: 'center', color: C.ink3 }}>
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
            </div>
          </div>
          <div style={{ fontFamily: 'var(--font-display, Georgia, serif)', fontSize: 30, lineHeight: 1.05, textAlign: 'center', letterSpacing: '-0.02em', color: C.ink, marginTop: 4 }}>
            This <em style={{ color: C.sage, fontStyle: 'italic' }}>week</em>.
          </div>
        </div>

        {/* Week body */}
        <div style={{ padding: '4px 8px 0', display: 'flex', flexDirection: 'column', gap: 1 }}>
          <div style={{ fontFamily: 'var(--font-sans, system-ui)', fontSize: 9, fontWeight: 700, letterSpacing: '0.14em', color: C.sage, padding: '6px 12px 2px' }}>
            SUN → SAT
          </div>
          {WEEK.map((_, i) => (
            <PlanWeekRow
              key={i} idx={i} state={state}
              isSwapped={isSwapped}
              isTapped={i === 3 && wedTapped && !justSwapped}
              justSwapped={i === 3 && justSwapped}
              dragPhase={dragPhase}
              isDragSource={i === 3 && dragPhase != null}
              isDropTarget={i === 4 && (dragPhase === 'move' || dragPhase === 'drop')}
              dragSwapped={dragSwapped}
              dragJustDone={dragJustDone && (i === 3 || i === 4)}
            />
          ))}
        </div>

        {/* Floating drag ghost (the pasta tile being dragged from Wed → Thu) */}
        {dragPhase && dragPhase !== 'done' && (
          <div style={{
            position: 'absolute', left: TILE_X - 21, top: 0,
            transform: `translateY(${ghostY - 21}px) scale(${ghostScale})`,
            opacity: ghostOpacity,
            transition: 'transform 700ms cubic-bezier(.32,.72,0,1), opacity 240ms ease',
            pointerEvents: 'none', zIndex: 9,
            filter: 'drop-shadow(0 8px 18px rgba(31,31,28,0.22))',
          }}>
            <TypeTile kind="easy" size={42} />
          </div>
        )}

        <AudibleSheet open={sheetOpen} pickedIdx={isPicked ? 2 : -1} />
        {!hero && <Cursor {...cursor} />}
        {window.LandingTabBar && <window.LandingTabBar active="plan" />}
      </div>
    </PhoneShell>

      {/* Caption — lifted out of the phone, editorial italic */}
      <div style={{
        maxWidth: 360, textAlign: 'center',
        fontFamily: 'var(--font-display, Georgia, serif)',
        fontSize: 18, fontStyle: 'italic',
        color: C.ink, lineHeight: 1.35, letterSpacing: '-0.01em',
        minHeight: 56, padding: '0 16px',
      }}>{caption}</div>
    </div>
  );
};

// ─── Mount ─────────────────────────────────────────────────────
const HeroPlanPhone = () => (
  <div style={{ pointerEvents: 'none' }}>
    <PlanAnimatedPhone hero />
  </div>
);

const mountPlanPhone = () => {
  const el = document.getElementById('plan-anim-mount');
  if (el) ReactDOM.createRoot(el).render(<PlanAnimatedPhone />);
  const heroEl = document.getElementById('hero-phone-mount');
  if (heroEl) ReactDOM.createRoot(heroEl).render(<HeroPlanPhone />);
};
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', mountPlanPhone);
} else {
  mountPlanPhone();
}

// Expose shared shell + tokens for sibling Babel scripts
Object.assign(window, { PhoneShell, PlanColors: C });
