// app/screen-cook.jsx — hands-busy cook mode. One step at a time, ingredient
// check-off, and count-up (stopwatch) timers that tick gently upward.
// match the current step to an ingredient group by keyword overlap
function activeGroupForStep(groups, stepText) {
if (!groups.length) return null;
const t = (stepText || '').toLowerCase();
let best = null, bestScore = 0;
groups.forEach((g) => {
if (!g.title) return;
const words = g.title.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
const score = words.reduce((s, w) => s + (t.includes(w) ? 1 : 0), 0);
if (score > bestScore) { bestScore = score; best = g; }
});
return bestScore > 0 ? best : null;
}
function CookCheckRow({ id, line, highlight, scale }) {
const { checkedFor, toggleChecked, scaleQuantities } = useApp();
const checked = checkedFor(id).has(line);
const display = scale && scale !== 1 ? scaleQuantities(line, scale) : line;
return (
);
}
function CookIngredients({ recipe, activeGroup, dense, scale }) {
const { parseIngredientGroups } = useApp();
const groups = parseIngredientGroups(recipe.ingredients);
if (groups.length <= 1 && !groups[0]?.title) {
return (
{(groups[0]?.items || []).map((l, i) => )}
);
}
return (
{groups.map((g, gi) => {
const isActive = activeGroup && g.title === activeGroup.title;
return (
{g.title && (
{g.title}
{isActive && now}
)}
{g.items.map((l, i) => )}
);
})}
);
}
// ── Timers ──
function TimerChip({ t }) {
const { toggleTimer, removeTimer } = useApp();
const now = useNow(t.running);
const secs = timerElapsed(t, now);
return (
{fmtTime(secs)}
{t.label}
);
}
function StepNumeral({ n, size, stroke }) {
return (
{String(n).padStart(2, '0')}
);
}
function CookMode({ recipe }) {
const { layout, back, openRecipe, cookStep, setCookStep, state, addTimer, parseIngredientGroups, settings } = useApp();
const total = recipe.directions.length;
const step = Math.min(cookStep(recipe.id), Math.max(0, total - 1));
const groups = parseIngredientGroups(recipe.ingredients);
const activeGroup = activeGroupForStep(groups, recipe.directions[step]);
const isMobile = layout === 'mobile';
const isTablet = layout === 'tablet';
const goPrev = () => setCookStep(recipe.id, Math.max(0, step - 1));
const goNext = () => {
if (step >= total - 1) { setCookStep(recipe.id, 0); openRecipe(recipe.id); }
else setCookStep(recipe.id, step + 1);
};
const last = step >= total - 1;
// ── servings scaler — scales displayed quantities + the cost figure live ──
// baseServings is the recipe's NATIVE serving count (the scale reference — the
// listed quantities are for this many). The initial "cook for" count follows
// the user's intent: a planned meal → its plannedServings; an unplanned cook →
// the user's default planning preference; falling back to the recipe's own.
const baseServings = window.LCDomain.costInfo(recipe).servingsCount || 4;
const plannedEntry = React.useMemo(() => Object.entries(state.plan || {})
.filter(([, e]) => e && !e.skip && e.recipeId === recipe.id)
.sort((a, b) => (a[0] < b[0] ? -1 : 1))
.map(([, e]) => e)[0] || null, [state.plan, recipe.id]);
const initialServings = (plannedEntry && plannedEntry.plannedServings)
|| (settings && settings.defaultPlanningServings) || baseServings;
const [servings, setServings] = React.useState(initialServings);
const scale = baseServings ? servings / baseServings : 1;
const TopDock = (
{recipe.name.split(' with ')[0]}
Cook · {step + 1}/{total}
{!isMobile && (
{Array.from({ length: total }).map((_, i) => (
))}
)}
{!isMobile && }
{state.timers.map((t) => )}
);
// mobile progress pips under dock
const MobilePips = (
{Array.from({ length: total }).map((_, i) => (
))}
);
const StepBlock = (
Method · step {step + 1}{activeGroup ? ` · ${activeGroup.title}` : ''}
{recipe.directions[step]}
);
const FooterBtns = (
←{isMobile ? '' : ' Previous step'}
{last ? 'Finish cooking' : 'Done — next step'} →
);
const IngredientPanel = (
<>
{activeGroup ? 'For this step' : 'Ingredients'}
{activeGroup && (
{activeGroup.title}.
)}
Tap items to check them off as you measure.
>
);
if (isMobile) {
return (
{TopDock}
{MobilePips}
{FooterBtns}
);
}
return (
{TopDock}
);
}
function CookScreen() {
const { getRecipe, state } = useApp();
const recipe = getRecipe(state.params.recipeId);
if (!recipe) return Recipe not found.
;
return ;
}
Object.assign(window, { CookScreen });