payfrit-portal/compare.html
Zara f8ea11601e Add Payfrit User Portal — scanner, dashboard, compare pages
Consumer-facing portal with mobile-first design:
- scan.html: Barcode scanner with product search, health score ring animation, and healthier alternatives display
- dashboard.html: User dashboard with recent scans, health trends chart, favorites grid, Connected Apps section, and settings
- compare.html: Side-by-side product comparison with winner banner, nutrition breakdown, and ingredient highlights
- products.json: Sample product data with scores, descriptions, and alternatives

Connected Apps section lets users manage third-party integrations
(Apple Health, MyFitnessPal, Google Fit, etc.) with permission controls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 05:54:19 +00:00

773 lines
26 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Compare Products — Payfrit</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0f1117;
--card: #1a1d27;
--card2: #1e2130;
--border: #2a2e3e;
--accent: #6c63ff;
--accent2: #00c9a7;
--text: #f0f0f5;
--muted: #8b8fa8;
--red: #ff5c5c;
--yellow: #f5a623;
--green: #00c9a7;
--radius: 16px;
--font: 'Segoe UI', system-ui, -apple-system, sans-serif;
}
body {
background: var(--bg);
color: var(--text);
font-family: var(--font);
min-height: 100vh;
padding-bottom: 40px;
}
/* ── Header ── */
.header {
background: var(--card);
border-bottom: 1px solid var(--border);
padding: 16px 20px;
display: flex;
align-items: center;
gap: 12px;
position: sticky;
top: 0;
z-index: 100;
}
.header .logo {
font-size: 1.3rem;
font-weight: 800;
background: linear-gradient(135deg, var(--accent), var(--accent2));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header .subtitle {
font-size: 0.8rem;
color: var(--muted);
margin-top: 1px;
}
/* ── Main wrapper ── */
.main { padding: 20px 16px; max-width: 680px; margin: 0 auto; }
/* ── Selector section ── */
.selector-section { margin-bottom: 24px; }
.selector-section h2 {
font-size: 1rem;
font-weight: 600;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 14px;
}
.selectors {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: 10px;
}
.vs-badge {
font-size: 0.75rem;
font-weight: 800;
color: var(--muted);
background: var(--card2);
border: 1px solid var(--border);
border-radius: 50px;
padding: 6px 10px;
text-align: center;
}
.product-slot {
background: var(--card);
border: 2px dashed var(--border);
border-radius: var(--radius);
padding: 14px;
cursor: pointer;
transition: border-color 0.2s, background 0.2s;
text-align: center;
min-height: 90px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
position: relative;
}
.product-slot:hover { border-color: var(--accent); }
.product-slot.filled {
border-style: solid;
border-color: var(--accent);
background: var(--card2);
text-align: left;
align-items: flex-start;
}
.product-slot .slot-emoji { font-size: 1.8rem; }
.product-slot .slot-prompt { font-size: 0.85rem; color: var(--muted); }
.product-slot .slot-name { font-size: 0.95rem; font-weight: 700; line-height: 1.3; }
.product-slot .slot-brand { font-size: 0.75rem; color: var(--muted); margin-top: 2px; }
.product-slot .slot-score {
font-size: 0.75rem;
font-weight: 700;
border-radius: 20px;
padding: 2px 8px;
margin-top: 4px;
}
.product-slot .clear-btn {
position: absolute;
top: 8px; right: 8px;
background: var(--border);
border: none;
color: var(--muted);
border-radius: 50%;
width: 22px; height: 22px;
font-size: 0.75rem;
cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: background 0.2s;
}
.product-slot .clear-btn:hover { background: var(--red); color: #fff; }
/* ── Compare CTA ── */
.compare-cta {
margin-top: 16px;
text-align: center;
}
.compare-btn {
background: linear-gradient(135deg, var(--accent), #8b83ff);
color: #fff;
border: none;
border-radius: 50px;
padding: 14px 36px;
font-size: 1rem;
font-weight: 700;
cursor: pointer;
width: 100%;
transition: opacity 0.2s, transform 0.1s;
}
.compare-btn:hover:not(:disabled) { opacity: 0.9; transform: translateY(-1px); }
.compare-btn:active:not(:disabled) { transform: translateY(0); }
.compare-btn:disabled { opacity: 0.35; cursor: not-allowed; }
/* ── Product picker modal ── */
.modal-overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.75);
backdrop-filter: blur(4px);
z-index: 200;
display: none;
align-items: flex-end;
justify-content: center;
}
.modal-overlay.open { display: flex; }
.modal {
background: var(--card);
border-radius: 24px 24px 0 0;
border: 1px solid var(--border);
width: 100%;
max-width: 680px;
max-height: 70vh;
overflow-y: auto;
padding: 24px 16px 32px;
animation: slideUp 0.25s ease;
}
@keyframes slideUp {
from { transform: translateY(100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.modal-handle {
width: 40px; height: 4px;
background: var(--border);
border-radius: 2px;
margin: 0 auto 20px;
}
.modal h3 {
font-size: 1.1rem;
font-weight: 700;
margin-bottom: 16px;
text-align: center;
}
.picker-list { display: flex; flex-direction: column; gap: 10px; }
.picker-item {
display: flex;
align-items: center;
gap: 14px;
background: var(--card2);
border: 1px solid var(--border);
border-radius: 12px;
padding: 12px 14px;
cursor: pointer;
transition: border-color 0.2s, background 0.2s;
}
.picker-item:hover { border-color: var(--accent); background: #252840; }
.picker-item.disabled { opacity: 0.35; pointer-events: none; }
.picker-item .p-emoji { font-size: 1.6rem; flex-shrink: 0; }
.picker-item .p-info { flex: 1; min-width: 0; }
.picker-item .p-name { font-size: 0.95rem; font-weight: 600; }
.picker-item .p-brand { font-size: 0.8rem; color: var(--muted); margin-top: 2px; }
.picker-item .p-cat {
font-size: 0.7rem;
background: var(--border);
border-radius: 20px;
padding: 2px 7px;
color: var(--muted);
margin-top: 4px;
display: inline-block;
}
.picker-score {
font-size: 0.9rem;
font-weight: 800;
border-radius: 8px;
padding: 4px 8px;
flex-shrink: 0;
}
/* ── Comparison result ── */
.comparison { display: none; margin-top: 10px; }
.comparison.visible { display: block; }
.winner-banner {
background: linear-gradient(135deg, rgba(108,99,255,0.15), rgba(0,201,167,0.15));
border: 1px solid rgba(0,201,167,0.3);
border-radius: var(--radius);
padding: 16px 20px;
text-align: center;
margin-bottom: 20px;
}
.winner-banner .trophy { font-size: 2rem; display: block; margin-bottom: 6px; }
.winner-banner .winner-label { font-size: 0.75rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; }
.winner-banner .winner-name { font-size: 1.2rem; font-weight: 800; margin-top: 4px; }
.winner-banner .winner-reason { font-size: 0.82rem; color: var(--muted); margin-top: 6px; line-height: 1.5; }
/* Side-by-side grid */
.compare-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 16px;
}
.product-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 16px;
position: relative;
overflow: hidden;
}
.product-card.winner { border-color: var(--green); }
.product-card.winner::after {
content: 'WINNER';
position: absolute;
top: 10px; right: -22px;
background: var(--green);
color: #000;
font-size: 0.55rem;
font-weight: 800;
letter-spacing: 0.08em;
padding: 3px 28px;
transform: rotate(35deg);
}
.product-card .card-emoji { font-size: 2.2rem; display: block; margin-bottom: 10px; }
.product-card .card-name { font-size: 0.9rem; font-weight: 700; line-height: 1.3; }
.product-card .card-brand { font-size: 0.75rem; color: var(--muted); margin-top: 3px; }
.product-card .card-size { font-size: 0.7rem; color: var(--muted); margin-top: 2px; }
.score-display {
margin: 14px 0 10px;
text-align: center;
}
.score-circle {
width: 56px; height: 56px;
border-radius: 50%;
border: 3px solid;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
margin: 0 auto 6px;
}
.score-circle .num { font-size: 1.2rem; font-weight: 900; line-height: 1; }
.score-circle .denom { font-size: 0.55rem; color: var(--muted); }
.score-bar-wrap {
background: var(--border);
border-radius: 4px;
height: 6px;
overflow: hidden;
margin-top: 8px;
}
.score-bar {
height: 100%;
border-radius: 4px;
transition: width 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.score-label { font-size: 0.7rem; color: var(--muted); margin-top: 6px; line-height: 1.4; text-align: center; }
/* Stat rows */
.stat-section { margin-top: 12px; }
.stat-section h4 { font-size: 0.7rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 8px; }
.stat-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.78rem;
padding: 5px 0;
border-bottom: 1px solid var(--border);
}
.stat-row:last-child { border-bottom: none; }
.stat-row .label { color: var(--muted); }
.stat-row .value { font-weight: 600; }
.stat-row .badge {
font-size: 0.65rem;
border-radius: 20px;
padding: 1px 6px;
font-weight: 700;
}
/* Ingredient tags */
.ingredients { margin-top: 10px; }
.ingredients h4 { font-size: 0.7rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 8px; }
.tag-list { display: flex; flex-wrap: wrap; gap: 5px; }
.tag {
font-size: 0.68rem;
border-radius: 20px;
padding: 3px 8px;
background: var(--border);
color: var(--muted);
}
.tag.good { background: rgba(0,201,167,0.15); color: var(--green); }
.tag.bad { background: rgba(255,92,92,0.15); color: var(--red); }
/* Full-width breakdown rows */
.breakdown-section { margin-bottom: 16px; }
.breakdown-section h3 {
font-size: 0.8rem;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 12px;
}
.breakdown-row {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: 8px;
padding: 10px 0;
border-bottom: 1px solid var(--border);
}
.breakdown-row:last-child { border-bottom: none; }
.breakdown-row .left { text-align: right; }
.breakdown-row .right { text-align: left; }
.breakdown-row .metric-name {
font-size: 0.72rem;
color: var(--muted);
text-align: center;
font-weight: 600;
}
.breakdown-row .val {
font-size: 0.85rem;
font-weight: 700;
}
.breakdown-row .val.win { color: var(--green); }
.breakdown-row .val.lose { color: var(--red); opacity: 0.7; }
/* Score color helpers */
.score-red { color: var(--red); border-color: var(--red); }
.score-yellow{ color: var(--yellow); border-color: var(--yellow); }
.score-green { color: var(--green); border-color: var(--green); }
.bar-red { background: var(--red); }
.bar-yellow { background: var(--yellow); }
.bar-green { background: var(--green); }
.badge-red { background: rgba(255,92,92,0.15); color: var(--red); }
.badge-yellow { background: rgba(245,166,35,0.15); color: var(--yellow); }
.badge-green { background: rgba(0,201,167,0.15); color: var(--green); }
/* Reset btn */
.reset-btn {
display: block;
width: 100%;
margin-top: 24px;
background: var(--card);
border: 1px solid var(--border);
color: var(--muted);
border-radius: 50px;
padding: 12px;
font-size: 0.9rem;
cursor: pointer;
transition: border-color 0.2s, color 0.2s;
}
.reset-btn:hover { border-color: var(--accent); color: var(--text); }
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
</style>
</head>
<body>
<!-- Header -->
<header class="header">
<div>
<div class="logo">Payfrit</div>
<div class="subtitle">Product Compare</div>
</div>
</header>
<!-- Main -->
<main class="main">
<!-- Selectors -->
<section class="selector-section">
<h2>Choose Two Products</h2>
<div class="selectors">
<div class="product-slot" id="slot-a" onclick="openPicker('a')">
<span class="slot-emoji"></span>
<span class="slot-prompt">Tap to pick product A</span>
</div>
<div class="vs-badge">VS</div>
<div class="product-slot" id="slot-b" onclick="openPicker('b')">
<span class="slot-emoji"></span>
<span class="slot-prompt">Tap to pick product B</span>
</div>
</div>
<div class="compare-cta">
<button class="compare-btn" id="compare-btn" onclick="runComparison()" disabled>
Compare Products
</button>
</div>
</section>
<!-- Comparison result -->
<section class="comparison" id="comparison">
<!-- Winner banner -->
<div class="winner-banner" id="winner-banner">
<span class="trophy">🏆</span>
<div class="winner-label">Healthier Choice</div>
<div class="winner-name" id="winner-name"></div>
<div class="winner-reason" id="winner-reason"></div>
</div>
<!-- Side by side cards -->
<div class="compare-grid" id="compare-grid">
<!-- Populated by JS -->
</div>
<!-- Head-to-head breakdown -->
<div class="breakdown-section">
<h3>Head-to-Head Breakdown</h3>
<div id="breakdown-rows"></div>
</div>
<!-- Reset -->
<button class="reset-btn" onclick="resetAll()">↩ Compare Different Products</button>
</section>
</main>
<!-- Product Picker Modal -->
<div class="modal-overlay" id="modal" onclick="closePickerOnOverlay(event)">
<div class="modal">
<div class="modal-handle"></div>
<h3 id="modal-title">Pick a Product</h3>
<div class="picker-list" id="picker-list"></div>
</div>
</div>
<script>
// ── State ──────────────────────────────────────────────
let allProducts = [];
let selectedA = null;
let selectedB = null;
let activePicker = null; // 'a' | 'b'
// ── Mock ingredient data (keyed by product id) ─────────
const INGREDIENTS = {
p001: {
positives: ['Vitamin C', 'Potassium', 'No preservatives'],
negatives: ['High sugar', 'Pasteurized']
},
p002: {
positives: ['Potatoes', 'Sunflower oil'],
negatives: ['High sodium', 'Saturated fat', 'Artificial flavors']
},
p003: {
positives: ['Whole grains', 'Organic seeds', 'High fiber', 'No HFCS'],
negatives: ['Some added sugar']
},
p004: {
positives: ['Live cultures', 'High protein', 'Calcium'],
negatives: ['Added sugar', 'Artificial fruit flavor']
},
p005: {
positives: ['Whole wheat', 'Iron', 'Fiber'],
negatives: ['Sugar coating', 'Processed', 'Low protein']
}
};
// Mock nutrition data per product
const NUTRITION = {
p001: { calories: 110, sugar: '22g', sodium: '0mg', fiber: '0g', protein: '1g' },
p002: { calories: 160, sugar: '1g', sodium: '170mg', fiber: '1g', protein: '2g' },
p003: { calories: 120, sugar: '5g', sodium: '170mg', fiber: '3g', protein: '5g' },
p004: { calories: 130, sugar: '12g', sodium: '65mg', fiber: '0g', protein: '11g' },
p005: { calories: 180, sugar: '11g', sodium: '5mg', fiber: '5g', protein: '5g' }
};
// ── Load products ──────────────────────────────────────
async function loadProducts() {
try {
const res = await fetch('products.json');
const data = await res.json();
allProducts = data.products;
} catch (e) {
// Fallback static data if fetch fails (e.g. file:// protocol)
allProducts = [
{ id:'p001', name:'Orange Juice Original', brand:'Tropicana', category:'Beverages', size:'52 fl oz', emoji:'🥤', score:4.5, scoreDesc:'High sugar content.' },
{ id:'p002', name:'Classic Potato Chips', brand:"Lay's", category:'Snacks', size:'8 oz', emoji:'🥔', score:3.2, scoreDesc:'High sodium & fat.' },
{ id:'p003', name:'Whole Grain Bread', brand:"Dave's Killer Bread", category:'Bakery', size:'27 oz', emoji:'🍞', score:8.2, scoreDesc:'Excellent fiber & protein.' },
{ id:'p004', name:'Greek Yogurt Strawberry', brand:'Chobani', category:'Dairy', size:'5.3 oz', emoji:'🫙', score:7.1, scoreDesc:'Good protein, some added sugar.' },
{ id:'p005', name:'Frosted Mini-Wheats', brand:"Kellogg's", category:'Cereal', size:'18 oz', emoji:'🌾', score:5.8, scoreDesc:'Good fiber, sugar coating.' }
];
}
}
// ── Score helpers ──────────────────────────────────────
function scoreClass(s) {
if (s >= 7.5) return 'green';
if (s >= 5) return 'yellow';
return 'red';
}
function scoreGrade(s) {
if (s >= 9) return 'Excellent';
if (s >= 7.5) return 'Great';
if (s >= 6) return 'Good';
if (s >= 4) return 'Fair';
return 'Poor';
}
// ── Picker ─────────────────────────────────────────────
function openPicker(slot) {
activePicker = slot;
const modal = document.getElementById('modal');
const title = document.getElementById('modal-title');
const list = document.getElementById('picker-list');
title.textContent = slot === 'a' ? 'Pick Product A' : 'Pick Product B';
list.innerHTML = '';
const other = slot === 'a' ? selectedB : selectedA;
allProducts.forEach(p => {
const disabled = other && other.id === p.id;
const sc = scoreClass(p.score);
const div = document.createElement('div');
div.className = 'picker-item' + (disabled ? ' disabled' : '');
div.innerHTML = `
<span class="p-emoji">${p.emoji}</span>
<div class="p-info">
<div class="p-name">${p.name}</div>
<div class="p-brand">${p.brand}</div>
<span class="p-cat">${p.category}</span>
</div>
<span class="picker-score score-${sc}">${p.score}</span>
`;
if (!disabled) div.onclick = () => selectProduct(slot, p);
list.appendChild(div);
});
modal.classList.add('open');
}
function closeModal() {
document.getElementById('modal').classList.remove('open');
activePicker = null;
}
function closePickerOnOverlay(e) {
if (e.target.id === 'modal') closeModal();
}
function selectProduct(slot, product) {
if (slot === 'a') { selectedA = product; }
else { selectedB = product; }
updateSlotUI(slot, product);
closeModal();
updateCompareBtn();
}
function updateSlotUI(slot, product) {
const el = document.getElementById(`slot-${slot}`);
const sc = scoreClass(product.score);
el.className = 'product-slot filled';
el.onclick = () => openPicker(slot);
el.innerHTML = `
<button class="clear-btn" onclick="clearSlot(event,'${slot}')">✕</button>
<span class="slot-emoji">${product.emoji}</span>
<div class="slot-name">${product.name}</div>
<div class="slot-brand">${product.brand}</div>
<span class="slot-score badge-${sc}">Score: ${product.score}</span>
`;
}
function clearSlot(e, slot) {
e.stopPropagation();
if (slot === 'a') selectedA = null;
else selectedB = null;
const el = document.getElementById(`slot-${slot}`);
el.className = 'product-slot';
el.onclick = () => openPicker(slot);
el.innerHTML = `
<span class="slot-emoji"></span>
<span class="slot-prompt">Tap to pick product ${slot.toUpperCase()}</span>
`;
updateCompareBtn();
document.getElementById('comparison').classList.remove('visible');
}
function updateCompareBtn() {
document.getElementById('compare-btn').disabled = !(selectedA && selectedB);
}
// ── Run comparison ─────────────────────────────────────
function runComparison() {
if (!selectedA || !selectedB) return;
const a = selectedA;
const b = selectedB;
const winner = a.score >= b.score ? a : b;
const loser = a.score >= b.score ? b : a;
const diff = Math.abs(a.score - b.score).toFixed(1);
// Winner banner
document.getElementById('winner-name').textContent = `${winner.emoji} ${winner.name}`;
document.getElementById('winner-reason').textContent =
diff == 0
? 'These products are equally healthy. You pick!'
: `Scores ${diff} points higher — ${winner.scoreDesc || 'Better nutritional profile.'}`;
// Cards
const grid = document.getElementById('compare-grid');
grid.innerHTML = buildCard(a, a.id === winner.id) + buildCard(b, b.id === winner.id);
// Animate bars
requestAnimationFrame(() => {
['a-bar','b-bar'].forEach(id => {
const el = document.getElementById(id);
if (el) el.style.width = el.dataset.target;
});
});
// Breakdown rows
buildBreakdown(a, b);
// Show section
document.getElementById('comparison').classList.add('visible');
document.getElementById('comparison').scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function buildCard(p, isWinner) {
const sc = scoreClass(p.score);
const ing = INGREDIENTS[p.id] || { positives: [], negatives: [] };
const nut = NUTRITION[p.id] || { calories:'-', sugar:'-', sodium:'-', fiber:'-', protein:'-' };
const pct = (p.score / 10 * 100).toFixed(0) + '%';
const grade = scoreGrade(p.score);
const posTags = ing.positives.map(t => `<span class="tag good">✓ ${t}</span>`).join('');
const negTags = ing.negatives.map(t => `<span class="tag bad">✗ ${t}</span>`).join('');
return `
<div class="product-card ${isWinner ? 'winner' : ''}">
<span class="card-emoji">${p.emoji}</span>
<div class="card-name">${p.name}</div>
<div class="card-brand">${p.brand}</div>
<div class="card-size">${p.size || ''}</div>
<div class="score-display">
<div class="score-circle score-${sc}">
<span class="num">${p.score}</span>
<span class="denom">/10</span>
</div>
<div class="score-bar-wrap">
<div class="score-bar bar-${sc}" id="${p.id === selectedA.id ? 'a' : 'b'}-bar"
style="width:0%" data-target="${pct}"></div>
</div>
<div class="score-label">${grade}</div>
</div>
<div class="stat-section">
<h4>Nutrition</h4>
<div class="stat-row"><span class="label">Calories</span><span class="value">${nut.calories} kcal</span></div>
<div class="stat-row"><span class="label">Sugar</span><span class="value">${nut.sugar}</span></div>
<div class="stat-row"><span class="label">Sodium</span><span class="value">${nut.sodium}</span></div>
<div class="stat-row"><span class="label">Fiber</span><span class="value">${nut.fiber}</span></div>
<div class="stat-row"><span class="label">Protein</span><span class="value">${nut.protein}</span></div>
</div>
<div class="ingredients">
<h4>Highlights</h4>
<div class="tag-list">${posTags}${negTags}</div>
</div>
</div>
`;
}
function buildBreakdown(a, b) {
const na = NUTRITION[a.id] || {};
const nb = NUTRITION[b.id] || {};
const metrics = [
{ label: 'Health Score', va: a.score, vb: b.score, higherBetter: true, fmt: v => v },
{ label: 'Calories', va: na.calories||0, vb: nb.calories||0, higherBetter: false, fmt: v => v + ' kcal' },
{ label: 'Sugar', va: parseFloat(na.sugar)||0, vb: parseFloat(nb.sugar)||0, higherBetter: false, fmt: (v,raw) => raw },
{ label: 'Sodium', va: parseFloat(na.sodium)||0, vb: parseFloat(nb.sodium)||0, higherBetter: false, fmt: (v,raw) => raw },
{ label: 'Fiber', va: parseFloat(na.fiber)||0, vb: parseFloat(nb.fiber)||0, higherBetter: true, fmt: (v,raw) => raw },
{ label: 'Protein', va: parseFloat(na.protein)||0,vb: parseFloat(nb.protein)||0,higherBetter: true, fmt: (v,raw) => raw },
];
const rawA = [a.score, na.calories, na.sugar, na.sodium, na.fiber, na.protein];
const rawB = [b.score, nb.calories, nb.sugar, nb.sodium, nb.fiber, nb.protein];
let html = '';
metrics.forEach((m, i) => {
const aWins = m.higherBetter ? m.va >= m.vb : m.va <= m.vb;
const aClass = aWins ? 'win' : 'lose';
const bClass = aWins ? 'lose' : 'win';
const aDisplay = m.fmt(m.va, rawA[i]);
const bDisplay = m.fmt(m.vb, rawB[i]);
html += `
<div class="breakdown-row">
<div class="left"><span class="val ${aClass}">${aDisplay}</span></div>
<div class="metric-name">${m.label}</div>
<div class="right"><span class="val ${bClass}">${bDisplay}</span></div>
</div>
`;
});
document.getElementById('breakdown-rows').innerHTML = html;
}
// ── Reset ──────────────────────────────────────────────
function resetAll() {
clearSlot({ stopPropagation:()=>{} }, 'a');
clearSlot({ stopPropagation:()=>{} }, 'b');
document.getElementById('comparison').classList.remove('visible');
window.scrollTo({ top: 0, behavior: 'smooth' });
}
// ── Init ───────────────────────────────────────────────
loadProducts();
</script>
</body>
</html>