// app/screen-recipe.jsx — read a recipe & decide. All actions live:
// start cooking · edit · favourite · add to shopping · plan this · share · rate · notes.

const overlayGhost = { background: 'rgba(12,12,14,0.55)', backdropFilter: 'blur(10px)',
  border: '1px solid rgba(255,255,255,0.18)', color: '#fff', boxShadow: 'none' };
function HeroDot() {
  return <span style={{ width: 3, height: 3, borderRadius: 999, background: 'rgba(255,255,255,0.4)' }} />;
}
function heroTime(r) {
  const info = (window.LCDomain && window.LCDomain.effortInfo) ? window.LCDomain.effortInfo(r) : null;
  return info && info.known ? info.label : '—';
}

// ── interactive rating ──
function RatingStars({ recipe, size = 16 }) {
  const { ratingFor, setRating } = useApp();
  const v = ratingFor(recipe);
  const [hover, setHover] = React.useState(0);
  return (
    <span style={{ display: 'inline-flex', gap: 2 }} onMouseLeave={() => setHover(0)}>
      {[1, 2, 3, 4, 5].map((i) => {
        const on = (hover || v) >= i;
        return (
          <button key={i} onMouseEnter={() => setHover(i)} onClick={() => setRating(recipe.id, i)}
            style={{ background: 'transparent', border: 'none', cursor: 'pointer', padding: 0,
              color: on ? 'var(--star)' : 'var(--paper-3)', display: 'inline-flex' }}>
            <Star size={size} />
          </button>
        );
      })}
    </span>
  );
}

function portalToast(node) {
  const host = (typeof document !== 'undefined') && document.getElementById('lc-screen');
  return host ? ReactDOM.createPortal(node, host) : node;
}

function Toast({ msg, action, onAction, onClose, top = 'calc(var(--lc-mobile-top, 14px) + 8px)' }) {
  React.useEffect(() => {
    const t = setTimeout(onClose, 4200);
    return () => clearTimeout(t);
  }, [msg]);
  // Portal into #lc-screen (the fixed device frame), NOT the scrolling content —
  // so toasts always sit at the top of the visible screen regardless of scroll.
  return portalToast(
    <div style={{ position: 'absolute', left: '50%', top, transform: 'translateX(-50%)',
      zIndex: 200, display: 'flex', alignItems: 'center', gap: 16, padding: '12px 16px 12px 18px',
      borderRadius: 999, background: 'rgba(22,22,26,0.96)', backdropFilter: 'blur(20px)',
      border: '1px solid var(--hairline)', boxShadow: '0 20px 50px rgba(0,0,0,0.6)',
      maxWidth: '90%' }}>
      <span style={{ width: 7, height: 7, borderRadius: 999, background: 'var(--olive)', flexShrink: 0 }} />
      <span style={{ fontFamily: 'Bricolage Grotesque', fontSize: 13, fontWeight: 500,
        color: 'var(--ink)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{msg}</span>
      {action && (
        <button onClick={onAction} style={{ background: 'transparent', border: 'none',
          cursor: 'pointer', color: 'var(--olive)', fontFamily: 'Bricolage Grotesque',
          fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap' }}>{action} →</button>
      )}
    </div>
  );
}

function Popover({ children, onClose, align = 'left', up = false }) {
  return (
    <>
      <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 40 }} />
      <div style={{ position: 'absolute', [up ? 'bottom' : 'top']: 'calc(100% + 8px)', [align]: 0, zIndex: 41,
        minWidth: 240, padding: 12, borderRadius: 14, background: 'rgba(20,20,24,0.97)',
        backdropFilter: 'blur(20px)', border: '1px solid var(--hairline)',
        boxShadow: '0 24px 60px rgba(0,0,0,0.6)' }}>{children}</div>
    </>
  );
}

function PlanPopover({ recipe, onClose, onPlanned, up, plannedDay }) {
  const { WEEK_DAYS, setPlanDay, state } = useApp();
  const plannedKey = Object.keys(state.plan || {}).find(
    (k) => state.plan[k] && !state.plan[k].skip && state.plan[k].recipeId === recipe.id);
  return (
    <Popover onClose={onClose} up={up}>
      <div style={{ fontFamily: 'Bricolage Grotesque', fontSize: 10, fontWeight: 600,
        letterSpacing: '0.24em', textTransform: 'uppercase', color: 'var(--olive)',
        padding: '4px 8px 10px' }}>{plannedDay ? 'Planned for ' + plannedDay : 'Plan for…'}</div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 6 }}>
        {WEEK_DAYS.map((d) => {
          const on = plannedKey === d.key;
          return (
          <button key={d.key} onClick={() => {
            if (on) { setPlanDay(d.key, {}); onClose(); onPlanned(d.day, true); }
            else { setPlanDay(d.key, { recipeId: recipe.id, why: 'you planned this', tag: 'your pick' });
              onClose(); onPlanned(d.day); }
          }} style={{ padding: '10px 6px', borderRadius: 10, cursor: 'pointer',
            background: on ? 'rgba(240,168,80,0.16)' : 'rgba(255,255,255,0.04)',
            border: on ? '1px solid rgba(240,168,80,0.5)' : '1px solid var(--hairline)',
            display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
            <span style={{ fontFamily: 'Bricolage Grotesque', fontSize: 9, fontWeight: 600,
              letterSpacing: '0.16em', textTransform: 'uppercase', color: on ? 'var(--olive)' : 'var(--ink-3)' }}>{d.day}</span>
            <span style={{ fontFamily: 'Bricolage Grotesque', fontSize: 18, fontWeight: 700,
              color: on ? 'var(--olive)' : 'var(--ink)', letterSpacing: '-0.03em' }}>{d.date}</span>
          </button>
          );
        })}
      </div>
      {plannedDay && (
        <div style={{ fontFamily: 'Spectral', fontStyle: 'italic', fontSize: 12, color: 'var(--ink-3)',
          padding: '10px 8px 2px', lineHeight: 1.4 }}>Tap {plannedDay.slice(0, 3)} again to remove · or pick another day to move it.</div>
      )}
    </Popover>
  );
}

function SharePopover({ recipe, onClose }) {
  const [copied, setCopied] = React.useState(false);
  const link = `luiscooks.app/r/${recipe.id}/shared/k3jf9`;
  return (
    <Popover onClose={onClose} align="right">
      <div style={{ fontFamily: 'Bricolage Grotesque', fontSize: 13, fontWeight: 600,
        color: 'var(--ink)', padding: '2px 4px' }}>Share this recipe</div>
      <div style={{ fontFamily: 'Spectral', fontStyle: 'italic', fontSize: 12,
        color: 'var(--ink-3)', padding: '2px 4px 12px' }}>Read-only · attribution always shown</div>
      <div style={{ display: 'flex', gap: 8 }}>
        <input readOnly value={link} style={{ flex: 1, background: 'rgba(0,0,0,0.4)',
          border: '1px solid var(--hairline)', borderRadius: 8, padding: '8px 10px',
          color: 'var(--ink-2)', fontFamily: 'var(--mono)', fontSize: 11, outline: 'none', minWidth: 0 }} />
        <FButton size="s" onClick={() => {
          try { navigator.clipboard.writeText(link); } catch (e) {}
          setCopied(true); setTimeout(() => setCopied(false), 1600);
        }}>{copied ? 'Copied' : 'Copy'}</FButton>
      </div>
    </Popover>
  );
}

// ── method list ──
function MethodList({ recipe, mini }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: mini ? 24 : 32 }}>
      {recipe.directions.map((d, i) => (
        <div key={i} style={{ display: 'grid', gridTemplateColumns: mini ? '48px 1fr' : '76px 1fr',
          gap: mini ? 14 : 20, alignItems: 'flex-start' }}>
          <span style={{ fontFamily: 'Spectral', fontStyle: 'italic', fontWeight: 500,
            fontSize: mini ? 42 : 68, lineHeight: 0.9, letterSpacing: '-0.02em',
            color: 'var(--olive)', paddingTop: 4 }}>{String(i + 1).padStart(2, '0')}</span>
          <p style={{ fontFamily: 'Spectral', fontWeight: 400, fontSize: mini ? 16 : 20,
            lineHeight: 1.6, color: 'var(--ink)', margin: mini ? 0 : '8px 0 0' }}>{d}</p>
        </div>
      ))}
    </div>
  );
}

function NotesCard({ recipe }) {
  const { noteFor, setNote } = useApp();
  const [editing, setEditing] = React.useState(false);
  const note = noteFor(recipe);
  return (
    <div style={{ padding: '22px 24px', background: 'rgba(240,168,80,0.05)',
      border: '1px solid rgba(240,168,80,0.20)', borderRadius: 14 }}>
      <span style={{ fontFamily: 'Bricolage Grotesque', fontSize: 10, fontWeight: 600,
        letterSpacing: '0.28em', textTransform: 'uppercase', color: 'var(--olive)' }}>Your notes</span>
      {editing ? (
        <textarea autoFocus value={note} onChange={(e) => setNote(recipe.id, e.target.value)}
          onBlur={() => setEditing(false)}
          style={{ width: '100%', marginTop: 12, minHeight: 90, resize: 'vertical',
            background: 'rgba(0,0,0,0.3)', border: '1px solid var(--hairline)', borderRadius: 10,
            padding: '10px 12px', color: 'var(--ink)', fontFamily: 'Caveat', fontSize: 24,
            lineHeight: 1.3, outline: 'none', boxSizing: 'border-box' }} />
      ) : (
        <p style={{ fontFamily: 'Caveat', fontWeight: 500, fontSize: 28, lineHeight: 1.3,
          color: note ? 'var(--ink)' : 'var(--ink-4)', margin: '12px 0 14px' }}>
          {note || 'Add a note for next time…'}
        </p>
      )}
      <button onClick={() => setEditing((e) => !e)} style={{ fontFamily: 'Bricolage Grotesque',
        fontSize: 12, fontWeight: 500, background: 'transparent', color: 'var(--olive)',
        border: 'none', cursor: 'pointer', padding: 0 }}>
        {editing ? 'done →' : 'edit your notes →'}
      </button>
    </div>
  );
}

// The single Effort cell (replaces the old PREP / COOK columns). Effort =
// active, hands-on minutes. Three states: a value, an in-flight AI estimate,
// or — for recipes with no timing — an opt-in "Estimate" affordance.
function EffortCell({ recipe, compact }) {
  const { estimateEffort } = useApp();
  const info = window.LCDomain.effortInfo(recipe);
  const valSize = compact ? 13.5 : 22;
  if (info.status === 'estimating') {
    return (
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, color: 'var(--olive)',
        fontFamily: 'Bricolage Grotesque', fontWeight: 600, fontSize: compact ? 12.5 : 15 }}>
        <Spinner size={compact ? 12 : 14} /> Estimating…
      </span>
    );
  }
  if (info.known) {
    return (
      <span style={{ fontFamily: 'Bricolage Grotesque', fontWeight: 600, fontSize: valSize,
        color: 'var(--ink)', letterSpacing: '-0.02em', whiteSpace: 'nowrap' }}>{info.label}</span>
    );
  }
  // No timing at all → opt-in AI estimate (one credit, on demand).
  return (
    <button onClick={() => estimateEffort(recipe.id)} title="Estimate active time with AI"
      style={{ display: 'inline-flex', alignItems: 'center', gap: 6, cursor: 'pointer',
        padding: compact ? '5px 10px' : '7px 13px', borderRadius: 999,
        border: '1px solid rgba(240,168,80,0.42)', background: 'rgba(240,168,80,0.10)',
        color: 'var(--olive)', fontFamily: 'Bricolage Grotesque', fontWeight: 600,
        fontSize: compact ? 11 : 12.5, whiteSpace: 'nowrap' }}>
      <Icon.sparkle size={compact ? 12 : 13} /> Estimate
    </button>
  );
}

function MetaStrip({ recipe, cols, compact }) {
  const tight = compact || cols <= 3;
  const cells = [
    { l: 'rating', el: <RatingStars recipe={recipe} size={compact ? 13 : (tight ? 14 : 16)} /> },
    { l: 'effort', el: <EffortCell recipe={recipe} compact={compact} /> },
    { l: 'serves', v: (recipe.servings || '').replace(/serves\s*/i, '') || '4' },
    { l: 'difficulty', v: recipe.difficulty },
  ];
  // Mobile: four centred stats separated by hairlines, with a shared value
  // baseline so stars, numbers and words line up cleanly.
  if (compact) {
    return (
      <div style={{ display: 'flex', alignItems: 'stretch', overflow: 'hidden',
        background: 'rgba(255,255,255,0.03)', border: '1px solid var(--hairline)', borderRadius: 14 }}>
        {cells.map((m, i) => (
          <div key={m.l} style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column',
            alignItems: 'center', gap: 7, padding: '13px 6px',
            borderLeft: i > 0 ? '1px solid var(--hairline)' : 'none' }}>
            <div style={{ fontFamily: 'Bricolage Grotesque', fontSize: 8.5, fontWeight: 600,
              letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--ink-3)',
              whiteSpace: 'nowrap' }}>{m.l}</div>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: 18 }}>
              {m.el || <span style={{ fontFamily: 'Bricolage Grotesque', fontWeight: 600, fontSize: 13.5,
                color: 'var(--ink)', letterSpacing: '-0.02em', whiteSpace: 'nowrap',
                textTransform: m.l === 'difficulty' ? 'capitalize' : 'none' }}>{m.v}</span>}
            </div>
          </div>
        ))}
      </div>
    );
  }
  return (
    <div style={{ display: 'grid', gridTemplateColumns: `repeat(${cols}, 1fr)`,
      background: 'rgba(255,255,255,0.03)', border: '1px solid var(--hairline)',
      borderRadius: 14, padding: tight ? '18px 18px' : '24px 28px', rowGap: tight ? 16 : 0 }}>
      {cells.map((m, i) => (
        <div key={m.l} style={{ minWidth: 0, paddingRight: tight ? 10 : 20,
          paddingLeft: (i % cols !== 0) ? (tight ? 12 : 22) : 0,
          paddingTop: i >= cols ? (tight ? 0 : 18) : 0,
          borderRight: (i % cols !== cols - 1) ? '1px solid var(--hairline)' : 'none' }}>
          <div style={{ fontFamily: 'Bricolage Grotesque', fontSize: 10, fontWeight: 600,
            letterSpacing: tight ? '0.16em' : '0.24em', textTransform: 'uppercase', color: 'var(--ink-3)',
            marginBottom: 8, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{m.l}</div>
          {m.el || <div style={{ fontFamily: 'Bricolage Grotesque', fontWeight: 600, fontSize: tight ? 17 : 22,
            color: 'var(--ink)', letterSpacing: '-0.02em', whiteSpace: 'nowrap', overflow: 'hidden',
            textOverflow: 'ellipsis' }}>{m.v}</div>}
        </div>
      ))}
    </div>
  );
}

// ── Action bar shared ──
function useRecipeActions(recipe) {
  const { go, openRecipe, isFav, toggleFav, addRecipeToShopping, back, state, WEEK_DAYS } = useApp();
  const [toast, setToast] = React.useState(null);
  const [planOpen, setPlanOpen] = React.useState(false);
  const [shareOpen, setShareOpen] = React.useState(false);

  // Surface an enhancement failure as a toast (not an inline panel). Fires only
  // on the enhancing→failed transition, so re-opening a failed recipe stays quiet.
  const prevStatus = React.useRef(recipe.enhancementStatus);
  React.useEffect(() => {
    const s = recipe.enhancementStatus;
    if (prevStatus.current === 'enhancing' && s === 'failed') {
      setToast({ msg: recipe.enhancementError || 'Couldn’t enhance the photo — please try again.',
        action: 'Try again', enhanceRetry: true });
    }
    prevStatus.current = s;
  }, [recipe.enhancementStatus, recipe.enhancementError]);
  const fav = isFav(recipe.id);
  const startCooking = () => go('cook', { recipeId: recipe.id });
  const edit = () => go('edit', { recipeId: recipe.id });
  const addShopping = () => {
    addRecipeToShopping(recipe.id);
  };
  // which day (if any) this recipe is planned for (plan is keyed by ISO date)
  const DAY_FULL = { Mon: 'Monday', Tue: 'Tuesday', Wed: 'Wednesday', Thu: 'Thursday',
    Fri: 'Friday', Sat: 'Saturday', Sun: 'Sunday' };
  const plannedKey = Object.keys(state.plan || {}).find(
    (k) => state.plan[k] && !state.plan[k].skip && state.plan[k].recipeId === recipe.id);
  let plannedDay = null;
  if (plannedKey) {
    const wd = (WEEK_DAYS || []).find((d) => d.key === plannedKey);
    if (wd) { plannedDay = DAY_FULL[wd.day]; }
    else {
      const [yy, mm, dd] = plannedKey.split('-').map(Number);
      const dt = new Date(yy, (mm || 1) - 1, dd || 1);
      const DOWF = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
      const MONF = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
      plannedDay = `${DOWF[dt.getDay()]} ${dt.getDate()} ${MONF[dt.getMonth()]}`;
    }
  }
  return { toast, setToast, planOpen, setPlanOpen, shareOpen, setShareOpen, fav, toggleFav,
    startCooking, edit, addShopping, plannedDay };
}

// ── Desktop / tablet ──
// ─────────────────────────────────────────────────────────────
// Photo enhancement — the "Enhanced" badge becomes a control, and a
// generate panel takes the photo's place when a recipe has none.
// (Status + trigger come from the adapter; mock simulates, live calls the API.)
// ─────────────────────────────────────────────────────────────
(function injectEnhanceCSS() {
  if (typeof document === 'undefined' || document.getElementById('lc-enhance-css')) return;
  const st = document.createElement('style');
  st.id = 'lc-enhance-css';
  st.textContent = `
    @keyframes lcSpin { to { transform: rotate(360deg); } }
    @keyframes lcShimmer { 0% { transform: translateX(-120%); } 100% { transform: translateX(120%); } }
    .lc-shimmer::after { content:''; position:absolute; inset:0;
      background: linear-gradient(100deg, transparent 18%, rgba(240,168,80,0.18) 50%, transparent 82%);
      transform: translateX(-120%); animation: lcShimmer 1.6s ease-in-out infinite; }
  `;
  document.head.appendChild(st);
})();

function Spinner({ size = 15, color = 'var(--olive)' }) {
  return <span style={{ width: size, height: size, borderRadius: '50%',
    border: '2px solid rgba(240,168,80,0.28)', borderTopColor: color, boxSizing: 'border-box',
    display: 'inline-block', animation: 'lcSpin 0.7s linear infinite', flexShrink: 0 }} />;
}

// enhancementStatus is authoritative (the adapter sets it). Default to 'none'
// — a recipe with a real photo but no AI enhancement should offer *Enhance*,
// not read as already-enhanced. 'done' means an AI photo genuinely exists.
function enhanceStatusOf(recipe) {
  return recipe.enhancementStatus || 'none';
}

const enhPill = { display: 'inline-flex', alignItems: 'center', gap: 8, height: 38,
  padding: '0 14px', boxSizing: 'border-box',
  borderRadius: 999, background: 'rgba(12,12,14,0.72)', backdropFilter: 'blur(10px)',
  border: '1px solid rgba(255,255,255,0.14)', fontFamily: 'Bricolage Grotesque',
  fontSize: 11, fontWeight: 600, letterSpacing: '0.06em', cursor: 'pointer', color: '#fff' };
const overlayBackBtn = { display: 'inline-flex', alignItems: 'center', gap: 8, height: 38,
  padding: '0 16px 0 13px', borderRadius: 999, cursor: 'pointer', background: 'rgba(12,12,14,0.6)',
  backdropFilter: 'blur(10px)', border: '1px solid rgba(255,255,255,0.16)', color: '#fff',
  fontFamily: 'Bricolage Grotesque', fontSize: 13, fontWeight: 500 };
const heroKicker = { fontFamily: 'Bricolage Grotesque', fontSize: 11, fontWeight: 600,
  letterSpacing: '0.28em', textTransform: 'uppercase', color: 'var(--olive)' };

const popHeading = { fontFamily: 'Spectral', fontStyle: 'italic', fontSize: 12,
  color: 'var(--ink-3)', padding: '2px 6px 8px' };

// The over-photo control. Five states, driven by hasOriginalPhoto +
// enhancementStatus + enhancementMethod (see CONTRACT.md → Photo enhancement):
//   has source, none      → Enhance ▸ AI Upscale · AI Generate
//   has source, upscaled  → AI Upscaled ▸ Revert · Re-upscale · Switch to Generate
//   has source, generated → AI Generated ▸ Revert · Re-generate · Switch to Upscale
//   no source,  none      → AI Generate (single action)
//   no source,  generated → AI Generated ▸ Re-generate
function EnhanceControl({ recipe }) {
  const { enhancePhoto, revertPhoto } = useApp();
  const [open, setOpen] = React.useState(false);
  const status = enhanceStatusOf(recipe);
  const hasOriginal = !!recipe.hasOriginalPhoto;
  const generated = recipe.enhancementMethod === 'generated';
  const run = (method) => { setOpen(false); enhancePhoto(recipe.id, { method }); };
  const revert = () => { setOpen(false); revertPhoto(recipe.id); };

  // Enhancing — a busy pill (the hero also shows a shimmer sweep).
  if (status === 'enhancing') {
    return (
      <span style={{ ...enhPill, cursor: 'default', color: 'var(--olive)', borderColor: 'rgba(240,168,80,0.4)' }}>
        <Spinner size={13} /> {hasOriginal ? 'Enhancing…' : 'Generating…'}
      </span>
    );
  }
  // Done — label reflects how the photo was made; menu options depend on
  // whether a real source exists to revert to / switch from.
  if (status === 'done') {
    return (
      <div style={{ position: 'relative' }}>
        <button onClick={() => setOpen((o) => !o)} style={{ ...enhPill, fontSize: 10,
          letterSpacing: '0.18em', textTransform: 'uppercase' }}>
          <span style={{ color: 'var(--olive)' }}>✦</span> {generated ? 'AI Generated' : 'AI Upscaled'}
          <Icon.chevD size={13} style={{ opacity: 0.55 }} />
        </button>
        {open && (
          <Popover onClose={() => setOpen(false)} align="right">
            <div style={popHeading}>{generated ? 'AI-generated photo' : 'AI-upscaled photo'}</div>
            {hasOriginal && <PopRow icon={<Icon.undo size={15} />} label="Revert to original"
              onClick={revert} />}
            {generated
              ? <PopRow icon={<Icon.sparkle size={15} />} label="Re-generate" onClick={() => run('generate')} />
              : <PopRow icon={<Icon.expand size={15} />} label="Re-upscale" onClick={() => run('upscale')} />}
            {hasOriginal && (generated
              ? <PopRow icon={<Icon.expand size={15} />} label="Switch to AI Upscale" onClick={() => run('upscale')} />
              : <PopRow icon={<Icon.sparkle size={15} />} label="Switch to AI Generate" onClick={() => run('generate')} />)}
          </Popover>
        )}
      </div>
    );
  }
  // none / failed, NO source — a single Generate action (no Upscale, no Revert).
  if (!hasOriginal) {
    return (
      <button onClick={() => run('generate')} style={enhPill}>
        <Icon.sparkle size={13} style={{ color: 'var(--olive)' }} /> Generate Photo
      </button>
    );
  }
  // none / failed, HAS source — Enhance ▸ choose Upscale or Generate.
  return (
    <div style={{ position: 'relative' }}>
      <button onClick={() => setOpen((o) => !o)} style={enhPill}>
        <Icon.sparkle size={13} style={{ color: 'var(--olive)' }} /> Enhance
        <Icon.chevD size={13} style={{ opacity: 0.55 }} />
      </button>
      {open && (
        <Popover onClose={() => setOpen(false)} align="right">
          <div style={popHeading}>Enhance this photo</div>
          <PopRow icon={<Icon.expand size={15} />} label="AI Upscale"
            sub="Sharpen the real photo" onClick={() => run('upscale')} />
          <PopRow icon={<Icon.sparkle size={15} />} label="AI Generate"
            sub="Create a fresh image" onClick={() => run('generate')} />
        </Popover>
      )}
    </div>
  );
}

function PopRow({ icon, label, sub, onClick }) {
  return (
    <button onClick={onClick} style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 10,
      padding: '10px 10px', borderRadius: 9, background: 'transparent', border: 'none', cursor: 'pointer',
      fontFamily: 'Bricolage Grotesque', textAlign: 'left' }}
      onMouseEnter={(e) => (e.currentTarget.style.background = 'rgba(255,255,255,0.05)')}
      onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}>
      <span style={{ color: 'var(--olive)', display: 'inline-flex', flexShrink: 0 }}>{icon}</span>
      <span style={{ display: 'flex', flexDirection: 'column', gap: 2, minWidth: 0 }}>
        <span style={{ fontSize: 13, fontWeight: 500, color: 'var(--ink)' }}>{label}</span>
        {sub && <span style={{ fontFamily: 'Spectral', fontStyle: 'italic', fontSize: 11.5,
          color: 'var(--ink-3)' }}>{sub}</span>}
      </span>
    </button>
  );
}

// Shared primary actions (Start cooking · Add to plan) — overlaid on a photo
// hero, or in-flow beneath the generate panel.
function HeroActions({ recipe, a, compact, overlay }) {
  const ghost = overlay ? overlayGhost : undefined;
  return (
    <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginTop: overlay ? 4 : 0 }}>
      <FButton size={compact ? 'm' : 'l'} onClick={a.startCooking}>
        Start cooking <span style={{ fontSize: 18 }}>→</span>
      </FButton>
      <div style={{ position: 'relative' }}>
        <FButton size={compact ? 'm' : 'l'} variant="ghost" onClick={() => a.setPlanOpen((o) => !o)}
          style={a.plannedDay
            ? { ...(ghost || {}), background: 'rgba(240,168,80,0.18)', borderColor: 'rgba(240,168,80,0.5)', color: 'var(--olive)' }
            : ghost}>
          {a.plannedDay
            ? <><Icon.check size={15} stroke={2.4} />{`Planned for ${a.plannedDay}`}</>
            : <><Icon.bookmark size={14} />Add to plan</>}
        </FButton>
        {a.planOpen && <PlanPopover up={overlay} recipe={recipe} plannedDay={a.plannedDay} onClose={() => a.setPlanOpen(false)}
          onPlanned={() => {}} />}
      </div>
    </div>
  );
}

function RecipeWide({ recipe, compact }) {
  const { back, photoFor, ratingFor, noteFor, go, enhancePhoto } = useApp();
  const a = useRecipeActions(recipe);
  const pad = compact ? '0 32px' : '0 48px';
  const photo = photoFor(recipe.id);

  return (
    <div style={{ minHeight: '100%', position: 'relative' }}>
      {/* HERO — same overlay layout whether or not there's a photo (placeholder fills in) */}
      <div style={{ padding: compact ? '20px 32px 0' : '24px 48px 0' }}>
        <div style={{ position: 'relative', height: compact ? 472 : 600, borderRadius: 18,
          overflow: 'hidden', boxShadow: '0 30px 80px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04)' }}>
          {photo
            ? <img src={photo} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
            : <div style={{ position: 'absolute', inset: 0, background: recipe.bg || '#1a1610',
                backgroundImage: `repeating-linear-gradient(${recipe.angle || 35}deg, rgba(255,255,255,0.05) 0 16px, transparent 16px 32px)` }} />}
          <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none',
            background: 'linear-gradient(180deg, rgba(0,0,0,0.42) 0%, transparent 22%, transparent 38%, rgba(0,0,0,0.5) 72%, rgba(0,0,0,0.86) 100%)' }} />
          {recipe.enhancementStatus === 'enhancing' && (
            <div className="lc-shimmer" style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }} />
          )}
          <div style={{ position: 'absolute', top: 18, left: 18 }}>
            <button onClick={back} style={overlayBackBtn}>← Back</button>
          </div>
          <div style={{ position: 'absolute', top: 18, right: 18, display: 'flex', alignItems: 'center', gap: 10 }}>
            <EnhanceControl recipe={recipe} />
            <div style={{ position: 'relative' }}>
              <RoundBtn onClick={() => a.setShareOpen((o) => !o)}><Icon.share size={16} /></RoundBtn>
              {a.shareOpen && <SharePopover recipe={recipe} onClose={() => a.setShareOpen(false)} />}
            </div>
            <RoundBtn onClick={a.edit}><Icon.edit size={16} /></RoundBtn>
            <RoundBtn onClick={() => a.toggleFav(recipe.id)} active={a.fav}><HeartIcon filled={a.fav} size={16} /></RoundBtn>
          </div>
          <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0,
            padding: compact ? '80px 32px 30px' : '110px 48px 40px', display: 'flex', flexDirection: 'column',
            gap: compact ? 16 : 20,
            background: 'linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.5) 36%, rgba(0,0,0,0.82) 100%)' }}>
            <div style={{ maxWidth: 920 }}>
              <span style={heroKicker}>{(recipe.meals || []).join(' · ')}</span>
              <div style={{ margin: '10px 0 0' }}>
                <FTitle name={recipe.name} size={compact ? 56 : 78} color="#fff" italicColor="var(--olive)" breakBeforeWith={false} />
              </div>
              {recipe.description && (
                <p style={{ fontFamily: 'Spectral', fontStyle: 'italic', fontSize: compact ? 16 : 19,
                  lineHeight: 1.45, color: 'rgba(255,255,255,0.82)', margin: '12px 0 0', maxWidth: 660,
                  display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
                  {recipe.description}
                </p>
              )}
            </div>
            <HeroActions recipe={recipe} a={a} compact={compact} overlay />
          </div>
        </div>
      </div>

      {/* Secondary actions (popovers live here, in flow, never clipped) */}
      {/* Detailed meta + cost */}
      <div style={{ padding: pad, marginTop: 20, display: 'flex', flexDirection: 'column', gap: 16 }}>
        <MetaStrip recipe={recipe} cols={4} />
        <CostBar recipe={recipe} compact={compact} />
      </div>

      {/* Body */}
      <div style={{ padding: compact ? '48px 32px 72px' : '56px 48px 80px', display: 'grid',
        gridTemplateColumns: compact ? '300px 1fr 260px' : '340px 1fr 300px', gap: compact ? 40 : 56 }}>
        <aside style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>
          <SectionHead kicker="Ingredients" title="What you'll need" sub={`${recipe.servings} · scale at cook time`} />
          <FIngredients ingredients={recipe.ingredients} mode="view" />
        </aside>
        <div>
          <SectionHead kicker="Method" title="How you cook it" />
          <div style={{ marginTop: 28 }}><MethodList recipe={recipe} /></div>
        </div>
        <aside style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
          <NotesCard recipe={recipe} />
          <div>
            <SidebarLabel>Filed under</SidebarLabel>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 14 }}>
              {(recipe.meals || []).map((c) => (
                <span key={c} style={{ fontFamily: 'Bricolage Grotesque', fontSize: 12, fontWeight: 500,
                  padding: '5px 12px', borderRadius: 999, border: '1px solid rgba(240,168,80,0.35)',
                  background: 'rgba(240,168,80,0.08)', color: 'var(--olive)' }}>{c}</span>
              ))}
            </div>
          </div>
          {recipe.source && (
            <div>
              <SidebarLabel>Source</SidebarLabel>
              <div style={{ fontFamily: 'Spectral', fontStyle: 'italic', fontSize: 15,
                color: 'var(--ink-2)', marginTop: 10, lineHeight: 1.5 }}>{recipe.source}</div>
            </div>
          )}
        </aside>
      </div>

      {a.toast && <Toast msg={a.toast.msg} action={a.toast.action}
        onAction={() => { if (a.toast.enhanceRetry) enhancePhoto(recipe.id); else go(a.toast.go); a.setToast(null); }}
        onClose={() => a.setToast(null)} />}
    </div>
  );
}

function SectionHead({ kicker, title, sub }) {
  return (
    <div>
      <span style={{ fontFamily: 'Bricolage Grotesque', fontSize: 10, fontWeight: 600,
        letterSpacing: '0.28em', textTransform: 'uppercase', color: 'var(--olive)' }}>{kicker}</span>
      <h3 style={{ fontFamily: 'Bricolage Grotesque', fontWeight: 600, fontSize: 32, lineHeight: 1.05,
        letterSpacing: '-0.03em', color: 'var(--ink)', margin: '6px 0 0' }}>
        {title}<span style={{ fontFamily: 'Spectral', fontStyle: 'italic', fontWeight: 400,
          color: 'var(--olive)' }}>.</span>
      </h3>
      {sub && <div style={{ fontFamily: 'Spectral', fontStyle: 'italic', fontSize: 14,
        color: 'var(--ink-3)', marginTop: 8 }}>{sub}</div>}
    </div>
  );
}
function SidebarLabel({ children }) {
  return <span style={{ fontFamily: 'Bricolage Grotesque', fontSize: 10, fontWeight: 600,
    letterSpacing: '0.28em', textTransform: 'uppercase', color: 'var(--ink-3)' }}>{children}</span>;
}

function PlaceholderHero({ recipe, height }) {
  return (
    <div style={{ height, borderRadius: 18, overflow: 'hidden', position: 'relative',
      background: recipe.bg || '#1a1610', boxShadow: '0 30px 80px rgba(0,0,0,0.5)' }}>
      <div style={{ position: 'absolute', inset: 0,
        background: `repeating-linear-gradient(${recipe.angle || 35}deg, rgba(255,255,255,0.05) 0 16px, transparent 16px 32px)` }} />
      <span style={{ position: 'absolute', left: 24, bottom: 22, fontFamily: 'var(--mono)',
        fontSize: 13, color: 'rgba(255,255,255,0.6)' }}>{recipe.placeholder || recipe.name.toLowerCase()}</span>
    </div>
  );
}

// ── Mobile ──
function RecipeMobile({ recipe }) {
  const { back, photoFor, go, enhancePhoto, revertPhoto } = useApp();
  const a = useRecipeActions(recipe);
  const [tab, setTab] = React.useState('Ingredients');
  const [moreOpen, setMoreOpen] = React.useState(false);
  const photo = photoFor(recipe.id);

  // Photo action row for the ⋯ menu — mirrors the EnhanceControl states.
  const pstatus = enhanceStatusOf(recipe);
  const hasOriginal = !!recipe.hasOriginalPhoto;
  const generated = recipe.enhancementMethod === 'generated';
  let photoRows;
  if (pstatus === 'enhancing') {
    photoRows = <PopRow icon={<Spinner size={15} />} label={hasOriginal ? 'Enhancing…' : 'Generating…'} onClick={() => {}} />;
  } else if (pstatus === 'done') {
    photoRows = (
      <>
        {hasOriginal && <PopRow icon={<Icon.undo size={15} />} label="Revert to original"
          onClick={() => { setMoreOpen(false); revertPhoto(recipe.id); }} />}
        <PopRow icon={<Icon.sparkle size={15} />} label={generated ? 'Re-generate photo' : 'Re-upscale photo'}
          onClick={() => { setMoreOpen(false); enhancePhoto(recipe.id, { method: generated ? 'generate' : 'upscale' }); }} />
      </>
    );
  } else {
    photoRows = <PopRow icon={<Icon.sparkle size={15} />} label={hasOriginal ? 'Enhance photo' : 'Generate a photo'}
      onClick={() => { setMoreOpen(false); enhancePhoto(recipe.id, hasOriginal ? {} : { method: 'generate' }); }} />;
  }

  return (
    <div style={{ position: 'relative', minHeight: '100%' }}>
      {/* Hero — same overlay layout whether or not there's a photo */}
      <div style={{ position: 'relative', height: 300, overflow: 'hidden' }}>
        {photo
          ? <img src={photo} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
          : <div style={{ position: 'absolute', inset: 0, background: recipe.bg || '#1a1610',
              backgroundImage: `repeating-linear-gradient(${recipe.angle || 35}deg, rgba(255,255,255,0.05) 0 14px, transparent 14px 28px)` }} />}
        <div style={{ position: 'absolute', inset: 0, pointerEvents: 'none',
          background: 'linear-gradient(180deg, rgba(0,0,0,0.5) 0%, transparent 30%, transparent 42%, rgba(0,0,0,0.55) 74%, rgba(0,0,0,0.9) 100%)' }} />
        {recipe.enhancementStatus === 'enhancing' && (
          <div className="lc-shimmer" style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }} />
        )}
        <div style={{ position: 'absolute', top: 0, left: 0, right: 0, padding: '38px 16px 14px',
          paddingTop: 'var(--lc-mobile-top, 38px)',
          display: 'flex', alignItems: 'center', gap: 8 }}>
          <RoundBtn onClick={back}><Icon.arrow size={17} stroke={2.3} style={{ transform: 'rotate(180deg)' }} /></RoundBtn>
          {recipe.enhancementStatus === 'enhancing' && (
            <span style={{ ...enhPill, height: 38, cursor: 'default', color: 'var(--olive)',
              borderColor: 'rgba(240,168,80,0.4)' }}><Spinner size={13} /> {hasOriginal ? 'Enhancing…' : 'Generating…'}</span>
          )}
          <span style={{ flex: 1 }} />
          <RoundBtn onClick={() => a.toggleFav(recipe.id)} active={a.fav}><HeartIcon filled={a.fav} /></RoundBtn>
          <div style={{ position: 'relative' }}>
            <RoundBtn onClick={() => setMoreOpen((o) => !o)}><Icon.more size={18} /></RoundBtn>
            {moreOpen && (
              <Popover onClose={() => setMoreOpen(false)} align="right">
                <div style={popHeading}>Recipe options</div>
                {photoRows}
                <PopRow icon={<Icon.edit size={15} />} label="Edit recipe"
                  onClick={() => { setMoreOpen(false); a.edit(); }} />
                <PopRow icon={<Icon.share size={15} />} label="Share recipe"
                  onClick={() => { setMoreOpen(false); a.setShareOpen(true); }} />
              </Popover>
            )}
            {a.shareOpen && <SharePopover recipe={recipe} onClose={() => a.setShareOpen(false)} />}
          </div>
        </div>
        <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '0 18px 16px' }}>
          <span style={{ fontFamily: 'Bricolage Grotesque', fontSize: 9, fontWeight: 600,
            letterSpacing: '0.24em', textTransform: 'uppercase', color: 'var(--olive)' }}>
            {(recipe.meals || []).join(' · ')}
          </span>
          <div style={{ marginTop: 6 }}>
            <FTitle name={recipe.name} size={30} color="#fff" italicColor="var(--olive)" breakBeforeWith={false} />
          </div>
        </div>
      </div>

      {/* Description (clamped) */}
      {recipe.description && (
        <div style={{ padding: '14px 18px 0' }}>
          <p style={{ fontFamily: 'Spectral', fontStyle: 'italic', fontSize: 15, lineHeight: 1.45,
            color: 'var(--ink-2)', margin: 0, display: '-webkit-box', WebkitLineClamp: 2,
            WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{recipe.description}</p>
        </div>
      )}

      <div style={{ padding: '14px 18px 0' }}><MetaStrip recipe={recipe} cols={4} compact /></div>

      <div style={{ padding: '14px 18px 0' }}><CostBar recipe={recipe} compact inlineRefresh /></div>

      <div style={{ padding: '14px 18px 0' }}>
        <FButton size="l" onClick={a.startCooking} style={{ width: '100%', minHeight: 54, fontSize: 17 }}>
          Start cooking <span style={{ fontSize: 18 }}>→</span>
        </FButton>
      </div>

      <div style={{ padding: '10px 18px 0' }}>
        <div style={{ position: 'relative' }}>
          <FButton size="m" variant="ghost" onClick={() => a.setPlanOpen((o) => !o)}
            style={a.plannedDay ? { width: '100%', color: 'var(--olive)', borderColor: 'rgba(240,168,80,0.5)',
              background: 'rgba(240,168,80,0.10)' } : { width: '100%' }}>
            {a.plannedDay ? <><Icon.check size={14} stroke={2.4} />{`Planned for ${a.plannedDay.slice(0, 3)}`}</> : <><Icon.bookmark size={14} />Add to plan</>}</FButton>
          {a.planOpen && <PlanPopover recipe={recipe} plannedDay={a.plannedDay} onClose={() => a.setPlanOpen(false)}
            onPlanned={() => {}} />}
        </div>
      </div>

      <div style={{ padding: '24px 18px 12px', display: 'flex', gap: 6 }}>
        {['Ingredients', 'Method', 'Notes'].map((t) => (
          <button key={t} onClick={() => setTab(t)} style={{ flex: 1, fontFamily: 'Bricolage Grotesque',
            fontSize: 13, fontWeight: 500, padding: '10px 12px', borderRadius: 999, textAlign: 'center',
            cursor: 'pointer',
            background: tab === t ? 'linear-gradient(180deg, rgba(255,255,255,0.10), rgba(255,255,255,0.02))' : 'transparent',
            border: tab === t ? '1px solid rgba(255,255,255,0.14)' : '1px solid var(--hairline)',
            color: tab === t ? 'var(--ink)' : 'var(--ink-3)' }}>{t}</button>
        ))}
      </div>

      <div style={{ padding: '12px 18px 36px' }}>
        {tab === 'Ingredients' && <FIngredients ingredients={recipe.ingredients} mode="view" />}
        {tab === 'Method' && <MethodList recipe={recipe} mini />}
        {tab === 'Notes' && <NotesCard recipe={recipe} />}
      </div>

      {a.toast && <Toast msg={a.toast.msg} action={a.toast.action}
        top="calc(var(--lc-mobile-top, 14px) + 14px)"
        onAction={() => { if (a.toast.enhanceRetry) enhancePhoto(recipe.id); else go(a.toast.go); a.setToast(null); }}
        onClose={() => a.setToast(null)} />}
    </div>
  );
}

function HeartIcon({ filled, size = 17 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill={filled ? 'currentColor' : 'none'}
      stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round">
      <path d="M20.8 4.6a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 10-7.78 7.78L12 21.2l8.84-8.83a5.5 5.5 0 000-7.78z" />
    </svg>
  );
}

function RoundBtn({ children, onClick, active }) {
  return (
    <button onClick={onClick} style={{ width: 38, height: 38, borderRadius: 999,
      background: 'rgba(12,12,14,0.6)', backdropFilter: 'blur(10px)',
      border: '1px solid rgba(255,255,255,0.16)', color: active ? 'var(--olive)' : 'var(--ink)',
      display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
      fontSize: 16 }}>{children}</button>
  );
}

function RecipeScreen() {
  const { layout, getRecipe, state } = useApp();
  const recipe = getRecipe(state.params.recipeId);
  if (!recipe) return <Shell tab={state.navTab}><div style={{ padding: 48 }}>Recipe not found.</div></Shell>;
  return (
    <Shell tab={state.navTab}>
      {layout === 'mobile' ? <RecipeMobile recipe={recipe} /> : <RecipeWide recipe={recipe} compact={layout === 'tablet'} />}
    </Shell>
  );
}

Object.assign(window, { RecipeScreen, Toast, RatingStars });
