From cbb7d53e787cc712ba72228200c15d5b0dc4fbfd Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Fri, 13 Mar 2026 07:16:55 +0000 Subject: [PATCH] =?UTF-8?q?Screensaver:=20orologio=20pi=C3=B9=20grande,=20?= =?UTF-8?q?frasi=20dinamiche=20fade-in/out,=20font=20fact=20ingrandito?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/css/style.css | 89 ++++++++ assets/js/app.js | 491 ++++++++++++++++++++++++++++++++++++++++++- data/dispensa.db | Bin 126976 -> 126976 bytes index.html | 16 +- 4 files changed, 584 insertions(+), 12 deletions(-) diff --git a/assets/css/style.css b/assets/css/style.css index f3f9f27..27ae0ca 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -2620,6 +2620,38 @@ body { accent-color: var(--primary); } +/* Meal type selector */ +.recipe-meal-grid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: 6px; + margin-top: 4px; +} +.recipe-meal-chip { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + padding: 10px 6px; + background: var(--bg); + border-radius: var(--radius-sm); + font-size: 0.8rem; + font-weight: 500; + cursor: pointer; + transition: background 0.2s, border-color 0.2s; + border: 2px solid transparent; + text-align: center; + white-space: nowrap; +} +.recipe-meal-chip:has(input:checked) { + background: rgba(45, 80, 22, 0.1); + border-color: var(--primary); + font-weight: 600; +} +.recipe-meal-chip input[type="radio"] { + display: none; +} + /* ===== LARGER PRODUCT PREVIEW (Action page) ===== */ .product-preview-large { flex-direction: column; @@ -3426,3 +3458,60 @@ body { color: var(--text-muted); flex-wrap: wrap; } + +/* ===== SCREENSAVER ===== */ +.screensaver-overlay { + position: fixed; + inset: 0; + background: #000; + z-index: 10000; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + transition: opacity 0.8s ease; +} +.screensaver-overlay.visible { + opacity: 1; +} +.screensaver-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 32px; + padding: 20px; + max-width: 90vw; +} +.screensaver-clock { + color: rgba(255,255,255,0.85); + font-size: 7rem; + font-weight: 200; + font-variant-numeric: tabular-nums; + letter-spacing: 4px; + text-align: center; + line-height: 1.2; + user-select: none; +} +.screensaver-clock .screensaver-date { + font-size: 1.4rem; + font-weight: 300; + opacity: 0.45; + letter-spacing: 1px; + margin-top: 4px; +} +.screensaver-fact { + color: rgba(255,255,255,0.55); + font-size: 1.6rem; + font-weight: 300; + text-align: center; + line-height: 1.5; + max-width: 500px; + min-height: 2.5em; + opacity: 0; + transition: opacity 1.5s ease; + user-select: none; +} +.screensaver-fact.visible { + opacity: 1; +} diff --git a/assets/js/app.js b/assets/js/app.js index b25ab45..7ae41ff 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -4085,18 +4085,31 @@ async function loadLog(more = false) { } // ===== RECIPE GENERATION ===== +const MEAL_TYPES = [ + { id: 'colazione', icon: '☀️', label: 'Colazione', from: 6, to: 9 }, + { id: 'break', icon: '🥐', label: 'Break Mattutino', from: 9, to: 11 }, + { id: 'pranzo', icon: '🍽️', label: 'Pranzo', from: 11, to: 14 }, + { id: 'merenda', icon: '🍪', label: 'Merenda', from: 14, to: 17 }, + { id: 'cena', icon: '🌙', label: 'Cena', from: 17, to: 22 }, + { id: 'spuntino', icon: '🌜', label: 'Spuntino Serale', from: 22, to: 6 }, +]; + function getMealType() { const hour = new Date().getHours(); - if (hour >= 5 && hour < 11) return 'colazione'; - if (hour >= 11 && hour < 16) return 'pranzo'; + for (const m of MEAL_TYPES) { + if (m.from < m.to) { if (hour >= m.from && hour < m.to) return m.id; } + else { if (hour >= m.from || hour < m.to) return m.id; } + } return 'cena'; } -const MEAL_LABELS = { - 'colazione': '☀️ Colazione', - 'pranzo': '🍽️ Pranzo', - 'cena': '🌙 Cena' -}; +const MEAL_LABELS = {}; +MEAL_TYPES.forEach(m => { MEAL_LABELS[m.id] = `${m.icon} ${m.label}`; }); + +function getSelectedMealType() { + const checked = document.querySelector('input[name="recipe-meal"]:checked'); + return checked ? checked.value : getMealType(); +} // ===== RECIPE ARCHIVE (DB-backed) ===== let _recipeArchiveCache = null; @@ -4203,9 +4216,18 @@ let _cachedRecipe = null; function openRecipeDialog() { const meal = getMealType(); const settings = getSettings(); - document.getElementById('recipe-meal-title').textContent = MEAL_LABELS[meal] || '🍳 Ricetta'; document.getElementById('recipe-overlay').style.display = 'flex'; + // Build meal selector radios + const mealGrid = document.getElementById('recipe-meal-grid'); + if (mealGrid) { + mealGrid.innerHTML = MEAL_TYPES.map(m => { + const checked = m.id === meal ? ' checked' : ''; + return ``; + }).join(''); + } + updateRecipeMealTitle(); + // Check for cached recipe matching current meal type if (_cachedRecipe && _cachedRecipe.meal === meal && _cachedRecipe.recipe) { document.getElementById('recipe-ask').style.display = 'none'; @@ -4367,18 +4389,31 @@ function renderRecipe(r) { document.getElementById('recipe-content').innerHTML = html; } +function updateRecipeMealTitle() { + const meal = getSelectedMealType(); + document.getElementById('recipe-meal-title').textContent = MEAL_LABELS[meal] || '🍳 Ricetta'; +} + function regenerateRecipe() { _cachedRecipe = null; document.getElementById('recipe-result').style.display = 'none'; document.getElementById('recipe-loading').style.display = 'none'; const meal = getMealType(); - document.getElementById('recipe-meal-title').textContent = MEAL_LABELS[meal] || '🍳 Ricetta'; + // Rebuild meal selector with auto-detected default + const mealGrid = document.getElementById('recipe-meal-grid'); + if (mealGrid) { + mealGrid.innerHTML = MEAL_TYPES.map(m => { + const checked = m.id === meal ? ' checked' : ''; + return ``; + }).join(''); + } + updateRecipeMealTitle(); document.getElementById('recipe-persons').value = 1; document.getElementById('recipe-ask').style.display = ''; } async function generateRecipe() { - const meal = getMealType(); + const meal = getSelectedMealType(); const persons = parseInt(document.getElementById('recipe-persons').value) || 1; const settings = getSettings(); @@ -4614,10 +4649,446 @@ function saveChatHistory() { api('chat_save', {}, 'POST', { messages: newMsgs }).catch(() => {}); } +// ===== SCREENSAVER & INACTIVITY AUTO-REFRESH ===== +let _inactivityTimer = null; +let _screensaverActive = false; +let _screensaverClockInterval = null; +let _screensaverFactInterval = null; +let _screensaverData = null; // cached data for fact generation +const SCREENSAVER_FACT_DURATION = 5 * 60 * 1000; // 5 minutes per fact +const INACTIVITY_TIMEOUT = 5 * 60 * 1000; // 5 minutes + +function resetInactivityTimer() { + if (_screensaverActive) return; // don't reset while screensaver is showing + clearTimeout(_inactivityTimer); + _inactivityTimer = setTimeout(activateScreensaver, INACTIVITY_TIMEOUT); +} + +function activateScreensaver() { + if (_screensaverActive) return; + _screensaverActive = true; + const overlay = document.getElementById('screensaver'); + overlay.style.display = 'flex'; + // Fade in + requestAnimationFrame(() => overlay.classList.add('visible')); + updateScreensaverClock(); + _screensaverClockInterval = setInterval(updateScreensaverClock, 1000); + // Load data and start facts + loadScreensaverData().then(() => { + showNextScreensaverFact(); + _screensaverFactInterval = setInterval(showNextScreensaverFact, SCREENSAVER_FACT_DURATION); + }); +} + +function updateScreensaverClock() { + const now = new Date(); + const time = now.toLocaleTimeString('it-IT', { hour: '2-digit', minute: '2-digit' }); + const date = now.toLocaleDateString('it-IT', { weekday: 'long', day: 'numeric', month: 'long' }); + const el = document.getElementById('screensaver-clock'); + if (el) el.innerHTML = `${time}
${date}
`; +} + +function dismissScreensaver() { + if (!_screensaverActive) return; + clearInterval(_screensaverClockInterval); + clearInterval(_screensaverFactInterval); + const overlay = document.getElementById('screensaver'); + overlay.classList.remove('visible'); + setTimeout(() => { + overlay.style.display = 'none'; + _screensaverActive = false; + _screensaverData = null; + // Reload all data for the current page + refreshCurrentPage(); + resetInactivityTimer(); + }, 400); +} + +// Load all data needed for screensaver facts +async function loadScreensaverData() { + try { + const [statsRes, invRes, bringRes] = await Promise.all([ + api('stats'), + api('inventory_list'), + api('bring_list').catch(() => null) + ]); + _screensaverData = { + stats: statsRes, + inventory: invRes.inventory || [], + shopping: bringRes && bringRes.success ? (bringRes.purchase || []) : [] + }; + } catch (e) { + _screensaverData = { stats: {}, inventory: [], shopping: [] }; + } +} + +// Show next random fact with fade in/out +function showNextScreensaverFact() { + const el = document.getElementById('screensaver-fact'); + if (!el) return; + el.classList.remove('visible'); + setTimeout(() => { + el.textContent = generateScreensaverFact(); + el.classList.add('visible'); + }, 1600); +} + +// Generate a dynamic fact from available data +function generateScreensaverFact() { + const d = _screensaverData || { stats: {}, inventory: [], shopping: [] }; + const inv = d.inventory; + const stats = d.stats; + const shop = d.shopping; + const now = new Date(); + const hour = now.getHours(); + + // Pre-compute useful data + const expired = stats.expired || []; + const expiringSoon = stats.expiring_soon || []; + const totalProducts = stats.total_products || inv.length; + const totalItems = stats.total_items || 0; + + const byLocation = {}; + const byCategory = {}; + const withExpiry = []; + const noExpiry = []; + const expiringThisWeek = []; + const expiringThisMonth = []; + const inFreezer = []; + const inFrigo = []; + const inDispensa = []; + + for (const item of inv) { + // by location + const loc = item.location || 'altro'; + if (!byLocation[loc]) byLocation[loc] = []; + byLocation[loc].push(item); + if (loc === 'freezer') inFreezer.push(item); + else if (loc === 'frigo') inFrigo.push(item); + else if (loc === 'dispensa') inDispensa.push(item); + + // by category + const cat = mapToLocalCategory(item.category, item.name); + if (!byCategory[cat]) byCategory[cat] = []; + byCategory[cat].push(item); + + // expiry + if (item.expiry_date) { + withExpiry.push(item); + const days = daysUntilExpiry(item.expiry_date); + if (days >= 0 && days <= 7) expiringThisWeek.push(item); + if (days >= 0 && days <= 30) expiringThisMonth.push(item); + } else { + noExpiry.push(item); + } + } + + // Greeting based on time + const greeting = hour < 12 ? 'Buongiorno' : hour < 18 ? 'Buon pomeriggio' : 'Buonasera'; + + // Estimated shopping total + let spesaTotal = 0; + let spesaPriced = 0; + for (const item of shop) { + const pd = shoppingPrices[item.name.toLowerCase()]; + if (pd && pd.product) { + const est = estimateItemPrice(pd.product, item.specification || pd.spec || ''); + spesaTotal += est ? est.estimated : pd.product.price; + spesaPriced++; + } + } + + // Random item picker + const rItem = (arr) => arr.length ? arr[Math.floor(Math.random() * arr.length)] : null; + + // All fact generators + const facts = []; + + // --- Expired items facts --- + if (expired.length > 0) { + facts.push(() => `Hai ${expired.length} ${expired.length === 1 ? 'prodotto scaduto' : 'prodotti scaduti'} in dispensa. Controlla!`); + facts.push(() => { + const names = expired.slice(0, 3).map(i => i.name); + return `Prodotti scaduti: ${names.join(', ')}${expired.length > 3 ? ` e altri ${expired.length - 3}` : ''}`; + }); + const freezerExpired = expired.filter(i => i.location === 'freezer'); + if (freezerExpired.length > 0) { + facts.push(() => { + const item = rItem(freezerExpired); + const safety = getExpiredSafety(item, Math.abs(daysUntilExpiry(item.expiry_date))); + if (safety.level === 'ok' || safety.level === 'warning') { + return `${item.name} è scaduto, ma essendo in freezer potrebbe essere ancora buono! Controlla.`; + } + return `${item.name} in freezer è scaduto da troppo tempo. Meglio buttarlo.`; + }); + } + const frigoExpired = expired.filter(i => i.location === 'frigo'); + if (frigoExpired.length > 0) { + facts.push(() => `Hai ${frigoExpired.length} ${frigoExpired.length === 1 ? 'prodotto scaduto' : 'prodotti scaduti'} in frigo!`); + } + } + + // --- Expiring soon facts --- + if (expiringSoon.length > 0) { + facts.push(() => { + const item = expiringSoon[0]; + const days = daysUntilExpiry(item.expiry_date); + if (days === 0) return `${item.name} scade oggi! Usalo subito.`; + if (days === 1) return `${item.name} scade domani. Pensaci!`; + return `${item.name} scade tra ${days} giorni.`; + }); + if (expiringSoon.length > 1) { + facts.push(() => `Hai ${expiringSoon.length} prodotti in scadenza ravvicinata.`); + } + } + if (expiringThisWeek.length > 0) { + facts.push(() => `Questa settimana scadono ${expiringThisWeek.length} prodotti. Pianifica i pasti di conseguenza!`); + facts.push(() => { + const item = rItem(expiringThisWeek); + const days = daysUntilExpiry(item.expiry_date); + const locLabel = LOCATIONS[item.location]?.label || item.location; + return `${item.name} (${locLabel}) scade tra ${days} ${days === 1 ? 'giorno' : 'giorni'}.`; + }); + } + if (expiringThisMonth.length > 0) { + facts.push(() => `In questo mese scadranno ${expiringThisMonth.length} prodotti.`); + } + + // --- Shopping list facts --- + if (shop.length > 0) { + facts.push(() => `Hai ${shop.length} ${shop.length === 1 ? 'prodotto' : 'prodotti'} nella lista della spesa.`); + facts.push(() => { + const names = shop.slice(0, 4).map(i => i.name); + return `Nella spesa: ${names.join(', ')}${shop.length > 4 ? '...' : ''}`; + }); + if (spesaTotal > 0) { + facts.push(() => `Il totale previsto per la spesa è circa €${spesaTotal.toFixed(2)}.`); + if (spesaPriced < shop.length) { + facts.push(() => `Spesa stimata: €${spesaTotal.toFixed(2)} (${spesaPriced} di ${shop.length} prodotti con prezzo).`); + } + } + } + if (shop.length === 0) { + facts.push(() => `La lista della spesa è vuota. Tutto a posto!`); + } + + // --- Location-based facts --- + if (inFrigo.length > 0) { + facts.push(() => `Hai ${inFrigo.length} prodotti in frigo.`); + facts.push(() => { + const item = rItem(inFrigo); + return `In frigo c'è: ${item.name}${item.brand ? ' (' + item.brand + ')' : ''}.`; + }); + } + if (inFreezer.length > 0) { + facts.push(() => `Hai ${inFreezer.length} prodotti nel freezer.`); + facts.push(() => { + const item = rItem(inFreezer); + return `Nel freezer c'è: ${item.name}. Non dimenticartelo!`; + }); + } + if (inDispensa.length > 0) { + facts.push(() => `In dispensa ci sono ${inDispensa.length} prodotti.`); + } + + // --- Category-based facts --- + const catEntries = Object.entries(byCategory); + if (catEntries.length > 0) { + facts.push(() => { + const sorted = catEntries.sort((a, b) => b[1].length - a[1].length); + const top = sorted[0]; + const catLabel = top[0]; + const icon = CATEGORY_ICONS[catLabel] || '📦'; + return `La categoria più presente è ${icon} ${catLabel} con ${top[1].length} prodotti.`; + }); + if (byCategory['carne'] && byCategory['carne'].length > 0) { + facts.push(() => `Hai ${byCategory['carne'].length} prodotti di carne. 🥩`); + } + if (byCategory['latticini'] && byCategory['latticini'].length > 0) { + facts.push(() => `Hai ${byCategory['latticini'].length} latticini in casa. 🥛`); + } + if (byCategory['verdura'] && byCategory['verdura'].length > 0) { + facts.push(() => `Hai ${byCategory['verdura'].length} tipi di verdura. Ottimo per la salute! 🥬`); + } + if (byCategory['frutta'] && byCategory['frutta'].length > 0) { + facts.push(() => `Hai ${byCategory['frutta'].length} tipi di frutta. 🍎`); + } + if (byCategory['bevande'] && byCategory['bevande'].length > 0) { + facts.push(() => `Hai ${byCategory['bevande'].length} bevande disponibili. 🥤`); + } + if (byCategory['surgelati'] && byCategory['surgelati'].length > 0) { + facts.push(() => `Hai ${byCategory['surgelati'].length} surgelati nel freezer. ❄️`); + } + if (byCategory['pasta'] && byCategory['pasta'].length > 0) { + facts.push(() => `Hai ${byCategory['pasta'].length} tipi di pasta. 🍝 Che ne dici di una carbonara?`); + } + if (byCategory['conserve'] && byCategory['conserve'].length > 0) { + facts.push(() => `Hai ${byCategory['conserve'].length} conserve in dispensa. 🥫`); + } + if (byCategory['snack'] && byCategory['snack'].length > 0) { + facts.push(() => `Hai ${byCategory['snack'].length} snack. Resisti alla tentazione! 🍪`); + } + if (byCategory['condimenti'] && byCategory['condimenti'].length > 0) { + facts.push(() => `Hai ${byCategory['condimenti'].length} condimenti a disposizione. 🧂`); + } + } + + // --- General inventory facts --- + if (inv.length > 0) { + facts.push(() => `Hai ${totalProducts} prodotti diversi in casa per un totale di ${Math.round(totalItems)} pezzi.`); + facts.push(() => { + const item = rItem(inv); + return `Lo sapevi? Hai ${item.name} in ${LOCATIONS[item.location]?.label || item.location}.`; + }); + facts.push(() => { + const item = rItem(inv); + const qty = formatQuantity(item.quantity, item.unit, item.default_quantity, item.package_unit); + return `${item.name}: ne hai ${qty}.`; + }); + } + if (noExpiry.length > 0) { + facts.push(() => `${noExpiry.length} prodotti non hanno una data di scadenza impostata.`); + } + if (withExpiry.length > 0) { + // Find the one expiring furthest away + const furthest = withExpiry.reduce((best, item) => { + const d = daysUntilExpiry(item.expiry_date); + return d > (best.d || 0) ? { item, d } : best; + }, { d: 0 }); + if (furthest.item && furthest.d > 30) { + facts.push(() => `Il prodotto con scadenza più lontana è ${furthest.item.name}: ${Math.round(furthest.d / 30)} mesi.`); + } + } + + // --- Quantity-based facts --- + const highQtyItems = inv.filter(i => parseFloat(i.quantity) >= 5); + if (highQtyItems.length > 0) { + facts.push(() => { + const item = rItem(highQtyItems); + const qty = formatQuantity(item.quantity, item.unit, item.default_quantity, item.package_unit); + return `Hai una bella scorta di ${item.name}: ${qty}!`; + }); + } + const lowQtyItems = inv.filter(i => parseFloat(i.quantity) <= 1 && parseFloat(i.quantity) > 0); + if (lowQtyItems.length > 0) { + facts.push(() => { + const item = rItem(lowQtyItems); + return `${item.name} sta per finire. Aggiungilo alla spesa?`; + }); + facts.push(() => `Ci sono ${lowQtyItems.length} prodotti quasi finiti.`); + } + + // --- Time-of-day greetings & suggestions --- + facts.push(() => `${greeting}! Se vuoi che ti preparo una ricetta, tocca qui.`); + facts.push(() => `${greeting}! La tua dispensa è sotto controllo. 😊`); + if (hour >= 6 && hour < 10) { + facts.push(() => `Buongiorno! Pronto per la colazione? ☕`); + if (byCategory['pane']) facts.push(() => `Buongiorno! Hai del pane per la colazione. 🍞`); + if (byCategory['latticini']) facts.push(() => `C'è del latte in frigo per il cappuccino? ☕🥛`); + } + if (hour >= 11 && hour < 14) { + facts.push(() => `È quasi ora di pranzo! Cosa cuciniamo? 🍽️`); + if (byCategory['pasta']) facts.push(() => `Ora di pranzo… Un bel piatto di pasta? 🍝`); + } + if (hour >= 17 && hour < 21) { + facts.push(() => `Buona sera! Hai pensato alla cena? 🍽️`); + if (byCategory['carne']) facts.push(() => `Per cena potresti usare la carne che hai. 🥩`); + if (byCategory['pesce']) facts.push(() => `Che ne dici di pesce per cena? 🐟`); + } + if (hour >= 21 || hour < 6) { + facts.push(() => `Buonanotte! Domani controlla le scadenze. 🌙`); + } + + // --- Weekly stats --- + const recentIn = stats.recent_in || 0; + const recentOut = stats.recent_out || 0; + if (recentIn > 0) { + facts.push(() => `Questa settimana hai aggiunto ${recentIn} prodotti.`); + } + if (recentOut > 0) { + facts.push(() => `Questa settimana hai consumato ${recentOut} prodotti.`); + } + if (recentIn > 0 && recentOut > 0) { + facts.push(() => `Bilancio settimanale: +${recentIn} entrati, -${recentOut} usciti.`); + } + + // --- Tips & curiosità (statici ma ruotano) --- + facts.push(() => `💡 Lo sapevi? I prodotti in freezer durano molto più a lungo della data di scadenza.`); + facts.push(() => `💡 Il pane congelato mantiene la fragranza per settimane.`); + facts.push(() => `💡 Le uova si conservano fino a 3-4 settimane dopo la data preferita.`); + facts.push(() => `💡 Lo yogurt chiuso in frigo dura spesso 1-2 settimane oltre la scadenza.`); + facts.push(() => `💡 Per evitare sprechi, usa prima i prodotti con scadenza più vicina.`); + facts.push(() => `💡 La carne in freezer può durare fino a 6 mesi senza problemi.`); + facts.push(() => `💡 Le verdure fresche durano di più se conservate nel cassetto del frigo.`); + facts.push(() => `💡 Controlla regolarmente la dispensa per evitare doppioni nella spesa.`); + facts.push(() => `💡 I latticini vanno conservati nella parte più fredda del frigo.`); + facts.push(() => `💡 Non ricongelare mai un alimento già scongelato. Cucinalo subito!`); + facts.push(() => `💡 Un frigo ordinato ti fa risparmiare tempo e denaro.`); + facts.push(() => `💡 Le conserve aperte vanno in frigo e consumate in pochi giorni.`); + + // --- Brand-based facts --- + const brands = inv.filter(i => i.brand).map(i => i.brand); + if (brands.length > 0) { + const brandCount = {}; + brands.forEach(b => { brandCount[b] = (brandCount[b] || 0) + 1; }); + const topBrand = Object.entries(brandCount).sort((a, b) => b[1] - a[1])[0]; + facts.push(() => `Il marca più presente nella tua dispensa è ${topBrand[0]} con ${topBrand[1]} prodotti.`); + } + + // --- Specific food combo facts --- + if (byCategory['pasta'] && byCategory['condimenti']) { + facts.push(() => `Hai pasta e condimenti: sei pronto per un primo piatto! 🍝`); + } + if (byCategory['pane'] && byCategory['carne']) { + facts.push(() => `Pane e carne: un panino veloce è sempre una buona idea! 🥪`); + } + if (byCategory['verdura'] && byCategory['carne']) { + facts.push(() => `Verdura e carne: hai tutto per un piatto equilibrato! 🥗🥩`); + } + + // --- Empty states --- + if (inv.length === 0) { + facts.push(() => `La dispensa è vuota! Fai una bella spesa. 🛒`); + facts.push(() => `Nessun prodotto registrato. Scansiona qualcosa per iniziare!`); + } + + // --- Location distribution --- + const locCount = Object.keys(byLocation).length; + if (locCount > 1) { + facts.push(() => { + const parts = Object.entries(byLocation).map(([loc, items]) => + `${LOCATIONS[loc]?.icon || '📦'} ${items.length}` + ); + return `Distribuzione: ${parts.join(' · ')}`; + }); + } + + // Pick a random fact + if (facts.length === 0) { + return `${greeting}! La tua Dispensa ti aspetta.`; + } + return facts[Math.floor(Math.random() * facts.length)](); +} + +function initInactivityWatcher() { + const events = ['pointerdown', 'pointermove', 'keydown', 'scroll', 'touchstart']; + events.forEach(evt => { + document.addEventListener(evt, () => { + if (_screensaverActive) { + dismissScreensaver(); + } else { + resetInactivityTimer(); + } + }, { passive: true }); + }); + resetInactivityTimer(); +} + // ===== INITIALIZATION ===== document.addEventListener('DOMContentLoaded', () => { syncSettingsFromDB(); showPage('dashboard'); + initInactivityWatcher(); }); // ===== DUPLICLICK (SPESA ONLINE) ===== diff --git a/data/dispensa.db b/data/dispensa.db index 699f1b4c08153026a9e74a41c367c1c06cb78d87..2bb131304ecb8e56b7d68d19a9f592a12a6bccd4 100644 GIT binary patch delta 1716 zcmaJ?O>Y}T81|$wDTR8Al0X9oUawRL$aZ(_tese(1{GBTQ7IxV9|Dw)cgM?!-I?uv zSRz&BaBA}bDiUIaH~@ztE`X2^`2(EjwaU5bIR_$fK|R1bYp3ffh=aYeR=Ub%o+ZFp9DGq)=cm7HdHw)8)C%^xzf8wMHDBdm< zZx70+zAg5@dapQDD9`=G8+D`JtQw7~*$6@^*5x%l=CQ~0w!R`gCSnS!J?7!VmI782 zSAxS~Mf%c_p@a(|i);xDE>a%p72Rjl!_;9Zd=FR{NfGI76W1XN)?)5s?F+gEJ{K5Z zOZmxYQ#TJcW9nkc2YQdWET$o+LSiVwkT$~wLnzYcwoL!NLEfqz;*2t!`1{rPQ^a5z z2DrnwHIeuo79tA6NVm60d7^Hlzig40(z`dw%v{T==nm`B#EaLiCRCsRSfJ6eQnE?T z%(kqoP6y?2lq@0lIZe;*kY^@a)p{#^f0KNX9&D4#=__00)VxvxUYA}5bqK{eip0u* z=p@H6{cPPb{tF0hr(~O)nHiON1W5YoCOKVOtTs&jdgYofsFGJd&jaas6k&ukrnw{^ zo?U=oJ^gH(SY#>vd6Tp!7i-9Ke~T;)SCt*|a$|)?5$35$2ct>nHU3XlbBxYJ(@?6+ zl2-c9P4dEQ(=dU2_6F;#bL6}rQi@H ziXb|n%hU#|!K(CljQmB+AX3Ofq(B{D9n^wnK|pOBFhtZ-^s=xXI`m<`zVbV^=F)XJ zt$?rG?cv;|>J)R4jOwGW!RDb&!KyAQDM6(=V2%4-U_Hwpcrxw zDY&u*mqJ7hT`ri<-c{HYt$1?u6^$mWVzCz}rP$|W_8k)n?b0Bgi}Hl-3ETjW5jHoP-FMm5Du zCY&UVF@>q|^&O5qvwLhL{+l?6=0A>DKl0e!B`$}RJ2N$fSU(YXeFq&=4Y%esk+ka~Ju?_M~ad7ML zugR`;gWNg(<1$$;secKgJ;3k6wDw?^0QplZ%^W*{?-SZyLEG2%22U;jp*=l2S)L<# m@!=I;O!vPdr;kr)_Y2yAb{`}6G_7>3JQi5oJ@}Pq%l`uAn=hdN delta 124 zcmZp8z~1nHeS$PEF9QREJP>mOG4n(Xb4K2c3CZ@1ESrzmJ14L*0YybN^DOvMzgf`X zE&pZ(hkN`J7nm+{V074Kz&M|ki7RDispensa Manager - + @@ -802,6 +802,10 @@

🍳 Ricetta

Genero una ricetta sana con gli ingredienti in dispensa, dando priorità a quelli in scadenza.

+
+ +
+
@@ -858,6 +862,14 @@
- + + + +