From a60272653103fb175512c6339a7abc11d2e4ad02 Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Sun, 17 May 2026 09:16:48 +0000 Subject: [PATCH] feat: zero-waste tips during cooking mode (#76) --- CHANGELOG.md | 6 +++++ README.md | 2 +- api/index.php | 3 ++- assets/css/style.css | 58 ++++++++++++++++++++++++++++++++++++++++++++ assets/js/app.js | 40 ++++++++++++++++++++++++++++++ index.html | 23 ++++++++++++++++-- manifest.json | 2 +- translations/de.json | 9 ++++++- translations/en.json | 9 ++++++- translations/es.json | 9 ++++++- translations/fr.json | 9 ++++++- translations/it.json | 9 ++++++- 12 files changed, 169 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc1faba..b951d57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Recipe scraps tips** — During cooking steps, detect "waste" generated (peels, cores, bones, eggshells, coffee grounds, citrus zest, etc.) and surface AI-powered tips on how to reuse them (compost, natural cleaner, broth, candied peel, etc.). Could be shown as an optional collapsible hint card below the step that generates the scrap. +## [1.7.19] - 2026-05-19 + +### Added +- **Zero-waste tips during cooking** — When cooking mode is active, a ♻️ card appears below each step that generates reusable scraps (peels, cooking water, egg whites, cheese rinds, bread crusts, vegetable tops, etc.). Gemini generates the tips as part of the recipe JSON at no extra API cost. Tips are dismissible per-step and reset on recipe restart. Opt-in toggle in Settings → Zero-waste tips (default OFF). Resolves [#76](https://github.com/dadaloop82/EverShelf/issues/76). +- New translation keys `cooking.zerowaste_*` and `settings.zerowaste.*` in all 5 languages (IT, EN, DE, FR, ES). + ## [1.7.18] - 2026-05-19 ### Added diff --git a/README.md b/README.md index 7714238..915a0d1 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ [![SQLite](https://img.shields.io/badge/SQLite-3-blue.svg)](https://www.sqlite.org/) [![Docker](https://img.shields.io/badge/Docker-Ready-2496ED.svg)](Dockerfile) [![i18n](https://img.shields.io/badge/i18n-IT%20%7C%20EN%20%7C%20DE%20%7C%20FR%20%7C%20ES-orange.svg)](translations/) -[![Version](https://img.shields.io/badge/version-1.7.18-brightgreen.svg)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/version-1.7.19-brightgreen.svg)](CHANGELOG.md) [![GitHub stars](https://img.shields.io/github/stars/dadaloop82/EverShelf?style=social)](https://github.com/dadaloop82/EverShelf/stargazers) [![Last commit](https://img.shields.io/github/last-commit/dadaloop82/EverShelf/main)](https://github.com/dadaloop82/EverShelf/commits/main) [![Contributors](https://img.shields.io/github/contributors/dadaloop82/EverShelf)](https://github.com/dadaloop82/EverShelf/graphs/contributors) diff --git a/api/index.php b/api/index.php index 479b9c0..c71b758 100644 --- a/api/index.php +++ b/api/index.php @@ -4458,13 +4458,14 @@ REGOLE: 6. Includi nella lista ingredienti TUTTI quelli citati nei passi (tranne acqua/sale/pepe/olio). 7. Language rule: {$recipeLangName} only for all textual fields (`title`, `tags`, `expiry_note`, `ingredients.qty`, `steps`, `nutrition_note`, `tools_needed`). Keep `meal` unchanged. 8. `tools_needed`: array of kitchen tools/appliances actually required by this recipe (e.g. ["Forno","Frullatore"]). Use the same language as all other text fields. Empty array [] if only stovetop/knife/pan needed. +9. `zero_waste_tips`: array of zero-waste tips for steps that generate reusable scraps (peels, leftover cooking water, egg whites, cheese rinds, bread crusts, vegetable tops, etc.). Each entry: {"step": 0-based_step_index, "scrap": "scrap name", "tip": "short practical reuse tip (max 20 words)"}. Use the same language as other text fields. Empty array [] if no reusable scraps are generated. DISPENSA: $ingredientsText Rispondi SOLO JSON valido (no markdown): {$promptLanguageRule} -{"title":"…","meal":"$mealType","persons":$persons,"prep_time":"…","cook_time":"…","tags":["…"],"expiry_note":"…","tools_needed":["…"],"ingredients":[{"name":"…","qty":"200 g","qty_number":200,"from_pantry":true}],"steps":["{$promptStepExample}"],"nutrition_note":"…"} +{"title":"…","meal":"$mealType","persons":$persons,"prep_time":"…","cook_time":"…","tags":["…"],"expiry_note":"…","tools_needed":["…"],"ingredients":[{"name":"…","qty":"200 g","qty_number":200,"from_pantry":true}],"steps":["{$promptStepExample}"],"nutrition_note":"…","zero_waste_tips":[{"step":0,"scrap":"…","tip":"…"}]} PROMPT; $genConfig = [ diff --git a/assets/css/style.css b/assets/css/style.css index 6459c7b..8f6585b 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -6875,6 +6875,64 @@ body.cooking-mode-active .app-header { opacity: 0.8; } +/* ===== ZERO-WASTE TIP CARD (cooking mode) ===== */ +.cooking-zerowaste-tip { + display: flex; + align-items: flex-start; + gap: 10px; + background: rgba(16, 185, 129, 0.10); + border: 1.5px solid rgba(16, 185, 129, 0.35); + border-radius: 12px; + padding: 12px 14px; + margin: 10px 16px 0; + position: relative; + animation: zwFadeIn 0.3s ease; + flex-direction: column; +} +@keyframes zwFadeIn { + from { opacity: 0; transform: translateY(6px); } + to { opacity: 1; transform: translateY(0); } +} +.cooking-zerowaste-label { + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #059669; +} +.cooking-zerowaste-scrap { + font-size: 0.85rem; + font-weight: 700; + color: #065f46; + margin-left: 4px; +} +.cooking-zerowaste-text { + font-size: 0.85rem; + color: var(--text); + margin: 4px 0 0; + line-height: 1.45; +} +.cooking-zerowaste-close { + position: absolute; + top: 8px; + right: 10px; + background: none; + border: none; + font-size: 0.85rem; + cursor: pointer; + color: #6b7280; + padding: 2px 4px; + line-height: 1; +} +.cooking-zerowaste-close:hover { color: #374151; } +[data-theme="dark"] .cooking-zerowaste-tip { + background: rgba(16, 185, 129, 0.08); + border-color: rgba(16, 185, 129, 0.25); +} +[data-theme="dark"] .cooking-zerowaste-scrap { color: #6ee7b7; } +[data-theme="dark"] .cooking-zerowaste-label { color: #34d399; } +[data-theme="dark"] .cooking-zerowaste-close { color: #9ca3af; } + /* ===== DARK MODE ===== */ [data-theme="dark"] { --bg: #0f172a; diff --git a/assets/js/app.js b/assets/js/app.js index 663ff3b..48c0001 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -2538,6 +2538,9 @@ async function loadSettingsUI() { // Dark mode setting const dmEl = document.getElementById('setting-dark-mode'); if (dmEl) dmEl.value = s.dark_mode || 'auto'; + // Zero-waste tips setting + const zwEl = document.getElementById('setting-zerowaste-tips'); + if (zwEl) zwEl.checked = s.zerowaste_tips_enabled === true; // Populate About section version _loadAboutSection(); @@ -2864,6 +2867,9 @@ async function saveSettings() { // Dark mode const dmSaveEl = document.getElementById('setting-dark-mode'); if (dmSaveEl) { s.dark_mode = dmSaveEl.value; _applyTheme(); } + // Zero-waste tips + const zwSaveEl = document.getElementById('setting-zerowaste-tips'); + if (zwSaveEl) s.zerowaste_tips_enabled = zwSaveEl.checked; // Meal plan enabled toggle const mpEnabledEl = document.getElementById('setting-meal-plan-enabled'); if (mpEnabledEl) s.meal_plan_enabled = mpEnabledEl.checked; @@ -12429,6 +12435,7 @@ function startCookingMode() { _cookingRecipe = JSON.parse(JSON.stringify(recipe)); _cookingStep = 0; _cookingVisited = new Set(); + _dismissedZeroWasteTips = new Set(); clearAllCookingTimers(); } _cookingTTS = true; @@ -12478,6 +12485,7 @@ function restartCookingMode() { _cookingStep = 0; _cookingWheelLastDelta = 0; _cookingVisited = new Set(); + _dismissedZeroWasteTips = new Set(); clearAllCookingTimers(); renderCookingStep(); } @@ -12699,10 +12707,42 @@ function renderCookingStep() { // Timer: detect duration in step text and show suggestion setupCookingTimerSuggestion(cleanStep); + // Zero-waste tip for this step + _renderZeroWasteTip(_cookingStep); + // TTS: auto-speak is handled by navigateCookingStep() and startCookingMode() callers. // Use replayCookingTTS() to re-read the current step manually ("Rileggi" button). } +// ===== ZERO-WASTE TIPS ===== +let _dismissedZeroWasteTips = new Set(); // dismissed tip indices for this cooking session + +function _renderZeroWasteTip(stepIdx) { + const tipEl = document.getElementById('cooking-zerowaste-tip'); + if (!tipEl) return; + // Check setting + const s = getSettings(); + if (!s.zerowaste_tips_enabled) { tipEl.style.display = 'none'; return; } + // Already dismissed for this step in this session + if (_dismissedZeroWasteTips.has(stepIdx)) { tipEl.style.display = 'none'; return; } + // Find tip for current step + const tips = (_cookingRecipe && _cookingRecipe.zero_waste_tips) || []; + const tip = tips.find(t => t.step === stepIdx); + if (!tip) { tipEl.style.display = 'none'; return; } + // Populate and show + const scrapEl = document.getElementById('cooking-zerowaste-scrap'); + const textEl = document.getElementById('cooking-zerowaste-text'); + if (scrapEl) scrapEl.textContent = tip.scrap || ''; + if (textEl) textEl.textContent = tip.tip || ''; + tipEl.style.display = 'flex'; +} + +function _dismissZeroWasteTip() { + _dismissedZeroWasteTips.add(_cookingStep); + const tipEl = document.getElementById('cooking-zerowaste-tip'); + if (tipEl) tipEl.style.display = 'none'; +} + function _buildTtsRequest(text, s) { const url = s.tts_url || ''; const method = s.tts_method || 'POST'; diff --git a/index.html b/index.html index 6b4792f..4fbacb9 100644 --- a/index.html +++ b/index.html @@ -11,7 +11,7 @@ EverShelf - + @@ -1300,6 +1300,19 @@ +
+

♻️ Suggerimenti zero-waste

+

Durante la cottura, mostra consigli su come riutilizzare gli scarti prodotti in ogni passo (bucce, acqua di cottura, ecc.). Disattivo per impostazione predefinita.

+
+ +
+

📤 Esporta inventario

Scarica l'inventario corrente in CSV o apri una versione stampabile (PDF).

@@ -1578,6 +1591,12 @@
+
@@ -1585,6 +1604,6 @@
- + diff --git a/manifest.json b/manifest.json index 900e6bd..37047a0 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "name": "EverShelf", "short_name": "EverShelf", "description": "Gestione completa della dispensa di casa con scansione barcode", - "version": "1.7.18", + "version": "1.7.19", "start_url": "/evershelf/", "display": "standalone", "background_color": "#f0f4e8", diff --git a/translations/de.json b/translations/de.json index 6406ea6..4c263ee 100644 --- a/translations/de.json +++ b/translations/de.json @@ -540,7 +540,9 @@ "recipe_done_tts": "Rezept abgeschlossen! Guten Appetit!", "expires_chip": "läuft ab {date}", "finish": "✅ Fertig", - "step_fallback": "Schritt {n}" + "step_fallback": "Schritt {n}", + "zerowaste_label": "♻️ Abfall", + "zerowaste_tip_title": "Zero-Waste-Tipp" }, "settings": { "title": "⚙️ Einstellungen", @@ -752,6 +754,11 @@ "off": "☀️ Hell", "on": "🌙 Dunkel", "auto": "🔄 Automatisch (System)" + }, + "zerowaste": { + "card_title": "♻️ Zero-Waste-Tipps", + "card_hint": "Zeige während des Kochens Tipps zur Wiederverwendung von Abfällen (Schalen, Kochwasser usw.). Standardmäßig deaktiviert.", + "label": "Tipps beim Kochen anzeigen" } }, "expiry": { diff --git a/translations/en.json b/translations/en.json index 4c841e3..22fa8c3 100644 --- a/translations/en.json +++ b/translations/en.json @@ -540,7 +540,9 @@ "recipe_done_tts": "Recipe complete! Enjoy your meal!", "expires_chip": "exp. {date}", "finish": "✅ Finish", - "step_fallback": "Step {n}" + "step_fallback": "Step {n}", + "zerowaste_label": "♻️ Scrap", + "zerowaste_tip_title": "Zero-waste tip" }, "settings": { "title": "⚙️ Settings", @@ -752,6 +754,11 @@ "off": "☀️ Light", "on": "🌙 Dark", "auto": "🔄 Auto (system)" + }, + "zerowaste": { + "card_title": "♻️ Zero-waste tips", + "card_hint": "During cooking, show tips on how to reuse scraps generated in each step (peels, cooking water, etc.). Disabled by default.", + "label": "Show tips during cooking" } }, "expiry": { diff --git a/translations/es.json b/translations/es.json index 346ce54..962c1f6 100644 --- a/translations/es.json +++ b/translations/es.json @@ -540,7 +540,9 @@ "recipe_done_tts": "¡Receta completada! ¡Buen provecho!", "expires_chip": "cad. {date}", "finish": "✅ Finalizar", - "step_fallback": "Paso {n}" + "step_fallback": "Paso {n}", + "zerowaste_label": "♻️ Desperdicio", + "zerowaste_tip_title": "Consejo sin desperdicios" }, "settings": { "title": "⚙️ Ajustes", @@ -752,6 +754,11 @@ "off": "☀️ Claro", "on": "🌙 Oscuro", "auto": "🔄 Automático (sistema)" + }, + "zerowaste": { + "card_title": "♻️ Consejos sin desperdicios", + "card_hint": "Durante la cocción, muestra consejos sobre cómo reutilizar los restos generados en cada paso (peladuras, agua de cocción, etc.). Desactivado por defecto.", + "label": "Mostrar consejos durante la cocción" } }, "expiry": { diff --git a/translations/fr.json b/translations/fr.json index c8fcf07..e663fe5 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -540,7 +540,9 @@ "recipe_done_tts": "Recette terminée ! Bon appétit !", "expires_chip": "exp. {date}", "finish": "✅ Terminer", - "step_fallback": "Étape {n}" + "step_fallback": "Étape {n}", + "zerowaste_label": "♻️ Déchet", + "zerowaste_tip_title": "Conseil zéro déchet" }, "settings": { "title": "⚙️ Paramètres", @@ -752,6 +754,11 @@ "off": "☀️ Clair", "on": "🌙 Sombre", "auto": "🔄 Automatique (système)" + }, + "zerowaste": { + "card_title": "♻️ Conseils zéro déchet", + "card_hint": "Pendant la cuisson, affichez des conseils pour réutiliser les déchets produits à chaque étape (épluchures, eau de cuisson, etc.). Désactivé par défaut.", + "label": "Afficher les conseils pendant la cuisson" } }, "expiry": { diff --git a/translations/it.json b/translations/it.json index b69ab60..2af20a9 100644 --- a/translations/it.json +++ b/translations/it.json @@ -540,7 +540,9 @@ "recipe_done_tts": "Ricetta completata! Buon appetito!", "expires_chip": "scade {date}", "finish": "✅ Fine", - "step_fallback": "Passo {n}" + "step_fallback": "Passo {n}", + "zerowaste_label": "♻️ Scarto", + "zerowaste_tip_title": "Consiglio anti-spreco" }, "settings": { "title": "⚙️ Configurazione", @@ -752,6 +754,11 @@ "off": "☀️ Chiaro", "on": "🌙 Scuro", "auto": "🔄 Automatico (sistema)" + }, + "zerowaste": { + "card_title": "♻️ Suggerimenti zero-waste", + "card_hint": "Durante la cottura, mostra consigli su come riutilizzare gli scarti prodotti in ogni passo (bucce, acqua di cottura, ecc.). Disattivo per impostazione predefinita.", + "label": "Mostra suggerimenti durante la cottura" } }, "expiry": {