import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js"; import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-auth.js"; import { getFirestore, collection, addDoc, deleteDoc, doc, onSnapshot, query, orderBy, where, setDoc, getDoc, limit } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-firestore.js"; // --- CONFIGURATION --- const firebaseConfig = { apiKey: "AIzaSyACa6exkcvxegYEsudPpw9puJt7NcZQYrY", authDomain: "calories-75d65.firebaseapp.com", projectId: "calories-75d65", storageBucket: "calories-75d65.firebasestorage.app", messagingSenderId: "875037676758", appId: "1:875037676758:web:3db8c1bf501f08d740e577", measurementId: "G-G9H9Q84MEG" }; const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); const MEALS_COL = "meals"; const PREFS_COL = "user_settings"; // State let currentUser = null; let unsubscribeMeals = null; let PROFILE = { calories: 2500, protein: 180, carbs: 250, fat: 80, weight: 180, tdee: 2500 }; // UI Refs const authView = document.getElementById('auth-view'); const appView = document.getElementById('app-view'); const errorMsg = document.getElementById('auth-error'); const modal = document.getElementById('settings-modal'); // Display & Bar Refs const displays = { cals: document.getElementById('total-cals'), prot: document.getElementById('total-prot'), carb: document.getElementById('total-carb'), fat: document.getElementById('total-fat'), predWeight: document.getElementById('pred-weight'), predDiff: document.getElementById('pred-diff'), calPercent: document.getElementById('cal-percent'), calBarFill: document.getElementById('cal-bar-fill'), protBar: document.getElementById('prot-bar'), carbBar: document.getElementById('carb-bar'), fatBar: document.getElementById('fat-bar') }; const ring = document.getElementById('ring-cals'); const circumference = 2 * Math.PI * 42; // Initialize ring ring.style.strokeDasharray = `${circumference} ${circumference}`; ring.style.strokeDashoffset = circumference; // Set greeting based on time function setGreeting() { const hour = new Date().getHours(); const greeting = document.getElementById('greeting'); if (hour < 12) greeting.textContent = 'Good morning'; else if (hour < 18) greeting.textContent = 'Good afternoon'; else greeting.textContent = 'Good evening'; } // --- AUTH HANDLERS --- document.getElementById('login-btn').addEventListener('click', () => { const e = document.getElementById('email').value; const p = document.getElementById('password').value; errorMsg.innerText = "Signing in..."; signInWithEmailAndPassword(auth, e, p).catch(err => showError(err)); }); document.getElementById('signup-btn').addEventListener('click', () => { const e = document.getElementById('email').value; const p = document.getElementById('password').value; errorMsg.innerText = "Creating account..."; createUserWithEmailAndPassword(auth, e, p).catch(err => showError(err)); }); document.getElementById('logout-btn').addEventListener('click', () => signOut(auth)); onAuthStateChanged(auth, (user) => { if (user) { currentUser = user; authView.classList.add('hidden'); appView.classList.remove('hidden'); setGreeting(); loadSettings(); loadMeals(); } else { currentUser = null; authView.classList.remove('hidden'); appView.classList.add('hidden'); if(unsubscribeMeals) unsubscribeMeals(); errorMsg.innerText = ""; } }); function showError(error) { let msg = error.code.replace("auth/", "").replace(/-/g, " "); errorMsg.innerText = "Error: " + msg; } // --- SETTINGS HANDLERS --- document.getElementById('settings-btn').addEventListener('click', () => { document.getElementById('set-cals').value = PROFILE.calories; document.getElementById('set-prot').value = PROFILE.protein; document.getElementById('set-carb').value = PROFILE.carbs; document.getElementById('set-fat').value = PROFILE.fat; document.getElementById('set-weight').value = PROFILE.weight || 180; document.getElementById('set-tdee').value = PROFILE.tdee || 2000; modal.style.display = 'flex'; }); document.getElementById('close-settings').addEventListener('click', () => modal.style.display = 'none'); modal.addEventListener('click', (e) => { if (e.target === modal) modal.style.display = 'none'; }); document.getElementById('settings-form').addEventListener('submit', async (e) => { e.preventDefault(); const newProfile = { calories: parseInt(document.getElementById('set-cals').value) || 2000, protein: parseInt(document.getElementById('set-prot').value) || 150, carbs: parseInt(document.getElementById('set-carb').value) || 200, fat: parseInt(document.getElementById('set-fat').value) || 60, weight: parseFloat(document.getElementById('set-weight').value) || 180, tdee: parseInt(document.getElementById('set-tdee').value) || 2000, }; try { await setDoc(doc(db, PREFS_COL, currentUser.uid), newProfile); PROFILE = newProfile; updateGoalsUI(); modal.style.display = 'none'; location.reload(); } catch (e) { alert("Error saving settings: " + e.message); } }); async function loadSettings() { const docSnap = await getDoc(doc(db, PREFS_COL, currentUser.uid)); if (docSnap.exists()) PROFILE = docSnap.data(); updateGoalsUI(); } function updateGoalsUI() { document.getElementById('goal-label').innerText = `of ${PROFILE.calories} kcal`; } // --- DATA LOGIC --- function loadMeals() { const q = query( collection(db, MEALS_COL), where("uid", "==", currentUser.uid), orderBy("timestamp", "desc"), limit(50) ); unsubscribeMeals = onSnapshot(q, (snap) => { const entries = []; snap.forEach(doc => entries.push({ ...doc.data(), id: doc.id })); render(entries); }, (error) => { console.error(error); if(error.message.includes("index")) { alert("Database Index Missing. Check console for link."); } } ); } document.getElementById('tracker-form').addEventListener('submit', async (e) => { e.preventDefault(); if(!currentUser) return; const btn = document.getElementById('submit-btn'); const btnText = btn.querySelector('span'); btnText.innerText = "Adding..."; try { await addDoc(collection(db, MEALS_COL), { uid: currentUser.uid, name: document.getElementById('in-name').value, cals: parseInt(document.getElementById('in-cals').value) || 0, prot: parseInt(document.getElementById('in-prot').value) || 0, carb: parseInt(document.getElementById('in-carb').value) || 0, fat: parseInt(document.getElementById('in-fat').value) || 0, timestamp: Date.now() }); e.target.reset(); document.getElementById('in-name').focus(); } catch (e) { alert("Error adding meal: " + e.message); } btnText.innerText = "Add Entry"; }); window.deleteEntry = async (id) => { if(confirm("Delete this entry?")) await deleteDoc(doc(db, MEALS_COL, id)); } // --- RENDER UI & PREDICTION --- function render(entries) { const todayStr = new Date().toDateString(); const todaysEntries = entries.filter(item => { const itemDate = new Date(item.timestamp).toDateString(); return itemDate === todayStr; }); const totals = todaysEntries.reduce((acc, item) => { acc.cals += item.cals; acc.prot += item.prot; acc.carb += item.carb; acc.fat += item.fat; return acc; }, { cals: 0, prot: 0, carb: 0, fat: 0 }); // Update Main Stats displays.cals.innerText = totals.cals.toLocaleString(); displays.prot.innerText = totals.prot + 'g'; displays.carb.innerText = totals.carb + 'g'; displays.fat.innerText = totals.fat + 'g'; // Calculate percentages const calPercent = Math.min((totals.cals / PROFILE.calories) * 100, 100); const protPercent = Math.min((totals.prot / PROFILE.protein) * 100, 100); const carbPercent = Math.min((totals.carb / PROFILE.carbs) * 100, 100); const fatPercent = Math.min((totals.fat / PROFILE.fat) * 100, 100); // Update percentage display displays.calPercent.innerText = Math.round(calPercent) + '%'; // Update ring const offset = circumference - (calPercent / 100) * circumference; ring.style.strokeDashoffset = offset; // Update bars displays.calBarFill.style.width = calPercent + '%'; displays.protBar.style.width = protPercent + '%'; displays.carbBar.style.width = carbPercent + '%'; displays.fatBar.style.width = fatPercent + '%'; // Over limit styling const goalLabel = document.getElementById('goal-label'); const isOver = totals.cals > PROFILE.calories; displays.cals.classList.toggle('over', isOver); ring.classList.toggle('over', isOver); displays.calBarFill.classList.toggle('over', isOver); goalLabel.classList.toggle('over', isOver); if (isOver) { goalLabel.innerText = `Over by ${(totals.cals - PROFILE.calories).toLocaleString()} kcal`; } else { goalLabel.innerText = `of ${PROFILE.calories.toLocaleString()} kcal`; } // --- WEIGHT PREDICTION --- const dailyDeficit = totals.cals - (PROFILE.tdee || 2000); const totalChangeCals = dailyDeficit * 28; const lbsChange = totalChangeCals / 3500; const futureWeight = (PROFILE.weight || 180) + lbsChange; displays.predWeight.innerText = futureWeight.toFixed(1) + " lbs"; displays.predWeight.classList.toggle('gain', lbsChange > 0); const changeIndicator = document.querySelector('.change-indicator'); const changeText = document.querySelector('.change-text'); changeIndicator.classList.toggle('gain', lbsChange > 0); if (lbsChange > 0) { changeText.innerText = `+${lbsChange.toFixed(1)} lbs gain`; } else { changeText.innerText = `${lbsChange.toFixed(1)} lbs loss`; } // --- LIST RENDER --- const list = document.getElementById('log-list'); list.innerHTML = ''; entries.forEach(item => { const itemDateStr = new Date(item.timestamp).toDateString(); const isToday = itemDateStr === todayStr; const timeLabel = new Date(item.timestamp).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); const dateLabel = isToday ? timeLabel : new Date(item.timestamp).toLocaleDateString([], {month:'short', day:'numeric'}); const li = document.createElement('li'); li.className = `log-item${isToday ? '' : ' old'}`; li.innerHTML = `

${item.name}

${dateLabel}
${item.prot}p ${item.carb}c ${item.fat}f
${item.cals}
`; list.appendChild(li); }); }