diff --git a/app/static/cookbook.css b/app/static/cookbook.css index ecbc44c..9654a6e 100755 --- a/app/static/cookbook.css +++ b/app/static/cookbook.css @@ -633,11 +633,41 @@ margin: 0 0 12px 0; display: flex; align-items: center; + justify-content: space-between; gap: 8px; border-bottom: 1.5px solid var(--altrosa); padding-bottom: 4px; } +.cb-reset-checks-btn { + display: inline-flex; + align-items: center; + gap: 4px; + background: rgba(30, 77, 59, 0.05); + border: 1px dashed rgba(30, 77, 59, 0.35); + color: var(--tannengruen); + font-family: var(--font-hand); + font-size: 0.92rem; + font-weight: 600; + padding: 2px 9px; + border-radius: 12px; + cursor: pointer; + transition: all 0.2s ease; + line-height: 1; + user-select: none; + -webkit-tap-highlight-color: transparent; +} + +.cb-reset-checks-btn:hover { + background: rgba(30, 77, 59, 0.12); + border-color: var(--tannengruen); + border-style: solid; +} + +.cb-reset-checks-btn:active { + transform: scale(0.96); +} + .ingredients-list { list-style: none; padding: 0; @@ -2572,6 +2602,21 @@ body.cook-mode-on .cookmode-text-box .cookmode-status-text { background: rgba(34, 34, 34, 0.04); } +.matrix-row { + cursor: pointer; + transition: background 0.15s ease; +} + +.matrix-row:hover { + background: rgba(30, 77, 59, 0.05); +} + +.matrix-row.is-checked td { + text-decoration: line-through; + opacity: 0.45; + color: #777; +} + /* Interactive step completion in cook mode */ .step-card { cursor: pointer; @@ -2740,6 +2785,7 @@ body.cook-mode-on .cookmode-text-box .cookmode-status-text { .cookmode-toggle-btn, .cb-nav-cookmode-btn, .cb-mobile-cookmode-item, + .cb-reset-checks-btn, .recipe-cookmode-banner, .step-done-check, .no-print { diff --git a/app/templates/recipe_detail.html b/app/templates/recipe_detail.html index 90929ba..7b9d652 100755 --- a/app/templates/recipe_detail.html +++ b/app/templates/recipe_detail.html @@ -60,6 +60,9 @@ Aus +
{% if not is_shared %} + {% if recipe.has_portion_table and recipe.portion_matrix %} @@ -210,7 +218,7 @@ {{ row.text }} {% else %} - + {{ row.name }} {{ row.amounts[2] }} {{ row.amounts[4] }} @@ -242,7 +250,12 @@
-

Zubereitung

+

+ Zubereitung + +

{% for step in recipe.instructions.split('\n') %} {% if step.strip() %} @@ -717,6 +730,7 @@ // Gespeicherten Zustand beim Laden prüfen window.addEventListener('DOMContentLoaded', () => { + loadChecksFromStorage(); try { const saved = localStorage.getItem('wtc_cookmode_active') === 'true'; if (saved) { @@ -735,14 +749,142 @@ } catch (e) {} }); + // ========================================== + // INTERAKTIVES ABHAKEN (ZUTATEN & SCHRITTE) MIT ZWISCHENSPEICHER + // ========================================== + const RECIPE_CHECK_STORAGE_KEY = 'wtc_recipe_checks_{{ recipe.id }}'; + + function getCheckableIngredients() { + return Array.from(document.querySelectorAll('.ingredients-list .ingredient-item, .portion-matrix-table .matrix-row')); + } + + function getCheckableSteps() { + return Array.from(document.querySelectorAll('.instructions-steps .step-card')); + } + + function updateResetButtonsVisibility(hasIngChecks, hasStepChecks) { + const ingResetBtn = document.getElementById('reset-ingredients-btn'); + if (ingResetBtn) ingResetBtn.style.display = hasIngChecks ? 'inline-flex' : 'none'; + + const stepResetBtn = document.getElementById('reset-steps-btn'); + if (stepResetBtn) stepResetBtn.style.display = hasStepChecks ? 'inline-flex' : 'none'; + + const menuResetBtn = document.getElementById('reset-all-menu-btn'); + if (menuResetBtn) menuResetBtn.style.display = (hasIngChecks || hasStepChecks) ? 'flex' : 'none'; + } + + function saveChecksToStorage() { + try { + const ingEls = getCheckableIngredients(); + const stepEls = getCheckableSteps(); + + const checkedIngredients = []; + ingEls.forEach((el, idx) => { + if (el.classList.contains('is-checked')) checkedIngredients.push(idx); + }); + + const checkedSteps = []; + stepEls.forEach((el, idx) => { + if (el.classList.contains('is-done')) checkedSteps.push(idx); + }); + + const data = { + recipeId: {{ recipe.id }}, + ingredients: checkedIngredients, + steps: checkedSteps, + updatedAt: Date.now() + }; + + const json = JSON.stringify(data); + sessionStorage.setItem(RECIPE_CHECK_STORAGE_KEY, json); + localStorage.setItem(RECIPE_CHECK_STORAGE_KEY, json); + + updateResetButtonsVisibility(checkedIngredients.length > 0, checkedSteps.length > 0); + } catch (e) { + console.warn('Fehler beim Speichern des Abhake-Status:', e); + } + } + + function loadChecksFromStorage() { + try { + let raw = sessionStorage.getItem(RECIPE_CHECK_STORAGE_KEY); + if (!raw) { + raw = localStorage.getItem(RECIPE_CHECK_STORAGE_KEY); + } + if (!raw) { + updateResetButtonsVisibility(false, false); + return; + } + + const data = JSON.parse(raw); + // Nach 8 Stunden verfällt der Zwischenspeicher automatisch + const EIGHT_HOURS = 8 * 60 * 60 * 1000; + if (data.updatedAt && (Date.now() - data.updatedAt > EIGHT_HOURS)) { + sessionStorage.removeItem(RECIPE_CHECK_STORAGE_KEY); + localStorage.removeItem(RECIPE_CHECK_STORAGE_KEY); + updateResetButtonsVisibility(false, false); + return; + } + + const ingEls = getCheckableIngredients(); + if (Array.isArray(data.ingredients)) { + data.ingredients.forEach(idx => { + if (ingEls[idx]) ingEls[idx].classList.add('is-checked'); + }); + } + + const stepEls = getCheckableSteps(); + if (Array.isArray(data.steps)) { + data.steps.forEach(idx => { + if (stepEls[idx]) stepEls[idx].classList.add('is-done'); + }); + } + + const hasIngChecks = Array.isArray(data.ingredients) && data.ingredients.length > 0; + const hasStepChecks = Array.isArray(data.steps) && data.steps.length > 0; + updateResetButtonsVisibility(hasIngChecks, hasStepChecks); + } catch (e) { + console.warn('Fehler beim Laden des Abhake-Status:', e); + } + } + // Interaktive Zutaten-Checkliste (zum Abhaken beim Vorbereiten) function toggleIngredientCheck(el) { el.classList.toggle('is-checked'); + saveChecksToStorage(); } // Interaktiver Schritt-Tracker (zum Markieren erledigter Schritte) function toggleStepCheck(el) { el.classList.toggle('is-done'); + saveChecksToStorage(); + } + + function resetIngredientChecks() { + const ingEls = getCheckableIngredients(); + ingEls.forEach(el => el.classList.remove('is-checked')); + saveChecksToStorage(); + showToast('Zutaten zurückgesetzt'); + } + + function resetStepChecks() { + const stepEls = getCheckableSteps(); + stepEls.forEach(el => el.classList.remove('is-done')); + saveChecksToStorage(); + showToast('Schritte zurückgesetzt'); + } + + function resetAllChecks() { + const ingEls = getCheckableIngredients(); + ingEls.forEach(el => el.classList.remove('is-checked')); + const stepEls = getCheckableSteps(); + stepEls.forEach(el => el.classList.remove('is-done')); + try { + sessionStorage.removeItem(RECIPE_CHECK_STORAGE_KEY); + localStorage.removeItem(RECIPE_CHECK_STORAGE_KEY); + } catch (e) {} + updateResetButtonsVisibility(false, false); + showToast('Alles zurückgesetzt'); } // Toast Benachrichtigung