import { RECIPES } from '../data/catalog.js?v=8'; import { MEAL_SLOTS } from '../planner/mealSlots.js'; function escapeHtml(s) { return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } const slotLabelMap = Object.fromEntries(MEAL_SLOTS.map((s) => [s.id, s.label])); const DEFAULT_MIN_MINUTES = 5; const DEFAULT_MAX_MINUTES = 120; const SEARCH_SHELL_BASE_SHADOW = '0 5px 10px rgba(0,0,0,0.16), 0 14px 22px rgba(0,0,0,0.24), 0 22px 34px rgba(0,0,0,0.18), inset 0 1px 0 rgba(255,255,255,0.04)'; function slotLabelsFor(recipe) { return (recipe.allowedSlots || []) .map((id) => slotLabelMap[id]) .filter(Boolean); } let filterState = { query: '', slots: [], tags: [], minMinutes: DEFAULT_MIN_MINUTES, maxMinutes: DEFAULT_MAX_MINUTES, }; function matchesFilters(recipe) { const { query, slots, tags, minMinutes, maxMinutes } = filterState; if (query) { const q = query.toLowerCase(); const haystack = `${recipe.title} ${(recipe.tags || []).join(' ')}`.toLowerCase(); if (!haystack.includes(q)) return false; } if (slots.length > 0) { if (!recipe.allowedSlots.some((s) => slots.includes(s))) return false; } if (tags.length > 0) { const recipeTags = (recipe.tags || []).map((t) => t.toLowerCase()); if (!tags.some((t) => recipeTags.includes(t.toLowerCase()))) return false; } if (minMinutes > DEFAULT_MIN_MINUTES && recipe.minutes < minMinutes) return false; if (maxMinutes < DEFAULT_MAX_MINUTES && recipe.minutes > maxMinutes) return false; return true; } function getFilteredRecipes() { return Object.values(RECIPES).filter(matchesFilters); } function renderRecipeCard(recipe) { const labels = slotLabelsFor(recipe); return `
${recipe.image ? `${escapeHtml(recipe.title)}` : `${escapeHtml(recipe.thumbLabel)}`}

${escapeHtml(recipe.title)}

${recipe.minutes} min
${recipe.nutritionPerServing.kcal} kcal
${labels.map((l) => `${escapeHtml(l)}`).join('')}
`; } function getEmptyStateHTML() { return ` `; } function syncRecipeScrollShadow() { const scroll = document.getElementById('recipe-scroll'); const searchShell = document.getElementById('recipe-search-shell'); if (!searchShell) return; if (!scroll) { searchShell.style.boxShadow = SEARCH_SHELL_BASE_SHADOW; return; } searchShell.style.boxShadow = SEARCH_SHELL_BASE_SHADOW; } function renderAllRecipeCards() { const grid = document.getElementById('recipe-grid'); if (!grid) return; grid.innerHTML = Object.values(RECIPES).map(renderRecipeCard).join(''); } function syncVisibleRecipeCards() { const grid = document.getElementById('recipe-grid'); const emptyState = document.getElementById('recipe-empty-state'); if (!grid || !emptyState) return; let visibleCount = 0; grid.querySelectorAll('[data-recipe-id]').forEach((card) => { const recipeId = card.getAttribute('data-recipe-id'); const recipe = recipeId ? RECIPES[recipeId] : null; const isVisible = Boolean(recipe && matchesFilters(recipe)); card.classList.toggle('hidden', !isVisible); if (isVisible) visibleCount += 1; }); grid.classList.toggle('hidden', visibleCount === 0); emptyState.classList.toggle('hidden', visibleCount !== 0); requestAnimationFrame(syncRecipeScrollShadow); } function renderGrid({ rebuild = false } = {}) { const grid = document.getElementById('recipe-grid'); if (!grid) return; if (rebuild || !grid.querySelector('[data-recipe-id]')) { renderAllRecipeCards(); } syncVisibleRecipeCards(); } export function getRecipeListHTML() { return `
${getEmptyStateHTML()}
`; } export function getFilterState() { return filterState; } export function applyFilters(newState) { Object.assign(filterState, newState); renderGrid(); } export function getFilteredCount() { return getFilteredRecipes().length; } export function refreshRecipeList() { renderGrid({ rebuild: true }); } export function setupRecipeList() { renderGrid({ rebuild: true }); document.getElementById('recipe-search-input')?.addEventListener('input', (e) => { filterState.query = e.target.value.trim(); renderGrid(); }); document.getElementById('recipe-scroll')?.addEventListener('scroll', syncRecipeScrollShadow); syncRecipeScrollShadow(); }