diff --git a/api/database.php b/api/database.php
index fe3f6f7..19efb2a 100644
--- a/api/database.php
+++ b/api/database.php
@@ -269,7 +269,9 @@ function estimateOpenedExpiryDaysPHP(string $name, string $category, string $loc
// ── F: Fridge — short-life perishables ──────────────────────────────
if (preg_match('/latte\s+(fresco|intero|parzial|scremato)/', $n)) return 3;
- if (preg_match('/latte\s+(uht|a\s+lunga)/', $n)) return 5;
+ if (preg_match('/latte\s+(uht|a\s+lunga)/', $n)) return 7;
+ // Long-life mountain/brand milks stored in pantry before use (UHT)
+ if (preg_match('/latte.*(montagna|alta\s+qual|parmalat|granarolo|esselunga|conservaz|microfiltrat)/i', $n)) return 7;
if (preg_match('/\blatte\b/', $n)) return 4;
if (preg_match('/\byogurt\b/', $n)) return 5;
if (preg_match('/mozzarella|burrata|stracciatella/', $n)) return 3;
diff --git a/api/index.php b/api/index.php
index 2104a27..240b8c0 100644
--- a/api/index.php
+++ b/api/index.php
@@ -1529,6 +1529,7 @@ function getStats(PDO $db): void {
COALESCE(i.vacuum_sealed, 0) as vacuum_sealed
FROM inventory i JOIN products p ON i.product_id = p.id
WHERE i.expiry_date IS NOT NULL AND i.expiry_date >= date('now') AND i.quantity > 0
+ AND (i.opened_at IS NULL OR i.opened_at = '')
ORDER BY i.expiry_date ASC
LIMIT 4
")->fetchAll();
diff --git a/assets/css/style.css b/assets/css/style.css
index 4485a0e..2a99489 100644
--- a/assets/css/style.css
+++ b/assets/css/style.css
@@ -378,8 +378,9 @@ body {
/* ── Anti-Waste Report Card ─────────────────────────────── */
#waste-chart-section {
- background: var(--bg-card);
- border: 1px solid var(--border);
+ background: linear-gradient(160deg, #f0fdf4 0%, var(--bg-card) 70%);
+ border: 2px solid #86efac;
+ border-left: 4px solid var(--success);
border-radius: var(--radius);
padding: 10px 12px;
margin-bottom: 10px;
@@ -498,16 +499,27 @@ body {
.aw-badge {
display: inline-flex;
align-items: center;
- gap: 4px;
- padding: 2px 8px;
- border-radius: 999px;
- font-size: 0.7rem;
+ gap: 5px;
+ padding: 4px 9px;
+ border-radius: 10px;
+ font-size: 0.72rem;
font-weight: 600;
border: 1px solid transparent;
}
-.aw-badge-money { background: #fef9c3; color: #854d0e; border-color: #fde047; }
-.aw-badge-meals { background: #f0fdf4; color: #166534; border-color: #86efac; }
-.aw-badge-co2 { background: #eff6ff; color: #1e40af; border-color: #93c5fd; }
+.aw-badge-icon { font-size: 0.85rem; flex-shrink: 0; }
+.aw-badge-body {
+ display: flex;
+ flex-direction: column;
+ line-height: 1.2;
+}
+.aw-badge-body b { font-size: 0.78rem; }
+.aw-badge-body small { font-size: 0.6rem; font-weight: 500; opacity: .75; }
+.aw-badge-rate { background: #f5f3ff; color: #5b21b6; border-color: #c4b5fd; }
+.aw-badge-money { background: #fef9c3; color: #854d0e; border-color: #fde047; }
+.aw-badge-meals { background: #f0fdf4; color: #166534; border-color: #86efac; }
+.aw-badge-co2 { background: #eff6ff; color: #1e40af; border-color: #93c5fd; }
+.aw-badge-wasted { background: #fef2f2; color: #991b1b; border-color: #fca5a5; }
+.aw-badge-better { background: #f0fdf4; color: #15803d; border-color: #4ade80; }
/* ── Trend mini-cards ───────────────────────────────────── */
.aw-trend-cards {
diff --git a/assets/js/app.js b/assets/js/app.js
index a68d1a9..62270a9 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -1337,7 +1337,9 @@ function estimateOpenedExpiryDays(product, location) {
}
if (/latte\s+(fresco|intero|parzial|scremato)/.test(name)) return 3;
- if (/latte\s+(uht|a\s+lunga)/.test(name)) return 5;
+ if (/latte\s+(uht|a\s+lunga)/.test(name)) return 7;
+ // Long-life mountain/brand milks stored in pantry before use (UHT)
+ if (/latte.*(montagna|alta\s+qual|parmalat|granarolo|esselunga|conservaz|microfiltrat)/i.test(name)) return 7;
if (/\blatte\b/.test(name)) return 4;
if (/\byogurt\b/.test(name)) return 5;
if (/mozzarella|burrata|stracciatella/.test(name)) return 3;
@@ -2234,11 +2236,34 @@ function _renderAntiWasteSection(used30, wasted30, usedP30, wastedP30, usedP60,
const arr2 = _awTrendArrow(rates[1], rates[2]);
const arrowHtml = a => a ? `${a.sym}` : '';
- // Badges
+ // Badges — richer info chips
+ const diffPct = avgRate - myRate; // positive = you're better
const badges = [];
- if (savedMoney > 0) badges.push(`💰 ${bm.currency}${savedMoney}`);
- if (savedMeals > 0) badges.push(`🥗 ${savedMeals} ${t('antiwaste.meals')}`);
- if (savedCO2 > 0) badges.push(`🌍 −${savedCO2} kg CO₂`);
+ // Always show your waste rate vs avg
+ badges.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(`
+ 🗑️
+ ${wasted30}${t('antiwaste.badge_wasted')}
+ `);
+ if (diffPct > 0) badges.push(`
+ ✅
+ −${diffPct}%${t('antiwaste.badge_better')}
+ `);
// Random starting fact
const facts = AW_FACTS[_currentLang] || AW_FACTS['it'];
diff --git a/translations/de.json b/translations/de.json
index fda69d2..00eb1a5 100644
--- a/translations/de.json
+++ b/translations/de.json
@@ -696,7 +696,12 @@
"source": "Quellen: REDUCE, Eurostat, USDA 2021",
"live_on": "Live-Daten",
"live_off": "Offline",
- "meals": "Mahlzeiten"
+ "meals": "Mahlzeiten",
+ "badge_rate": "Abfallquote",
+ "badge_saved_money": "gespart vs Ø",
+ "badge_meals": "Mahlzeiten gerettet",
+ "badge_wasted": "verschwendet",
+ "badge_better": "weniger als Ø"
},
"error": {
"generic": "Fehler",
diff --git a/translations/en.json b/translations/en.json
index 526f861..ccfe85d 100644
--- a/translations/en.json
+++ b/translations/en.json
@@ -696,7 +696,12 @@
"source": "Sources: REDUCE, Eurostat, USDA 2021",
"live_on": "Live data",
"live_off": "Offline",
- "meals": "meals"
+ "meals": "meals",
+ "badge_rate": "waste rate",
+ "badge_saved_money": "saved vs avg",
+ "badge_meals": "meals saved",
+ "badge_wasted": "items wasted",
+ "badge_better": "less than avg"
},
"error": {
"generic": "Error",
diff --git a/translations/it.json b/translations/it.json
index 0cae976..2c6b36f 100644
--- a/translations/it.json
+++ b/translations/it.json
@@ -696,7 +696,12 @@
"source": "Fonti: REDUCE, Eurostat, USDA 2021",
"live_on": "Dati in tempo reale",
"live_off": "Offline",
- "meals": "pasti"
+ "meals": "pasti",
+ "badge_rate": "tasso spreco",
+ "badge_saved_money": "risparmio vs media",
+ "badge_meals": "pasti salvati",
+ "badge_wasted": "prod. sprecati",
+ "badge_better": "in meno vs media"
},
"error": {
"generic": "Errore",