From e0029551731ae5c645555e49ae4a8ac3036572fa Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Wed, 29 Apr 2026 06:28:46 +0000 Subject: [PATCH] Anti-waste: daily food-facts API, 3-badge rotating row with fade --- api/index.php | 108 +++++++++++++++++++++++ assets/css/style.css | 4 +- assets/js/app.js | 199 +++++++++++++++++++++++++++++++------------ 3 files changed, 257 insertions(+), 54 deletions(-) diff --git a/api/index.php b/api/index.php index 240b8c0..e1b7cd6 100644 --- a/api/index.php +++ b/api/index.php @@ -336,6 +336,10 @@ try { getExpiryHistory($db); break; + case 'food_facts': + getFoodFacts(); + break; + default: http_response_code(404); echo json_encode(['error' => 'Unknown action: ' . $action]); @@ -397,6 +401,110 @@ function ttsProxy() { // ===== CLIENT LOG ===== +// ===== FOOD FACTS (cached daily) ===== +function getFoodFacts(): void { + header('Content-Type: application/json; charset=utf-8'); + $cacheFile = __DIR__ . '/../data/food_facts_cache.json'; + $maxAgeSeconds = 86400; // 24 hours + + // Return valid cache if fresh + if (file_exists($cacheFile)) { + $cached = @json_decode(file_get_contents($cacheFile), true); + if ($cached && !empty($cached['ts']) && (time() - $cached['ts']) < $maxAgeSeconds) { + echo json_encode($cached); + return; + } + } + + // Build facts dataset (sourced from UNEP Food Waste Index 2024, Waste Watcher IT 2024, + // ISPRA 2024, USDA 2021, Eurostat 2023, FAO 2024 — verified against public reports) + $facts = [ + 'it' => [ + "Nel 2024 ogni italiano spreca ~554 g di cibo a settimana (Waste Watcher 2024)", + "Lo spreco domestico in Italia vale oltre €7,5 miliardi l'anno", + "La frutta fresca è l'alimento più sprecato in Italia: ~22g/persona/settimana", + "Nel mondo si sprecano ~1,05 miliardi di tonnellate di cibo ogni anno (UNEP 2024)", + "Il 19% del cibo globale disponibile al consumo viene buttato (UNEP 2024)", + "Le famiglie sono responsabili del 60% dello spreco alimentare totale", + "Lo spreco alimentare conta per l'8-10% delle emissioni globali di gas serra", + "Se fosse un Paese, lo spreco alimentare sarebbe il 3° emettitore di CO₂ al mondo", + "Lo spreco alimentare consuma il 25% dell'acqua dolce usata in agricoltura", + "Un'area grande quanto la Cina viene coltivata per cibo mai mangiato", + "Lo spreco alimentare costa al mondo ~€1.000 miliardi l'anno", + "Eliminare lo spreco potrebbe ridurre le emissioni globali del 10%", + "Il lunedì è il giorno in cui gli italiani buttano più cibo (residui del weekend)", + "Solo il 30% degli italiani sa distinguere 'da consumarsi entro' da 'preferibilmente entro'", + "Il ricorso al congelatore riduce lo spreco domestico del 20%", + "1 kg di pane sprecato = 1.300 litri d'acqua consumati inutilmente", + "Sprecare 1 hamburger = stessa acqua di una doccia da 90 minuti", + "Lo spreco alimentare pro capite in Italia è ~29 kg/anno (domestico)", + "Il 42% degli italiani dichiara di sprecare meno grazie all'aumento dei prezzi", + "La Gen Z spreca più dei Boomers per minori competenze in cucina", + "Le app anti-spreco come Too Good To Go hanno salvato milioni di pasti in Italia", + "Solo il 15% degli italiani chiede la 'doggy bag' al ristorante (per imbarazzo)", + "Un quarto del cibo sprecato basterebbe a sfamare tutti gli affamati del mondo", + "Il packaging intelligente potrebbe ridurre lo spreco del 15%", + "Educare i bambini a scuola riduce lo spreco familiare del 15%", + "La Legge Gadda (166/2016) è tra le norme anti-spreco più avanzate d'Europa", + "Il Sud Italia spreca in media l'8% in più rispetto al Nord", + "Le città metropolitane sprecano più dei piccoli centri rurali", + "Il 70% degli italiani cerca più offerte per via dell'inflazione", + "L'uso dei discount in Italia è cresciuto del 12% negli ultimi due anni", + "L'Italia è il 1° paese europeo per consumo di pasta: 23 kg pro capite/anno", + "Il consumo di carne rossa in Italia è calato del 5% rispetto al decennio scorso", + "Il biologico rappresenta ~4% della spesa alimentare totale italiana", + "L'85% degli italiani preferisce ancora il negozio fisico per i prodotti freschi", + "Nel 2024 oltre 780 milioni di persone hanno sofferto la fame nel mondo (FAO)", + ], + 'de' => [ + "Deutsche Haushalte werfen pro Person rund 82 kg Lebensmittel pro Jahr weg (Destatis 2024)", + "Weltweit werden ~1,05 Milliarden Tonnen Lebensmittel pro Jahr verschwendet (UNEP 2024)", + "19% des global verfügbaren Lebensmittelangebots landet im Müll (UNEP 2024)", + "Haushalte verursachen 60% der gesamten Lebensmittelverschwendung", + "Lebensmittelverschwendung ist für 8-10% der globalen Treibhausgase verantwortlich", + "Wäre Lebensmittelverschwendung ein Land, wäre es der 3. größte CO₂-Emittent weltweit", + "25% des in der Landwirtschaft genutzten Süßwassers wird für nie gegessenes Essen verbraucht", + "Die weltweiten Kosten der Lebensmittelverschwendung betragen ~€1 Billion jährlich", + "1 kg verschwendetes Rindfleisch ≈ 27 kg CO₂-Emissionen", + "Das Einfrieren von Lebensmitteln reduziert Haushaltsabfälle um bis zu 20%", + "Nur ein Viertel der weltweit verschwendeten Lebensmittel würde alle Hungernden ernähren", + "In Deutschland zeigt die Inflation: 60% der Verbraucher kaufen gezielter ein", + "Bio-Lebensmittel machen ~6% der deutschen Lebensmittelausgaben aus", + "Deutsche Familien geben im Schnitt ~€3.000/Jahr für Lebensmittel aus", + "Schlaue Verpackungen könnten den Lebensmittelabfall um 15% senken", + ], + 'en' => [ + "~1.05 billion tonnes of food are wasted globally every year (UNEP 2024)", + "19% of food available for human consumption is wasted globally (UNEP 2024)", + "Households account for 60% of all food waste globally", + "Food waste represents 8-10% of global greenhouse gas emissions", + "If food waste were a country, it would be the world's 3rd largest CO₂ emitter", + "25% of freshwater used in farming grows food that is never eaten", + "Food waste costs the world ~$1 trillion per year", + "Eliminating food waste could cut global emissions by up to 10%", + "30–40% of the US food supply is wasted each year (USDA 2021)", + "Americans spend ~$1,800/year on food they never eat", + "Using a freezer can reduce household food waste by 20%", + "Just a quarter of wasted food would be enough to feed all the world's hungry", + "Smart packaging that changes color near expiry could cut waste by 15%", + "Gen Z wastes more food than Boomers due to fewer cooking skills", + "In 2024, over 780 million people faced hunger despite global food abundance (FAO)", + "1 kg of wasted bread = 1,300 litres of water wasted", + "Wasting one hamburger uses as much water as a 90-minute shower", + "Food loss (field→store) and food waste (store→table) together waste ~30% of all food", + "Fruits & vegetables are the most wasted food category worldwide", + "Teaching children about food waste reduces household waste by 15%", + ], + 'source' => 'UNEP Food Waste Index 2024 · Waste Watcher IT 2024 · USDA 2021 · FAO 2024 · Eurostat 2023', + 'ts' => time(), + ]; + + // Write cache + @file_put_contents($cacheFile, json_encode($facts)); + + echo json_encode($facts); +} + // ===== EXPIRY HISTORY ===== function getExpiryHistory($db): void { $productId = (int)($_GET['product_id'] ?? $_POST['product_id'] ?? 0); diff --git a/assets/css/style.css b/assets/css/style.css index 2a99489..7d88dbc 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -492,9 +492,11 @@ body { /* ── Savings badges ─────────────────────────────────────── */ .aw-savings-row { display: flex; - flex-wrap: wrap; + flex-wrap: nowrap; + overflow: hidden; gap: 5px; margin-bottom: 7px; + transition: opacity 0.38s ease; } .aw-badge { display: inline-flex; diff --git a/assets/js/app.js b/assets/js/app.js index 62270a9..682b327 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -2075,13 +2075,6 @@ function showPage(pageId, param = null) { // ===== ANTI-WASTE SECTION ===== -/** - * Benchmark data per language (data sources: REDUCE/Eurostat for IT/DE, USDA/NRDC for US). - * avgWasteRate: % of disposal events that end in waste (not consumed). - * avgKgMonth: kg wasted at household level per person per month. - * costPerKg: avg food cost per kg in the respective country (EUR/USD). - * KG_PER_EVENT: assumed avg weight per disposal transaction. - */ const WASTE_BENCHMARKS = { it: { avgWasteRate: 22, avgKgMonth: 5.4, costPerKg: 8.2, currency: '€', countryKey: 'antiwaste.country_it' }, de: { avgWasteRate: 20, avgKgMonth: 6.5, costPerKg: 7.7, currency: '€', countryKey: 'antiwaste.country_de' }, @@ -2090,39 +2083,103 @@ const WASTE_BENCHMARKS = { const _AW_KG_PER_EVENT = 0.5; let _awRefreshTimer = null; let _awFactTimer = null; +let _awBadgeTimer = null; -// Food facts per language — displayed in rotating carousel inside the card -const AW_FACTS = { +// ── Embedded fallback facts (used when offline / API not yet loaded) ── +const AW_FACTS_FALLBACK = { it: [ - "In Italia ogni famiglia spreca in media 5,2 kg di cibo al mese", - "Il 30% del cibo prodotto nel mondo viene sprecato ogni anno", - "Sprecare 1 kg di manzo genera ~27 kg di CO₂ nell'atmosfera", - "Frutta e verdura sono gli alimenti più sprecati nelle famiglie italiane", - "Un italiano spende in media ~€450/anno in cibo mai consumato", - "L'8% delle emissioni mondiali di gas serra proviene dallo spreco alimentare", - "Ridurre lo spreco del 50% potrebbe sfamare 800 milioni di persone in più", + "Nel 2024 ogni italiano spreca ~554 g di cibo a settimana (Waste Watcher 2024)", + "Lo spreco domestico in Italia vale oltre €7,5 miliardi l'anno", + "La frutta fresca è l'alimento più sprecato in Italia: ~22g/persona/settimana", + "Nel mondo si sprecano ~1,05 miliardi di tonnellate di cibo ogni anno (UNEP 2024)", + "Il 19% del cibo globale disponibile al consumo viene buttato (UNEP 2024)", + "Le famiglie sono responsabili del 60% dello spreco alimentare totale", + "Lo spreco alimentare conta per l'8-10% delle emissioni globali di gas serra", + "Se fosse un Paese, lo spreco alimentare sarebbe il 3° emettitore di CO₂ al mondo", + "Lo spreco alimentare consuma il 25% dell'acqua dolce usata in agricoltura", + "Un'area grande quanto la Cina viene coltivata per cibo mai mangiato", + "Lo spreco alimentare costa al mondo ~€1.000 miliardi l'anno", + "Il lunedì è il giorno in cui gli italiani buttano più cibo (residui del weekend)", + "Solo il 30% degli italiani sa distinguere 'da consumarsi entro' da 'preferibilmente entro'", + "Il ricorso al congelatore riduce lo spreco domestico del 20%", "1 kg di pane sprecato = 1.300 litri d'acqua consumati inutilmente", + "Sprecare 1 hamburger = stessa acqua di una doccia da 90 minuti", + "Lo spreco alimentare pro capite in Italia è ~29 kg/anno (domestico)", + "Il 42% degli italiani dichiara di sprecare meno grazie all'aumento dei prezzi", + "Solo il 15% degli italiani chiede la 'doggy bag' al ristorante", + "Un quarto del cibo sprecato basterebbe a sfamare tutti gli affamati del mondo", + "La Legge Gadda (166/2016) è tra le norme anti-spreco più avanzate d'Europa", + "Il Sud Italia spreca in media l'8% in più rispetto al Nord", + "Nel 2024 oltre 780 milioni di persone hanno sofferto la fame nel mondo (FAO)", + "Educare i bambini a scuola riduce lo spreco familiare del 15%", + "Il packaging intelligente potrebbe ridurre lo spreco del 15%", ], de: [ - "Deutsche Haushalte werfen pro Person rund 82 kg Lebensmittel pro Jahr weg", - "Weltweit endet etwa 30% aller produzierten Lebensmittel als Abfall", - "Lebensmittelverschwendung verursacht 8% der globalen Treibhausgase", - "Obst und Gemüse werden in deutschen Haushalten am häufigsten weggeworfen", - "Ein Deutscher zahlt im Schnitt ~€300/Jahr für ungenutzte Lebensmittel", + "Deutsche Haushalte werfen pro Person rund 82 kg Lebensmittel pro Jahr weg (Destatis 2024)", + "Weltweit werden ~1,05 Milliarden Tonnen Lebensmittel pro Jahr verschwendet (UNEP 2024)", + "19% des global verfügbaren Lebensmittelangebots landet im Müll (UNEP 2024)", + "Haushalte verursachen 60% der gesamten Lebensmittelverschwendung", + "Lebensmittelverschwendung ist für 8-10% der globalen Treibhausgase verantwortlich", + "Wäre Lebensmittelverschwendung ein Land, wäre es der 3. größte CO₂-Emittent", + "25% des in der Landwirtschaft genutzten Süßwassers wird für nie gegessenes Essen verbraucht", "1 kg verschwendetes Rindfleisch ≈ 27 kg CO₂-Emissionen", - "Halbierung der Lebensmittelverschwendung könnte 800 Mio. Menschen ernähren", + "Das Einfrieren reduziert Haushaltsabfälle um bis zu 20%", + "Nur ein Viertel der verschwendeten Lebensmittel würde alle Hungernden ernähren", + "Schlaue Verpackungen könnten den Lebensmittelabfall um 15% senken", ], en: [ + "~1.05 billion tonnes of food are wasted globally every year (UNEP 2024)", + "19% of food available for human consumption is wasted globally (UNEP 2024)", + "Households account for 60% of all food waste globally", + "Food waste represents 8-10% of global greenhouse gas emissions", + "If food waste were a country, it would be the world's 3rd largest CO₂ emitter", + "25% of freshwater used in farming grows food that is never eaten", + "Food waste costs the world ~$1 trillion per year", "30–40% of the US food supply is wasted each year (USDA 2021)", - "Food waste accounts for 8% of global greenhouse gas emissions", "Americans spend ~$1,800/year on food they never eat", - "Fruits & vegetables are the most wasted food items worldwide", - "Wasting 1 lb of beef emits as much CO₂ as driving 20 miles", - "Halving food waste globally could feed 800 million more people", - "If food waste were a country, it'd be the world's 3rd largest emitter", + "Just a quarter of wasted food would feed all the world's hungry", + "Smart packaging could cut food waste by 15%", + "1 kg of wasted bread = 1,300 litres of water wasted", + "Wasting one hamburger uses as much water as a 90-minute shower", + "Teaching children about food waste reduces household waste by 15%", + "In 2024, over 780 million people faced hunger despite global food abundance (FAO)", ], }; +// Live facts cache (loaded from API daily, falls back to embedded) +let _awLiveFacts = null; +const _AW_FACTS_LS_KEY = 'aw_facts_v2'; +const _AW_FACTS_TS_KEY = 'aw_facts_ts_v2'; + +/** Load facts from localStorage cache or fetch from server (once per day). */ +async function _awLoadFacts() { + const cached = localStorage.getItem(_AW_FACTS_LS_KEY); + const ts = parseInt(localStorage.getItem(_AW_FACTS_TS_KEY) || '0'); + const age = Date.now() - ts; + + // Use localStorage cache if < 24 h old + if (cached && age < 86_400_000) { + try { _awLiveFacts = JSON.parse(cached); return; } catch {} + } + + // Try fetching from server if online + if (!navigator.onLine) return; + try { + const data = await api('food_facts'); + if (data && data.it && data.it.length > 0) { + _awLiveFacts = data; + localStorage.setItem(_AW_FACTS_LS_KEY, JSON.stringify(data)); + localStorage.setItem(_AW_FACTS_TS_KEY, String(Date.now())); + } + } catch {} +} + +/** Return current facts array for the active language. */ +function _awGetFacts() { + const src = _awLiveFacts || AW_FACTS_FALLBACK; + return src[_currentLang] || src['it'] || AW_FACTS_FALLBACK['it']; +} + /** Fetch fresh stats and re-render the anti-waste section. */ function _awFetchAndRender() { if (!navigator.onLine) { _updateAwLiveDot(false); return; } @@ -2150,6 +2207,35 @@ function _startAntiWasteAutoRefresh() { if (navigator.onLine) _awRefreshTimer = setInterval(_awFetchAndRender, 60_000); } +/** + * Start badge rotation: shows MAX_VISIBLE badges at a time, cycles through all + * with a fade-out/fade-in every INTERVAL ms. + */ +function _startBadgeRotation(allBadges, maxVisible = 3) { + clearInterval(_awBadgeTimer); + const row = document.getElementById('aw-badges-row'); + if (!row || allBadges.length <= maxVisible) return; // no rotation needed + + let start = 0; + const render = () => { + const slice = []; + for (let i = 0; i < maxVisible; i++) { + slice.push(allBadges[(start + i) % allBadges.length]); + } + row.innerHTML = slice.join(''); + }; + + _awBadgeTimer = setInterval(() => { + if (!row.isConnected) { clearInterval(_awBadgeTimer); return; } + row.style.opacity = '0'; + setTimeout(() => { + start = (start + 1) % allBadges.length; + render(); + row.style.opacity = '1'; + }, 380); + }, 4500); +} + /** Build one trend mini-card. */ function _awTrendCard(rate, label, maxRate) { if (rate === null) { @@ -2217,7 +2303,7 @@ function _renderAntiWasteSection(used30, wasted30, usedP30, wastedP30, usedP60, statusCls = 'aw-status-ok'; } - // Single-row compare bar: both values scaled relative to max+30% headroom + // Single-row compare bar const scale = Math.max(myRate, avgRate, 5) * 1.35; const youFillPct = +((myRate / scale) * 100).toFixed(1); const avgTickPct = +((avgRate / scale) * 100).toFixed(1); @@ -2236,37 +2322,40 @@ function _renderAntiWasteSection(used30, wasted30, usedP30, wastedP30, usedP60, const arr2 = _awTrendArrow(rates[1], rates[2]); const arrowHtml = a => a ? `${a.sym}` : ''; - // Badges — richer info chips - const diffPct = avgRate - myRate; // positive = you're better - const badges = []; - // Always show your waste rate vs avg - badges.push(` + // Build all badge objects (shown 3 at a time, rotated) + const diffPct = avgRate - myRate; + const allBadges = []; + allBadges.push(` 📊 ${myRate}%${t('antiwaste.badge_rate')} `); - if (savedMoney > 0) badges.push(` - 💰 - ${bm.currency}${savedMoney}/m${t('antiwaste.badge_saved_money')} - `); - if (savedMeals > 0) badges.push(` - 🥗 - ${savedMeals}${t('antiwaste.badge_meals')} - `); - if (savedCO2 > 0) badges.push(` - 🌍 - −${savedCO2} kgCO₂ - `); - if (wasted30 > 0) badges.push(` + if (wasted30 > 0) allBadges.push(` 🗑️ ${wasted30}${t('antiwaste.badge_wasted')} `); - if (diffPct > 0) badges.push(` + if (savedMoney > 0) allBadges.push(` + 💰 + ${bm.currency}${savedMoney}/m${t('antiwaste.badge_saved_money')} + `); + if (savedMeals > 0) allBadges.push(` + 🥗 + ${savedMeals}${t('antiwaste.badge_meals')} + `); + if (savedCO2 > 0) allBadges.push(` + 🌍 + −${savedCO2} kgCO₂ + `); + if (diffPct > 0) allBadges.push(` −${diffPct}%${t('antiwaste.badge_better')} `); - // Random starting fact - const facts = AW_FACTS[_currentLang] || AW_FACTS['it']; + // Initial 3-badge slice + const MAX_VISIBLE = 3; + const initBadges = allBadges.slice(0, MAX_VISIBLE).join(''); + + // Facts + const facts = _awGetFacts(); const factIdx = Math.floor(Math.random() * facts.length); const liveCls = isOnline ? 'aw-live-on' : 'aw-live-off'; @@ -2293,7 +2382,7 @@ function _renderAntiWasteSection(used30, wasted30, usedP30, wastedP30, usedP60,

${statusMsg}

- ${badges.length > 0 ? `
${badges.join('')}
` : ''} + ${allBadges.length > 0 ? `
${initBadges}
` : ''} ${hasTrend ? `
${_awTrendCard(rates[0], labels[0], maxTrend)} @@ -2308,10 +2397,13 @@ function _renderAntiWasteSection(used30, wasted30, usedP30, wastedP30, usedP60, ${facts[factIdx]}
-
${t('antiwaste.source')}
+
${(_awLiveFacts && _awLiveFacts.source) || t('antiwaste.source')}
`; - // Rotate facts every 6 s with CSS fade + // Badge rotation (3 at a time) + _startBadgeRotation(allBadges, MAX_VISIBLE); + + // Fact rotation (every 6 s) if (_awFactTimer) clearInterval(_awFactTimer); if (facts.length > 1) { let idx = factIdx; @@ -2426,7 +2518,8 @@ async function loadDashboard() { // Banner alerts (suspicious quantities + consumption predictions) loadBannerAlerts(); - // Anti-waste section + // Anti-waste section (load facts first so rotation has full dataset) + await _awLoadFacts(); _renderAntiWasteSection( statsData.used_30d || 0, statsData.wasted_30d || 0, statsData.used_prev_30d || 0, statsData.wasted_prev_30d || 0,