Korapati's picture
Upload 4 files
9b2830a verified
/* ═══════════════════════════════════════════
NutriVision β€” script.js
═══════════════════════════════════════════ */
/* ── Hamburger ── */
const hamburger = document.getElementById('hamburger');
const mobileMenu = document.getElementById('mobileMenu');
if (hamburger) {
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('open');
mobileMenu.classList.toggle('open');
});
}
/* ── Navbar scroll shadow ── */
window.addEventListener('scroll', () => {
const nb = document.getElementById('navbar');
if (nb) nb.style.boxShadow = window.scrollY > 10 ? '0 2px 24px rgba(0,0,0,.4)' : 'none';
});
/* ── Reveal on scroll ── */
const revealEls = document.querySelectorAll('.reveal');
if (revealEls.length) {
const io = new IntersectionObserver(entries => {
entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('in-view'); io.unobserve(e.target); } });
}, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });
revealEls.forEach(el => io.observe(el));
}
/* ── Counter animation ── */
function animateCounter(el, target, duration = 1200) {
const start = performance.now();
const update = (now) => {
const progress = Math.min((now - start) / duration, 1);
const ease = 1 - Math.pow(1 - progress, 3);
el.textContent = Math.floor(ease * target);
if (progress < 1) requestAnimationFrame(update);
else el.textContent = target;
};
requestAnimationFrame(update);
}
document.querySelectorAll('.counter').forEach(el => {
const io = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) { animateCounter(el, +el.dataset.to); io.unobserve(el); }
});
}, { threshold: 0.5 });
io.observe(el);
});
/* ════════════════════════════════
ANALYZER PAGE
════════════════════════════════ */
const uploadZone = document.getElementById('uploadZone');
const imgInput = document.getElementById('imgInput');
const uploadPlaceholder = document.getElementById('uploadPlaceholder');
const uploadPreview = document.getElementById('uploadPreview');
const previewImg = document.getElementById('previewImg');
const removeImgBtn = document.getElementById('removeImg');
const analyzerForm = document.getElementById('analyzerForm');
const loadingOverlay = document.getElementById('loadingOverlay');
const resultsPanel = document.getElementById('resultsPanel');
if (uploadZone) {
uploadZone.addEventListener('click', () => {
if (uploadPreview.style.display === 'none' || !uploadPreview.style.display) imgInput.click();
});
uploadZone.addEventListener('dragover', e => { e.preventDefault(); uploadZone.classList.add('dragover'); });
uploadZone.addEventListener('dragleave', () => uploadZone.classList.remove('dragover'));
uploadZone.addEventListener('drop', e => {
e.preventDefault(); uploadZone.classList.remove('dragover');
if (e.dataTransfer.files[0]) handleFile(e.dataTransfer.files[0]);
});
imgInput.addEventListener('change', e => { if (e.target.files[0]) handleFile(e.target.files[0]); });
removeImgBtn.addEventListener('click', e => {
e.stopPropagation();
imgInput.value = '';
previewImg.src = '';
uploadPreview.style.display = 'none';
uploadPlaceholder.style.display = 'block';
});
}
function handleFile(file) {
const valid = ['image/png','image/jpeg','image/jpg','image/webp'];
if (!valid.includes(file.type)) { showToast('⚠️ Please upload PNG, JPG, JPEG, or WebP', 'error'); return; }
if (file.size > 16 * 1024 * 1024) { showToast('⚠️ File too large. Max 16MB.', 'error'); return; }
const dt = new DataTransfer();
dt.items.add(file);
imgInput.files = dt.files;
const reader = new FileReader();
reader.onload = e => {
previewImg.src = e.target.result;
uploadPlaceholder.style.display = 'none';
uploadPreview.style.display = 'block';
};
reader.readAsDataURL(file);
}
/* ── Form submit ── */
if (analyzerForm) {
analyzerForm.addEventListener('submit', async e => {
e.preventDefault();
if (!imgInput.files || !imgInput.files[0]) {
showToast('⚠️ Please upload a food image first', 'error'); return;
}
showLoading();
const fd = new FormData(analyzerForm);
try {
stepProgress(1);
const resp = await fetch('/analyze', { method: 'POST', body: fd });
stepProgress(2);
const data = await resp.json();
stepProgress(3);
if (!resp.ok) throw new Error(data.error || 'Analysis failed');
stepProgress(4);
await sleep(400);
hideLoading();
renderResults(data);
resultsPanel.style.display = 'flex';
setTimeout(() => resultsPanel.scrollIntoView({ behavior: 'smooth', block: 'start' }), 200);
} catch (err) {
hideLoading();
showToast('❌ ' + err.message, 'error');
}
});
}
/* ── Loading helpers ── */
function showLoading() {
loadingOverlay.classList.add('active');
document.querySelectorAll('.lstep').forEach(s => s.classList.remove('active'));
}
function hideLoading() { loadingOverlay.classList.remove('active'); }
function stepProgress(n) {
const el = document.getElementById('lstep' + n);
if (el) el.classList.add('active');
}
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
/* ── Render results ── */
function renderResults(d) {
// Banner
document.getElementById('r_food').textContent = d.food || 'Unknown Food';
document.getElementById('r_confidence').textContent = '🎯 ' + (d.confidence || 'β€”');
document.getElementById('r_source').textContent = 'πŸ€– ' + (d.detection_source || 'β€”');
// BMI
if (d.bmi) {
document.getElementById('r_bmi').textContent = d.bmi;
document.getElementById('r_bmiCat').textContent = d.bmi_category || 'β€”';
const bmiCat = (d.bmi_category || '').toLowerCase();
const bmiVal = document.getElementById('r_bmi');
if (bmiCat.includes('obese')) bmiVal.style.color = '#f47171';
else if (bmiCat.includes('overweight')) bmiVal.style.color = '#f5c842';
else if (bmiCat.includes('under')) bmiVal.style.color = '#60a5fa';
else bmiVal.style.color = 'var(--primary)';
}
// Nutrition grid
const nutr = d.nutrition || {};
const nutriMap = [
{ key: 'calories', label: 'Calories', emoji: 'πŸ”₯' },
{ key: 'protein', label: 'Protein', emoji: 'πŸ’ͺ' },
{ key: 'carbohydrates', label: 'Carbs', emoji: '🌾' },
{ key: 'fat', label: 'Fat', emoji: 'πŸ₯‘' },
{ key: 'fiber', label: 'Fiber', emoji: '🌿' },
{ key: 'sugar', label: 'Sugar', emoji: '🍬' },
{ key: 'sodium', label: 'Sodium', emoji: 'πŸ§‚' },
{ key: 'serving_size', label: 'Serving', emoji: 'πŸ₯£' },
];
const ngEl = document.getElementById('r_nutrition');
ngEl.innerHTML = '';
nutriMap.forEach(item => {
const val = nutr[item.key];
if (!val) return;
const div = document.createElement('div');
div.className = 'nutri-item';
div.innerHTML = `<div class="nutri-val">${item.emoji} ${val}</div><div class="nutri-label">${item.label}</div>`;
ngEl.appendChild(div);
});
// Benefits
const bList = document.getElementById('r_benefits');
bList.innerHTML = '';
(d.health_benefits || []).forEach(b => {
const li = document.createElement('li');
li.textContent = b;
bList.appendChild(li);
});
// Portion
document.getElementById('r_portion').textContent = d.portion_advice || '1 standard serving';
// Health context
const ctx = document.getElementById('contextCard');
if (d.health_context) {
ctx.style.display = 'block';
document.getElementById('r_context').textContent = d.health_context;
} else { ctx.style.display = 'none'; }
// Alternatives
const altCard = document.getElementById('altCard');
const altList = document.getElementById('r_alternatives');
altList.innerHTML = '';
if (d.alternatives && d.alternatives.length) {
altCard.style.display = 'block';
d.alternatives.forEach(alt => {
const div = document.createElement('div');
div.className = 'alt-item';
let urlsHtml = '';
if (alt.urls && alt.urls.length) {
const links = alt.urls.map(u =>
`<a href="${u.url}" target="_blank" rel="noopener" class="buy-link">${u.emoji || ''} ${u.platform}</a>`
).join('');
urlsHtml = `<div class="alt-buy"><span class="buy-label">πŸ›’ Buy from:</span>${links}</div>`;
}
div.innerHTML = `
<div class="alt-name">βœ“ ${alt.name}</div>
<div class="alt-reason">${alt.reason}</div>
${urlsHtml}
`;
altList.appendChild(div);
});
} else { altCard.style.display = 'none'; }
}
/* ── Reset analyzer ── */
function resetAnalyzer() {
if (imgInput) { imgInput.value = ''; }
if (previewImg) { previewImg.src = ''; }
if (uploadPreview) { uploadPreview.style.display = 'none'; }
if (uploadPlaceholder) { uploadPlaceholder.style.display = 'block'; }
if (resultsPanel) { resultsPanel.style.display = 'none'; }
window.scrollTo({ top: 0, behavior: 'smooth' });
}
/* ── Toast notifications ── */
function showToast(msg, type = 'info') {
const t = document.createElement('div');
t.style.cssText = `
position:fixed;bottom:1.5rem;right:1.5rem;z-index:99999;
padding:.85rem 1.4rem;border-radius:12px;font-size:.9rem;font-weight:600;
background:${type==='error'?'#f47171':'var(--primary)'};
color:${type==='error'?'#fff':'#080d14'};
box-shadow:0 8px 24px rgba(0,0,0,.4);
animation:slideUp .3s ease both;
max-width:320px;line-height:1.4;
`;
t.textContent = msg;
document.body.appendChild(t);
setTimeout(() => { t.style.opacity='0'; t.style.transition='.3s'; setTimeout(()=>t.remove(),300); }, 3500);
}
console.log('πŸ₯— NutriVision loaded βœ…');