// app/screen-library.jsx — browse, search, filter, click into a recipe. // Responsive: laptop 4-col + feature · iPad 3-col + feature · iPhone 2-col + featured card. function useLibraryFilter() { const { allRecipes, state } = useApp(); const q = (state.search || '').trim().toLowerCase(); const f = state.filter || 'all'; const list = allRecipes.filter((r) => { if (f !== 'all') { if (!(r.meals || []).includes(f)) return false; } if (q) { const hay = (r.name + ' ' + (r.meals || []).join(' ') + ' ' + r.ingredients.join(' ')).toLowerCase(); if (!hay.includes(q)) return false; } return true; }); return window.LCDomain.sortRecipes(list, state.sort || 'recent'); } const LIB_CHIPS = ['all', 'breakfast', 'lunch', 'dinner', 'dessert', 'snack']; // Fixed sort options. Each key has ONE meaningful direction (no asc/desc toggle); // missing data always sinks to the bottom — see LCDomain.sortRecipes. const SORT_OPTIONS = [ { key: 'recent', label: 'Recently cooked', note: 'default order' }, { key: 'rating-desc', label: 'Rating', note: 'high to low' }, { key: 'cost-asc', label: 'Cost', note: 'low to high' }, { key: 'effort-asc', label: 'Effort', note: 'quick first' }, ]; function SortControl({ iconOnly }) { const { state, set } = useApp(); const [open, setOpen] = React.useState(false); const ref = React.useRef(null); React.useEffect(() => { if (!open) return; const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; const onKey = (e) => { if (e.key === 'Escape') setOpen(false); }; document.addEventListener('mousedown', onDown); document.addEventListener('keydown', onKey); return () => { document.removeEventListener('mousedown', onDown); document.removeEventListener('keydown', onKey); }; }, [open]); const cur = state.sort || 'recent'; const active = cur !== 'recent'; const curOpt = SORT_OPTIONS.find((o) => o.key === cur) || SORT_OPTIONS[0]; return (
{open && (
Sort by
{SORT_OPTIONS.map((o) => { const on = o.key === cur; return ( ); })}
)}
); } function ClickableCard({ r, size }) { const { openRecipe, photoFor } = useApp(); return (
openRecipe(r.id)} style={{ cursor: 'pointer' }}>
); } function SearchBar({ wide }) { const { state, set, allRecipes } = useApp(); return (
set({ search: e.target.value })} placeholder="search recipes, ingredients" style={{ border: 'none', background: 'transparent', outline: 'none', flex: 1, fontFamily: 'Bricolage Grotesque', fontSize: 15, color: 'var(--ink)' }} /> {state.search ? ( ) : ( {allRecipes.length} )}
); } function ChipRow({ scroll }) { const { state, set } = useApp(); return (
{LIB_CHIPS.map((c) => { const active = state.filter === c; return ( ); })}
); } function EmptyResults() { const { set } = useApp(); return (
Nothing matches that.
); } // ── Desktop / tablet ── function LibraryWide({ compact }) { const { allRecipes, openRecipe, photoFor, go, state } = useApp(); const list = useLibraryFilter(); const featured = allRecipes.find((r) => r.id === 'sichuan-chicken'); const cols = compact ? 3 : 4; const pad = compact ? '0 32px' : '0 48px'; const searching = state.search || state.filter !== 'all'; return (
{/* Title row */}
Your library · recently cooked

{allRecipes.length} recipes you cook.

go('edit', { isNew: true })}> New recipe
{/* Search + filters + sort */}
{/* Feature (only when not searching) */} {!searching && featured && (
openRecipe(featured.id)} style={{ cursor: 'pointer', padding: compact ? '36px 32px 32px' : '26px 48px 32px', display: 'grid', gridTemplateColumns: '0.95fr 1.05fr', gap: compact ? 40 : 56, alignItems: 'stretch' }}>
this week's pick

{featured.description}

{window.LCDomain.costPerServeLabel(featured) ? `${window.LCDomain.costPerServeLabel(featured)} /serve` : 'no cost yet'} · 35 min · serves 4 View →
)} {/* Grid header */}
{searching ? `${list.length} ${list.length === 1 ? 'match' : 'matches'}` : 'The full library'}
{/* Grid */}
{list.length === 0 ? : (
{list.map((r) => )}
)}
); } // ── Mobile ── function LibraryMobile() { const { allRecipes, openRecipe, photoFor, state } = useApp(); const list = useLibraryFilter(); const featured = allRecipes.find((r) => r.id === 'sichuan-chicken'); const searching = state.search || state.filter !== 'all'; return (
Your library

{allRecipes.length} recipes you cook.

{!searching && featured && (
openRecipe(featured.id)} style={{ position: 'relative', aspectRatio: '4 / 5', borderRadius: 18, overflow: 'hidden', cursor: 'pointer', boxShadow: '0 20px 50px rgba(0,0,0,0.5)' }}>
★ THIS WEEK
Mains · Asian · Quick

sichuan-style cold chicken with chilli oil.

{window.LCDomain.costPerServeLabel(featured) || '$4.20'} /serve · 35 min
)}
{searching ? `${list.length} ${list.length === 1 ? 'match' : 'matches'}` : 'Full library'}
{list.length === 0 ? : (
{list.map((r) => )}
)}
); } function LibraryScreen() { const { layout } = useApp(); return ( {layout === 'mobile' ? : } ); } Object.assign(window, { LibraryScreen });