Merge branch 'develop'
This commit is contained in:
+108
@@ -336,6 +336,10 @@ try {
|
|||||||
getExpiryHistory($db);
|
getExpiryHistory($db);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'food_facts':
|
||||||
|
getFoodFacts();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo json_encode(['error' => 'Unknown action: ' . $action]);
|
echo json_encode(['error' => 'Unknown action: ' . $action]);
|
||||||
@@ -397,6 +401,110 @@ function ttsProxy() {
|
|||||||
|
|
||||||
// ===== CLIENT LOG =====
|
// ===== 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 =====
|
// ===== EXPIRY HISTORY =====
|
||||||
function getExpiryHistory($db): void {
|
function getExpiryHistory($db): void {
|
||||||
$productId = (int)($_GET['product_id'] ?? $_POST['product_id'] ?? 0);
|
$productId = (int)($_GET['product_id'] ?? $_POST['product_id'] ?? 0);
|
||||||
|
|||||||
@@ -492,9 +492,11 @@ body {
|
|||||||
/* ── Savings badges ─────────────────────────────────────── */
|
/* ── Savings badges ─────────────────────────────────────── */
|
||||||
.aw-savings-row {
|
.aw-savings-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
margin-bottom: 7px;
|
margin-bottom: 7px;
|
||||||
|
transition: opacity 0.38s ease;
|
||||||
}
|
}
|
||||||
.aw-badge {
|
.aw-badge {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|||||||
+146
-53
@@ -2075,13 +2075,6 @@ function showPage(pageId, param = null) {
|
|||||||
|
|
||||||
// ===== ANTI-WASTE SECTION =====
|
// ===== 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 = {
|
const WASTE_BENCHMARKS = {
|
||||||
it: { avgWasteRate: 22, avgKgMonth: 5.4, costPerKg: 8.2, currency: '€', countryKey: 'antiwaste.country_it' },
|
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' },
|
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;
|
const _AW_KG_PER_EVENT = 0.5;
|
||||||
let _awRefreshTimer = null;
|
let _awRefreshTimer = null;
|
||||||
let _awFactTimer = null;
|
let _awFactTimer = null;
|
||||||
|
let _awBadgeTimer = null;
|
||||||
|
|
||||||
// Food facts per language — displayed in rotating carousel inside the card
|
// ── Embedded fallback facts (used when offline / API not yet loaded) ──
|
||||||
const AW_FACTS = {
|
const AW_FACTS_FALLBACK = {
|
||||||
it: [
|
it: [
|
||||||
"In Italia ogni famiglia spreca in media 5,2 kg di cibo al mese",
|
"Nel 2024 ogni italiano spreca ~554 g di cibo a settimana (Waste Watcher 2024)",
|
||||||
"Il 30% del cibo prodotto nel mondo viene sprecato ogni anno",
|
"Lo spreco domestico in Italia vale oltre €7,5 miliardi l'anno",
|
||||||
"Sprecare 1 kg di manzo genera ~27 kg di CO₂ nell'atmosfera",
|
"La frutta fresca è l'alimento più sprecato in Italia: ~22g/persona/settimana",
|
||||||
"Frutta e verdura sono gli alimenti più sprecati nelle famiglie italiane",
|
"Nel mondo si sprecano ~1,05 miliardi di tonnellate di cibo ogni anno (UNEP 2024)",
|
||||||
"Un italiano spende in media ~€450/anno in cibo mai consumato",
|
"Il 19% del cibo globale disponibile al consumo viene buttato (UNEP 2024)",
|
||||||
"L'8% delle emissioni mondiali di gas serra proviene dallo spreco alimentare",
|
"Le famiglie sono responsabili del 60% dello spreco alimentare totale",
|
||||||
"Ridurre lo spreco del 50% potrebbe sfamare 800 milioni di persone in più",
|
"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",
|
"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: [
|
de: [
|
||||||
"Deutsche Haushalte werfen pro Person rund 82 kg Lebensmittel pro Jahr weg",
|
"Deutsche Haushalte werfen pro Person rund 82 kg Lebensmittel pro Jahr weg (Destatis 2024)",
|
||||||
"Weltweit endet etwa 30% aller produzierten Lebensmittel als Abfall",
|
"Weltweit werden ~1,05 Milliarden Tonnen Lebensmittel pro Jahr verschwendet (UNEP 2024)",
|
||||||
"Lebensmittelverschwendung verursacht 8% der globalen Treibhausgase",
|
"19% des global verfügbaren Lebensmittelangebots landet im Müll (UNEP 2024)",
|
||||||
"Obst und Gemüse werden in deutschen Haushalten am häufigsten weggeworfen",
|
"Haushalte verursachen 60% der gesamten Lebensmittelverschwendung",
|
||||||
"Ein Deutscher zahlt im Schnitt ~€300/Jahr für ungenutzte Lebensmittel",
|
"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",
|
"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: [
|
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)",
|
"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",
|
"Americans spend ~$1,800/year on food they never eat",
|
||||||
"Fruits & vegetables are the most wasted food items worldwide",
|
"Just a quarter of wasted food would feed all the world's hungry",
|
||||||
"Wasting 1 lb of beef emits as much CO₂ as driving 20 miles",
|
"Smart packaging could cut food waste by 15%",
|
||||||
"Halving food waste globally could feed 800 million more people",
|
"1 kg of wasted bread = 1,300 litres of water wasted",
|
||||||
"If food waste were a country, it'd be the world's 3rd largest emitter",
|
"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. */
|
/** Fetch fresh stats and re-render the anti-waste section. */
|
||||||
function _awFetchAndRender() {
|
function _awFetchAndRender() {
|
||||||
if (!navigator.onLine) { _updateAwLiveDot(false); return; }
|
if (!navigator.onLine) { _updateAwLiveDot(false); return; }
|
||||||
@@ -2150,6 +2207,35 @@ function _startAntiWasteAutoRefresh() {
|
|||||||
if (navigator.onLine) _awRefreshTimer = setInterval(_awFetchAndRender, 60_000);
|
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. */
|
/** Build one trend mini-card. */
|
||||||
function _awTrendCard(rate, label, maxRate) {
|
function _awTrendCard(rate, label, maxRate) {
|
||||||
if (rate === null) {
|
if (rate === null) {
|
||||||
@@ -2217,7 +2303,7 @@ function _renderAntiWasteSection(used30, wasted30, usedP30, wastedP30, usedP60,
|
|||||||
statusCls = 'aw-status-ok';
|
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 scale = Math.max(myRate, avgRate, 5) * 1.35;
|
||||||
const youFillPct = +((myRate / scale) * 100).toFixed(1);
|
const youFillPct = +((myRate / scale) * 100).toFixed(1);
|
||||||
const avgTickPct = +((avgRate / 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 arr2 = _awTrendArrow(rates[1], rates[2]);
|
||||||
const arrowHtml = a => a ? `<span class="aw-tc-arrow ${a.cls}">${a.sym}</span>` : '';
|
const arrowHtml = a => a ? `<span class="aw-tc-arrow ${a.cls}">${a.sym}</span>` : '';
|
||||||
|
|
||||||
// Badges — richer info chips
|
// Build all badge objects (shown 3 at a time, rotated)
|
||||||
const diffPct = avgRate - myRate; // positive = you're better
|
const diffPct = avgRate - myRate;
|
||||||
const badges = [];
|
const allBadges = [];
|
||||||
// Always show your waste rate vs avg
|
allBadges.push(`<span class="aw-badge aw-badge-rate">
|
||||||
badges.push(`<span class="aw-badge aw-badge-rate">
|
|
||||||
<span class="aw-badge-icon">📊</span>
|
<span class="aw-badge-icon">📊</span>
|
||||||
<span class="aw-badge-body"><b>${myRate}%</b><small>${t('antiwaste.badge_rate')}</small></span>
|
<span class="aw-badge-body"><b>${myRate}%</b><small>${t('antiwaste.badge_rate')}</small></span>
|
||||||
</span>`);
|
</span>`);
|
||||||
if (savedMoney > 0) badges.push(`<span class="aw-badge aw-badge-money">
|
if (wasted30 > 0) allBadges.push(`<span class="aw-badge aw-badge-wasted">
|
||||||
<span class="aw-badge-icon">💰</span>
|
|
||||||
<span class="aw-badge-body"><b>${bm.currency}${savedMoney}/m</b><small>${t('antiwaste.badge_saved_money')}</small></span>
|
|
||||||
</span>`);
|
|
||||||
if (savedMeals > 0) badges.push(`<span class="aw-badge aw-badge-meals">
|
|
||||||
<span class="aw-badge-icon">🥗</span>
|
|
||||||
<span class="aw-badge-body"><b>${savedMeals}</b><small>${t('antiwaste.badge_meals')}</small></span>
|
|
||||||
</span>`);
|
|
||||||
if (savedCO2 > 0) badges.push(`<span class="aw-badge aw-badge-co2">
|
|
||||||
<span class="aw-badge-icon">🌍</span>
|
|
||||||
<span class="aw-badge-body"><b>−${savedCO2} kg</b><small>CO₂</small></span>
|
|
||||||
</span>`);
|
|
||||||
if (wasted30 > 0) badges.push(`<span class="aw-badge aw-badge-wasted">
|
|
||||||
<span class="aw-badge-icon">🗑️</span>
|
<span class="aw-badge-icon">🗑️</span>
|
||||||
<span class="aw-badge-body"><b>${wasted30}</b><small>${t('antiwaste.badge_wasted')}</small></span>
|
<span class="aw-badge-body"><b>${wasted30}</b><small>${t('antiwaste.badge_wasted')}</small></span>
|
||||||
</span>`);
|
</span>`);
|
||||||
if (diffPct > 0) badges.push(`<span class="aw-badge aw-badge-better">
|
if (savedMoney > 0) allBadges.push(`<span class="aw-badge aw-badge-money">
|
||||||
|
<span class="aw-badge-icon">💰</span>
|
||||||
|
<span class="aw-badge-body"><b>${bm.currency}${savedMoney}/m</b><small>${t('antiwaste.badge_saved_money')}</small></span>
|
||||||
|
</span>`);
|
||||||
|
if (savedMeals > 0) allBadges.push(`<span class="aw-badge aw-badge-meals">
|
||||||
|
<span class="aw-badge-icon">🥗</span>
|
||||||
|
<span class="aw-badge-body"><b>${savedMeals}</b><small>${t('antiwaste.badge_meals')}</small></span>
|
||||||
|
</span>`);
|
||||||
|
if (savedCO2 > 0) allBadges.push(`<span class="aw-badge aw-badge-co2">
|
||||||
|
<span class="aw-badge-icon">🌍</span>
|
||||||
|
<span class="aw-badge-body"><b>−${savedCO2} kg</b><small>CO₂</small></span>
|
||||||
|
</span>`);
|
||||||
|
if (diffPct > 0) allBadges.push(`<span class="aw-badge aw-badge-better">
|
||||||
<span class="aw-badge-icon">✅</span>
|
<span class="aw-badge-icon">✅</span>
|
||||||
<span class="aw-badge-body"><b>−${diffPct}%</b><small>${t('antiwaste.badge_better')}</small></span>
|
<span class="aw-badge-body"><b>−${diffPct}%</b><small>${t('antiwaste.badge_better')}</small></span>
|
||||||
</span>`);
|
</span>`);
|
||||||
|
|
||||||
// Random starting fact
|
// Initial 3-badge slice
|
||||||
const facts = AW_FACTS[_currentLang] || AW_FACTS['it'];
|
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 factIdx = Math.floor(Math.random() * facts.length);
|
||||||
|
|
||||||
const liveCls = isOnline ? 'aw-live-on' : 'aw-live-off';
|
const liveCls = isOnline ? 'aw-live-on' : 'aw-live-off';
|
||||||
@@ -2293,7 +2382,7 @@ function _renderAntiWasteSection(used30, wasted30, usedP30, wastedP30, usedP60,
|
|||||||
<p class="aw-status-inline ${statusCls}">${statusMsg}</p>
|
<p class="aw-status-inline ${statusCls}">${statusMsg}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${badges.length > 0 ? `<div class="aw-savings-row">${badges.join('')}</div>` : ''}
|
${allBadges.length > 0 ? `<div id="aw-badges-row" class="aw-savings-row">${initBadges}</div>` : ''}
|
||||||
|
|
||||||
${hasTrend ? `<div class="aw-trend-cards">
|
${hasTrend ? `<div class="aw-trend-cards">
|
||||||
${_awTrendCard(rates[0], labels[0], maxTrend)}
|
${_awTrendCard(rates[0], labels[0], maxTrend)}
|
||||||
@@ -2308,10 +2397,13 @@ function _renderAntiWasteSection(used30, wasted30, usedP30, wastedP30, usedP60,
|
|||||||
<span id="aw-fact-text" class="aw-fact-text">${facts[factIdx]}</span>
|
<span id="aw-fact-text" class="aw-fact-text">${facts[factIdx]}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="aw-source">${t('antiwaste.source')}</div>
|
<div class="aw-source">${(_awLiveFacts && _awLiveFacts.source) || t('antiwaste.source')}</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// 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 (_awFactTimer) clearInterval(_awFactTimer);
|
||||||
if (facts.length > 1) {
|
if (facts.length > 1) {
|
||||||
let idx = factIdx;
|
let idx = factIdx;
|
||||||
@@ -2426,7 +2518,8 @@ async function loadDashboard() {
|
|||||||
// Banner alerts (suspicious quantities + consumption predictions)
|
// Banner alerts (suspicious quantities + consumption predictions)
|
||||||
loadBannerAlerts();
|
loadBannerAlerts();
|
||||||
|
|
||||||
// Anti-waste section
|
// Anti-waste section (load facts first so rotation has full dataset)
|
||||||
|
await _awLoadFacts();
|
||||||
_renderAntiWasteSection(
|
_renderAntiWasteSection(
|
||||||
statsData.used_30d || 0, statsData.wasted_30d || 0,
|
statsData.used_30d || 0, statsData.wasted_30d || 0,
|
||||||
statsData.used_prev_30d || 0, statsData.wasted_prev_30d || 0,
|
statsData.used_prev_30d || 0, statsData.wasted_prev_30d || 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user