KeeTok Meals Planner

KeeTok Meals Planner

Meal Library
๐Ÿณ Breakfast
๐Ÿฅ— Lunch
๐Ÿฝ๏ธ Dinner
๐ŸŽ Snacks
๐Ÿ“ฆ Other
Daily View
Today

Today’s Nutrition

0
Calories
0g
Protein
0g
Carbs
0g
Fats
0g
Sugar
$0.00
Cost
Weekly View
This Week
Sun
Mon
Tue
Wed
Thu
Fri
Sat
Monthly View
This Month
Sun
Mon
Tue
Wed
Thu
Fri
Sat
Pro Nutrition Goals Calculator

Personal Stats

Male
Female

Current Body Fat %

Or set exact:20%

Goal Body Fat %

Or set exact:15%

Daily Activity Level

Mostly sitting
Desk job, minimal exercise
On my feet
Active job OR 1-3x/week
Very active
Physical job OR 4-6x/week
Athlete
Training daily

Eating Style

Balanced
Mix of everything
Higher protein
Meat/fish/eggs focus
Plant-based
Veggies, beans, tofu
Higher fat
Nuts, avocado, oils

Dietary Restrictions

Your Personalized Pro Plan

Daily Targets

2000
Calories/day
50g
Protein
250g
Carbs
65g
Fats

Body Composition

Recommended Plan Phase

Pro Tips

    Shopping List
    Pantry Stock Management

    Stock Awareness

    Today

    Weight Tracker

    First thing after waking
    Before sleep
    โ€”
    Today AM
    โ€”
    7-day avg
    โ€”
    Change
    โ€”
    Goal weight

    Water Intake

    cups (8 oz each)
    0 of 8 cups ยท 0 oz

    Eating Habits

    7-Day Habit Streak

    Full day   Partial   No data
    '; w.document.write(html); w.document.close(); w.focus(); w.print(); w.close(); } function shareShoppingList(){ document.getElementById('share-modal').classList.add('flex'); } function shareViaEmail(){ let body="Shopping List:\n\n"; const by={}; shoppingList.forEach(i=>{const s=i.store||'General';if(!by[s])by[s]=[];by[s].push(i.name);}); for(const s in by){body+=`${s}:\n`;by[s].forEach((item,idx)=>{body+=`${idx+1}. ${item}\n`;});body+='\n';} window.location.href=`mailto:?subject=${encodeURIComponent('My Shopping List')}&body=${encodeURIComponent(body)}`; } function copyShareLink(){ const el=document.getElementById('share-link'); el.select(); document.execCommand('copy'); showToast('Link copied!'); } function closeShareModal(){ document.getElementById('share-modal').classList.remove('flex'); } // ============================================================ // STOCK MANAGEMENT // ============================================================ function updateStockList(){ const cont=document.getElementById('stock-items'); if(!cont) return; cont.innerHTML=''; if(!stockItems.length){cont.innerHTML='

    Pantry is empty

    ';return;} stockItems.forEach((item,idx)=>{ const mealsLeft=item.usagePerMeal>0?Math.floor(item.quantity/item.usagePerMeal):0; let statusClass='stock-status-ok', statusText='OK'; if(item.quantity<=0||mealsLeft<=0){statusClass='stock-status-out';statusText='Out';} else if(item.quantity<=item.reorderPoint||mealsLeft<=5){statusClass='stock-status-low';statusText='Low';} const bp=getBestStorePrice(item); const priceInfo=bp?`Best: ${bp.store} $${bp.price.toFixed(2)}${bp.isSale?' ๐Ÿท':''}${bp.isBulk?' ๐Ÿ“ฆ':''}`:''; const el=document.createElement('div'); el.className='stock-item'; el.innerHTML=`
    ${item.name}
    ${item.category||''}${item.quantity} ${item.unit}${priceInfo}
    ${mealsLeft} meals ${statusText}
    `; el.querySelector('.stock-item-details').addEventListener('click',()=>showStockDetails(idx)); el.querySelector('.stock-item-status').addEventListener('click',()=>showStockDetails(idx)); el.querySelector('.stock-shopping-btn').addEventListener('click',e=>{e.stopPropagation();addStockItemToShoppingList(idx);}); el.querySelector('.inc-btn').addEventListener('click',e=>{e.stopPropagation();increaseStockQuantity(idx);}); el.querySelector('.dec-btn').addEventListener('click',e=>{e.stopPropagation();decreaseStockQuantity(idx);}); cont.appendChild(el); }); updateStockAwareness(); updateStoreFilterOptions(); saveToLocalStorage(); } function getBestStorePrice(item){ if(!item.storePrices||!item.storePrices.length) return null; let best=null; item.storePrices.forEach(sp=>{ const eff=(sp.isSale&&sp.bulkPrice&&parseFloat(sp.bulkPrice)>0)?Math.min(sp.price,parseFloat(sp.bulkPrice)):sp.price; if(!best||eff{ const bp=getBestStorePrice(item); totalVal+=item.quantity*(bp?bp.price:0); const m=item.usagePerMeal>0?Math.floor(item.quantity/item.usagePerMeal):0; if(item.quantity<=0||m<=0){out++;outItems.push({name:item.name,idx});} else if(item.quantity<=item.reorderPoint||m<=5){low++;lowItems.push({name:item.name,idx});} }); let html=`
    Total Items:${stockItems.length}
    Running Low:${low}
    Out of Stock:${out}
    Inventory Value:$${totalVal.toFixed(2)}
    `; const attentionItems=[...outItems,...lowItems.filter(li=>!outItems.some(oi=>oi.name===li.name))]; if(attentionItems.length){ html+=`
    Needs Attention
    `; attentionItems.forEach(a=>{ const bp=getBestStorePrice(stockItems[a.idx]); const storeName=bp?bp.store:'General'; html+=`
    ${a.name}
    `; }); } cont.innerHTML=html; cont.querySelectorAll('.attention-add-btn').forEach(btn=>{ btn.addEventListener('click',function(){ const idx=parseInt(this.dataset.idx); const item=stockItems[idx]; const label=`${item.quantity} ${item.unit} ${item.name}`; addToShoppingList(label,this.dataset.store,this.dataset.sale==='true',this.dataset.bulk==='true',this.dataset.cheapest==='true'); }); }); } function addStockItem(){ const name=document.getElementById('stock-item-input').value.trim(); const qty=parseFloat(document.getElementById('stock-quantity-input').value)||0; const unit=document.getElementById('stock-unit-input').value; if(!name) return; const existing=stockItems.findIndex(i=>i.name.toLowerCase()===name.toLowerCase()&&i.unit===unit); if(existing>=0){stockItems[existing].quantity+=qty;stockItems[existing].lastUpdated=formatDate(new Date());} else{stockItems.push({id:Date.now(),name,quantity:qty,unit,usagePerMeal:1,minimumStock:qty*0.2,reorderPoint:qty*0.3,category:'General',tags:[],notes:'',usedInMeals:[],storePrices:[],lastUpdated:formatDate(new Date())});} document.getElementById('stock-item-input').value=''; document.getElementById('stock-quantity-input').value=''; updateStockList(); } function addStockItemToShoppingList(idx){ const item=stockItems[idx]; const bp=getBestStorePrice(item); const storeName=bp?bp.store:'General'; const label=`${item.quantity} ${item.unit} ${item.name}`; addToShoppingList(label,storeName,bp?bp.isSale:false,bp?bp.isBulk:false,!!bp); } function increaseStockQuantity(idx){stockItems[idx].quantity+=1;updateStockList();saveToLocalStorage();} function decreaseStockQuantity(idx){stockItems[idx].quantity=Math.max(0,stockItems[idx].quantity-1);updateStockList();saveToLocalStorage();} function showStockDetails(idx){ editingStockIndex=idx; const item=stockItems[idx]; document.getElementById('stock-modal-title').textContent=item.name; document.getElementById('stock-detail-name').value=item.name; document.getElementById('stock-detail-quantity').value=item.quantity; document.getElementById('stock-detail-unit').value=item.unit; document.getElementById('stock-detail-category').value=item.category||''; document.getElementById('stock-detail-tags').value=Array.isArray(item.tags)?item.tags.join(', '):item.tags||''; document.getElementById('stock-detail-usage-per-meal').value=item.usagePerMeal; document.getElementById('stock-detail-minimum-stock').value=item.minimumStock; document.getElementById('stock-detail-reorder-point').value=item.reorderPoint; document.getElementById('stock-detail-notes').value=item.notes||''; const m=item.usagePerMeal>0?Math.floor(item.quantity/item.usagePerMeal):0; document.getElementById('stock-meals-remaining').textContent=m; document.getElementById('stock-days-supply').textContent=m; const bp=getBestStorePrice(item); document.getElementById('stock-best-price').textContent=bp?`${bp.store} $${bp.price.toFixed(2)}`:'โ€”'; renderStorePriceRows(item.storePrices||[]); updateBestStoreSummary(item.storePrices||[]); const mc=document.getElementById('stock-used-in-meals'); mc.innerHTML=''; (item.usedInMeals||[]).forEach(mn=>{ const el=document.createElement('div'); el.className='stock-meal-item'; el.innerHTML=`${mn}`; const rmBtn=document.createElement('button'); rmBtn.className='button small'; rmBtn.textContent='Remove'; rmBtn.addEventListener('click',()=>removeMealFromStock(mn,idx)); el.appendChild(rmBtn); mc.appendChild(el); }); if(!item.usedInMeals||!item.usedInMeals.length) mc.innerHTML='

    Not used in any meals

    '; document.getElementById('stock-details-modal').classList.add('flex'); } function renderStorePriceRows(prices){const cont=document.getElementById('store-prices-list');cont.innerHTML='';prices.forEach((sp,i)=>renderOneStoreRow(sp,i));} function renderOneStoreRow(sp,i){ const cont=document.getElementById('store-prices-list'); const row=document.createElement('div'); row.className='store-price-row'; row.dataset.rowIndex=i; row.innerHTML=`
    `; cont.appendChild(row); } function updateStorePriceField(rowIdx,field,value){ if(editingStockIndex<0) return; const prices=stockItems[editingStockIndex].storePrices||[]; if(!prices[rowIdx]) prices[rowIdx]={store:'',price:0,bulkPrice:'',isSale:false,isBulk:false}; if(field==='price'||field==='bulkPrice') prices[rowIdx][field]=parseFloat(value)||0; else prices[rowIdx][field]=value; updateBestStoreSummary(prices); } function addStorePriceRow(){ if(editingStockIndex<0) return; if(!stockItems[editingStockIndex].storePrices) stockItems[editingStockIndex].storePrices=[]; const newEntry={store:'',price:0,bulkPrice:'',isSale:false,isBulk:false}; stockItems[editingStockIndex].storePrices.push(newEntry); renderOneStoreRow(newEntry,stockItems[editingStockIndex].storePrices.length-1); updateBestStoreSummary(stockItems[editingStockIndex].storePrices); } function removeStorePriceRow(i){ if(editingStockIndex<0) return; stockItems[editingStockIndex].storePrices.splice(i,1); renderStorePriceRows(stockItems[editingStockIndex].storePrices); updateBestStoreSummary(stockItems[editingStockIndex].storePrices); } function updateBestStoreSummary(prices){ const el=document.getElementById('best-store-summary'); if(!el) return; if(!prices||!prices.length){el.classList.remove('visible');return;} let best=null; prices.forEach(sp=>{ if(!sp.store) return; const eff=(sp.isSale&&parseFloat(sp.bulkPrice)>0)?Math.min(sp.price,parseFloat(sp.bulkPrice)):sp.price; if(!best||effCheapest: ${best.store} at $${best.eff.toFixed(2)}${best.isSale?'SALE':''}${best.isBulk&&best.bulkPrice?`BULK $${parseFloat(best.bulkPrice).toFixed(2)}`:''}`; } function saveStockItem(){ if(editingStockIndex<0) return; const item=stockItems[editingStockIndex]; item.name=document.getElementById('stock-detail-name').value.trim(); item.quantity=parseFloat(document.getElementById('stock-detail-quantity').value)||0; item.unit=document.getElementById('stock-detail-unit').value; item.category=document.getElementById('stock-detail-category').value.trim(); item.tags=document.getElementById('stock-detail-tags').value.split(',').map(t=>t.trim()).filter(Boolean); item.usagePerMeal=parseFloat(document.getElementById('stock-detail-usage-per-meal').value)||1; item.minimumStock=parseFloat(document.getElementById('stock-detail-minimum-stock').value)||0; item.reorderPoint=parseFloat(document.getElementById('stock-detail-reorder-point').value)||0; item.notes=document.getElementById('stock-detail-notes').value.trim(); item.lastUpdated=formatDate(new Date()); const rows=document.querySelectorAll('#store-prices-list .store-price-row'); item.storePrices=[]; rows.forEach(row=>{ const inputs=row.querySelectorAll('input'); const store=inputs[0].value.trim(); const price=parseFloat(inputs[1].value)||0; const bulkPrice=inputs[2].value; const isSale=inputs[3].checked; const isBulk=inputs[4].checked; if(store) item.storePrices.push({store,price,bulkPrice,isSale,isBulk}); }); updateStockList(); closeStockDetails(); saveToLocalStorage(); showToast('Stock item saved!'); } function deleteStockItem(){ if(editingStockIndex<0) return; if(confirm(`Delete "${stockItems[editingStockIndex].name}"?`)){stockItems.splice(editingStockIndex,1);updateStockList();closeStockDetails();saveToLocalStorage();} } function removeMealFromStock(mealName,stockIdx){stockItems[stockIdx].usedInMeals=(stockItems[stockIdx].usedInMeals||[]).filter(m=>m!==mealName);saveToLocalStorage();showStockDetails(stockIdx);} function closeStockDetails(){editingStockIndex=-1;document.getElementById('stock-details-modal').classList.remove('flex');} // ============================================================ // RECIPE DETAILS // ============================================================ function showRecipeDetails(category, name){ const meal=getMealInfo(category,name); if(!meal){showToast(`Recipe not found: ${name}`,'warning');return;} editingRecipeCategory=category; editingRecipeIndex=mealData[category].findIndex(m=>m.name===name); pendingRecipePhoto=null; document.getElementById('recipe-modal-title').textContent=name; document.getElementById('recipe-basic-info-grid').innerHTML=`
    ${category}
    ${meal.prepTime||0}
    ${meal.cookTime||0}
    ${meal.servings||1}
    ${meal.difficulty||'Easy'}
    ${meal.cost.toFixed(2)}
    `; document.getElementById('recipe-tags').textContent=meal.tags||''; document.getElementById('recipe-serving-suggestions').textContent=meal.servingSuggestions||''; // Photo tab const photoBox=document.getElementById('recipe-photo-preview-box'); if(meal.photo){ photoBox.innerHTML=`blank`; } else { photoBox.innerHTML=``; } // Ingredients const list=document.getElementById('recipe-ingredients-list'); list.innerHTML=''; (meal.ingredients||[]).forEach(ing=>{ const li=document.createElement('li'); const cb=document.createElement('input'); cb.type='checkbox'; const span=document.createElement('span'); span.textContent=ing; const shopBtn=document.createElement('button'); shopBtn.className='add-to-shopping-btn'; shopBtn.textContent='+ Shopping'; shopBtn.dataset.ingredient=ing; shopBtn.addEventListener('click',function(){addIngredientToShoppingList(this.dataset.ingredient);}); const stockBtn=document.createElement('button'); stockBtn.className='add-to-stock-btn'; stockBtn.textContent='+ Stock'; stockBtn.dataset.ingredient=ing; stockBtn.addEventListener('click',function(){addIngredientToStock(this.dataset.ingredient);}); li.appendChild(cb); li.appendChild(span); li.appendChild(shopBtn); li.appendChild(stockBtn); list.appendChild(li); }); document.getElementById('recipe-instructions').value=meal.instructions||''; document.getElementById('recipe-notes').value=meal.notes||''; ['calories','protein','carbs','fats','fiber','sugar'].forEach(k=>{ document.getElementById(`recipe-${k}`).textContent=(meal.nutrition[k]||0)+(k!=='calories'?'g':''); document.getElementById(`edit-${k}`).value=meal.nutrition[k]||0; }); updateRecipeNutritionChart(meal.nutrition.protein,meal.nutrition.carbs,meal.nutrition.fats); isEditingRecipe=false; document.getElementById('recipe-edit-btn').style.display=''; document.getElementById('recipe-save-btn').style.display='none'; document.getElementById('recipe-cancel-btn').style.display='none'; document.getElementById('recipe-instructions').readOnly=true; document.getElementById('recipe-notes').readOnly=true; showRecipeTab('details'); document.getElementById('recipe-details-modal').classList.add('flex'); } function updateRecipeNutritionChart(p,c,f){ const ctx=document.getElementById('recipe-nutrition-chart').getContext('2d'); if(window.recipeNutritionChart) window.recipeNutritionChart.destroy(); window.recipeNutritionChart=new Chart(ctx,{type:'doughnut',data:{labels:['Protein','Carbs','Fats'],datasets:[{data:[p,c,f],backgroundColor:['#4285F4','#FBBC05','#EA4335'],borderWidth:1}]},options:{responsive:true,maintainAspectRatio:false,cutout:'70%',plugins:{legend:{position:'bottom',labels:{boxWidth:12,padding:10,font:{size:10}}}}}}); } function closeRecipeDetails(){document.getElementById('recipe-details-modal').classList.remove('flex');} function showRecipeTab(tab){ document.querySelectorAll('#recipe-details-modal .modal-tab').forEach(t=>t.classList.remove('active')); const activeTab=document.querySelector(`#recipe-details-modal .modal-tab[onclick="showRecipeTab('${tab}')"]`); if(activeTab) activeTab.classList.add('active'); document.querySelectorAll('#recipe-details-modal .modal-tab-content').forEach(c=>c.classList.remove('active')); const cont=document.getElementById(`recipe-${tab}-tab`); if(cont) cont.classList.add('active'); } function toggleRecipeEdit(){ isEditingRecipe=true; document.getElementById('recipe-edit-btn').style.display='none'; document.getElementById('recipe-save-btn').style.display=''; document.getElementById('recipe-cancel-btn').style.display=''; document.getElementById('recipe-instructions').readOnly=false; document.getElementById('recipe-notes').readOnly=false; const meal=mealData[editingRecipeCategory][editingRecipeIndex]; document.getElementById('recipe-basic-info-grid').innerHTML=`
    ${editingRecipeCategory}
    `; const tagsEl=document.getElementById('recipe-tags'); if(tagsEl){ const inp=document.createElement('input'); inp.type='text'; inp.className='detail-input'; inp.id='edit-tags'; inp.value=meal.tags||''; tagsEl.replaceWith(inp); } const ssEl=document.getElementById('recipe-serving-suggestions'); if(ssEl){ const inp=document.createElement('input'); inp.type='text'; inp.className='detail-input'; inp.id='edit-serving-suggestions'; inp.value=meal.servingSuggestions||''; ssEl.replaceWith(inp); } } function saveRecipeChanges(){ if(editingRecipeIndex<0) return; const meal=mealData[editingRecipeCategory][editingRecipeIndex]; meal.prepTime=parseInt(document.getElementById('edit-prep-time')?.value)||0; meal.cookTime=parseInt(document.getElementById('edit-cook-time')?.value)||0; meal.servings=parseInt(document.getElementById('edit-servings')?.value)||1; meal.difficulty=document.getElementById('edit-difficulty')?.value||'Easy'; meal.cost=parseFloat(document.getElementById('edit-cost')?.value)||0; meal.tags=document.getElementById('edit-tags')?.value||''; meal.servingSuggestions=document.getElementById('edit-serving-suggestions')?.value||''; meal.instructions=document.getElementById('recipe-instructions').value; meal.notes=document.getElementById('recipe-notes').value; ['calories','protein','carbs','fats','fiber','sugar'].forEach(k=>{meal.nutrition[k]=parseFloat(document.getElementById(`edit-${k}`)?.value)||0;}); updateMealLibraryDisplay(); updateAddMealModalLibrary(); updateDailyNutrition(formatDate(currentDate)); saveToLocalStorage(); showToast('Recipe saved!'); cancelRecipeEdit(); showRecipeDetails(editingRecipeCategory,meal.name); } function cancelRecipeEdit(){ isEditingRecipe=false; document.getElementById('recipe-edit-btn').style.display=''; document.getElementById('recipe-save-btn').style.display='none'; document.getElementById('recipe-cancel-btn').style.display='none'; document.getElementById('recipe-instructions').readOnly=true; document.getElementById('recipe-notes').readOnly=true; if(editingRecipeIndex>=0&&editingRecipeCategory){const m=mealData[editingRecipeCategory][editingRecipeIndex];if(m) showRecipeDetails(editingRecipeCategory,m.name);} } function addNewIngredient(){ const input=document.getElementById('new-ingredient-input'); const ing=input.value.trim(); if(!ing||editingRecipeIndex<0) return; mealData[editingRecipeCategory][editingRecipeIndex].ingredients.push(ing); const li=document.createElement('li'); const cb=document.createElement('input'); cb.type='checkbox'; const span=document.createElement('span'); span.textContent=ing; const shopBtn=document.createElement('button'); shopBtn.className='add-to-shopping-btn'; shopBtn.textContent='+ Shopping'; shopBtn.dataset.ingredient=ing; shopBtn.addEventListener('click',function(){addIngredientToShoppingList(this.dataset.ingredient);}); li.appendChild(cb); li.appendChild(span); li.appendChild(shopBtn); document.getElementById('recipe-ingredients-list').appendChild(li); input.value=''; saveToLocalStorage(); } function addIngredientToStock(ing){ const parts=ing.split(' '); let qty=1,unit='units',name=ing; if(parts.length>1&&!isNaN(parseFloat(parts[0]))){qty=parseFloat(parts[0]);unit=parts[1];name=parts.slice(2).join(' ');} const existing=stockItems.findIndex(i=>ing.toLowerCase().includes(i.name.toLowerCase())); if(existing>=0){showStockDetails(existing);} else{stockItems.push({id:Date.now(),name,quantity:qty,unit,usagePerMeal:1,minimumStock:qty*0.2,reorderPoint:qty*0.3,category:'General',tags:[],notes:`From recipe: ${ing}`,usedInMeals:[],storePrices:[],lastUpdated:formatDate(new Date())});updateStockList();showToast(`"${name}" added to pantry`);} } // ============================================================ // ADD MEAL MODAL // ============================================================ function showAddMealModal(){document.getElementById('add-meal-modal').classList.add('flex'); updateAddMealModalLibrary();} function closeAddMealModal(){document.getElementById('add-meal-modal').classList.remove('flex');} function showAddMealTab(tab){ document.querySelectorAll('#add-meal-modal .modal-tab').forEach(t=>t.classList.remove('active')); const el=document.querySelector(`#add-meal-modal .modal-tab[onclick="showAddMealTab('${tab}')"]`); if(el) el.classList.add('active'); document.querySelectorAll('#add-meal-modal .modal-tab-content').forEach(c=>c.classList.remove('active')); document.getElementById(`add-meal-${tab}-tab`).classList.add('active'); } function updateAddMealModalLibrary(){ const cont=document.getElementById('add-meal-library-tab'); if(!cont) return; cont.innerHTML=''; for(const cat in mealData){ if(!mealData[cat].length) continue; const sec=document.createElement('div'); let icon='fa-utensils'; if(cat==='breakfast') icon='fa-bread-slice'; else if(cat==='dinner') icon='fa-drumstick-bite'; else if(cat==='snacks') icon='fa-apple-alt'; sec.innerHTML=`
    ${cat.charAt(0).toUpperCase()+cat.slice(1)}
    `; const grid=document.createElement('div'); grid.className='add-meal-items'; mealData[cat].forEach(m=>{ const el=document.createElement('div'); el.className='add-meal-item'; el.dataset.cat=cat; el.dataset.name=m.name; if(m.photo){ el.innerHTML=`${m.name}${m.name}`; } else { el.innerHTML=`${m.name}`; } el.addEventListener('click',function(){addMealFromLibrary(this.dataset.cat,this.dataset.name);}); grid.appendChild(el); }); sec.appendChild(grid); cont.appendChild(sec); } } function addMealFromLibrary(cat,name){addMealToPlan(formatDate(currentDate),cat,name);closeAddMealModal();showToast(`"${name}" added to today's plan`);} function selectIcon(icon,event){ selectedIcon=icon; document.getElementById('icon-preview').innerHTML=``; document.querySelectorAll('.icon-selection-item').forEach(i=>i.classList.remove('selected')); if(event&&event.target) event.target.closest('.icon-selection-item').classList.add('selected'); } function addCustomMealToPlan(){ const name=document.getElementById('custom-meal-name').value.trim(); const type=document.getElementById('custom-meal-type').value; const cost=parseFloat(document.getElementById('custom-meal-cost').value)||0; if(!name) return; if(!mealData[type]) mealData[type]=[]; mealData[type].push({name,cost,icon:selectedIcon,photo:pendingCustomMealPhoto||null,nutrition:{calories:0,protein:0,carbs:0,fats:0,sugar:0,fiber:0},prepTime:0,cookTime:0,servings:1,difficulty:'Easy',ingredients:[],instructions:'',notes:'',tags:'',servingSuggestions:''}); pendingCustomMealPhoto=null; updateMealLibraryDisplay(); updateAddMealModalLibrary(); addMealToPlan(formatDate(currentDate),type,name); document.getElementById('custom-meal-name').value=''; document.getElementById('custom-meal-cost').value=''; document.getElementById('custom-meal-photo-box').innerHTML=``; closeAddMealModal(); saveToLocalStorage(); } // ============================================================ // DELETE HELPERS // ============================================================ function confirmDeleteMeal(cat,name){ selectedMealToDelete=name; selectedMealTypeToDelete=cat; pendingDeleteCallback=confirmDeleteMealAction; document.getElementById('delete-confirm-title').textContent='Delete from Library'; document.getElementById('delete-confirm-msg').textContent=`Remove "${name}" from the meal library?`; document.getElementById('delete-confirm-btn').textContent='Delete'; document.getElementById('delete-confirm-modal').classList.add('flex'); } function confirmDeleteMealAction(){ mealData[selectedMealTypeToDelete]=mealData[selectedMealTypeToDelete].filter(m=>m.name!==selectedMealToDelete); for(const d in mealPlan) mealPlan[d]=mealPlan[d].filter(m=>!(m.type===selectedMealTypeToDelete&&m.name===selectedMealToDelete)); updateMealLibraryDisplay(); updateAddMealModalLibrary(); updateCalendar(); updateDailyNutrition(formatDate(currentDate)); saveToLocalStorage(); } function confirmDeleteMealFromPlan(type,name,date){ if(confirm(`Remove "${name}" from this day?`)){ if(mealPlan[date]){mealPlan[date]=mealPlan[date].filter(m=>!(m.type===type&&m.name===name));updateCalendar();updateDailyNutrition(date);saveToLocalStorage();} } } function confirmDelete(){if(pendingDeleteCallback) pendingDeleteCallback();cancelDelete();} function cancelDelete(){selectedMealToDelete=null;selectedMealTypeToDelete=null;pendingDeleteCallback=null;document.getElementById('delete-confirm-modal').classList.remove('flex');} function showRestartDataConfirm(){ pendingDeleteCallback=resetData; document.getElementById('delete-confirm-title').textContent='Restart All Data'; document.getElementById('delete-confirm-msg').textContent='This will delete ALL data. Cannot be undone.'; document.getElementById('delete-confirm-btn').textContent='Restart'; document.getElementById('delete-confirm-modal').classList.add('flex'); } function resetData(){ mealData={breakfast:[{name:'Oatmeal Example',cost:2.50,icon:'fa-bread-slice',photo:null,nutrition:{calories:300,protein:10,carbs:55,fats:3,sugar:12,fiber:8},prepTime:10,cookTime:15,servings:2,difficulty:'Easy',ingredients:['1 cup rolled oats','2 cups water','1 banana','1 tbsp honey'],instructions:'1. Boil water.\n2. Add oats.\n3. Cook 5 min.',notes:'Use milk for creamier.',tags:'Quick, Healthy',servingSuggestions:'Serve hot'}],lunch:[],dinner:[],snacks:[],other:[]}; mealPlan={}; shoppingList=[]; stockItems=[{id:1,name:'Rolled oats',quantity:2,unit:'kg',usagePerMeal:0.1,minimumStock:0.5,reorderPoint:1,category:'Grains',tags:[],notes:'',usedInMeals:['Oatmeal Example'],storePrices:[{store:'Walmart',price:3.50,bulkPrice:'',isSale:false,isBulk:false}],lastUpdated:'2024-01-15'}]; nutritionGoals={calories:2000,protein:50,carbs:300,fats:65,sugar:25}; habitsData={}; updateMealLibraryDisplay(); updateAddMealModalLibrary(); updateCalendar(); updateShoppingList(); updateStockList(); updateDailyNutrition(formatDate(currentDate)); saveToLocalStorage(); showToast('Data reset!'); closeSettingsModal(); } // ============================================================ // PRO NUTRITION CALCULATOR // ============================================================ function selectActivityLevel(level,el){ selectedActivityLevel=level; document.querySelectorAll('#activity-levels .checkbox-item').forEach(i=>i.classList.remove('selected')); if(el) el.classList.add('selected'); document.querySelectorAll('.activity-checkbox').forEach(c=>c.checked=false); const cb=document.getElementById(`activity-${level}`); if(cb) cb.checked=true; } function selectEatingStyle(style,el){ selectedEatingStyle=style; document.querySelectorAll('#eating-styles .checkbox-item').forEach(i=>i.classList.remove('selected')); if(el) el.classList.add('selected'); document.querySelectorAll('.style-checkbox').forEach(c=>c.checked=false); const cb=document.getElementById(`style-${style}`); if(cb) cb.checked=true; } function toggleRestriction(r){ const cb=document.getElementById(`restriction-${r}`); const item=event.target.closest('.restriction-item'); if(r==='none'){ document.querySelectorAll('#diet-restrictions .restriction-item').forEach(i=>i.classList.remove('selected')); document.querySelectorAll('#diet-restrictions input[type="checkbox"]').forEach(c=>c.checked=false); if(cb) cb.checked=true; if(item) item.classList.add('selected'); selectedRestrictions=['none']; } else { const noneCb=document.getElementById('restriction-none'); if(noneCb&&noneCb.checked){noneCb.checked=false;document.querySelector('#diet-restrictions .restriction-item:first-child')?.classList.remove('selected');selectedRestrictions=[];} if(cb){ cb.checked=!cb.checked; if(item) item.classList.toggle('selected',cb.checked); if(cb.checked){selectedRestrictions=selectedRestrictions.filter(x=>x!=='none');if(!selectedRestrictions.includes(r)) selectedRestrictions.push(r);} else{selectedRestrictions=selectedRestrictions.filter(x=>x!==r);} } } } function calculateNutritionGoals(){ const age=parseInt(document.getElementById('user-age').value)||30; const heightFt=parseInt(document.getElementById('height-ft').value)||5; const heightIn=parseInt(document.getElementById('height-in').value)||9; const weightLbs=parseFloat(document.getElementById('current-weight').value)||180; const goalWeightLbs=parseFloat(document.getElementById('goal-weight').value)||160; const timelineWeeks=parseInt(document.getElementById('timeline').value)||12; const heightCm=((heightFt*12)+heightIn)*2.54; const weightKg=weightLbs*0.453592; let bmr; if(selectedGender==='male'){ bmr=88.362+(13.397*weightKg)+(4.799*heightCm)-(5.677*age); } else { bmr=447.593+(9.247*weightKg)+(3.098*heightCm)-(4.330*age); } let actMult=1.2; if(selectedActivityLevel==='light') actMult=1.375; else if(selectedActivityLevel==='active') actMult=1.55; else if(selectedActivityLevel==='athlete') actMult=1.725; let tdee=bmr*actMult; const currentBF=currentBFPercent/100, goalBF=goalBFPercent/100; const leanMassKg=weightKg*(1-currentBF); const targetWeightKg=goalWeightLbs*0.453592; const fatToLose=weightKg-targetWeightKg; let dailyCalories=tdee, phase='maintain'; if(fatToLose>0){const dailyDeficit=((fatToLose*7700)/timelineWeeks)/7;dailyCalories=tdee-dailyDeficit;phase='cut';} else if(fatToLose<0){const dailySurplus=((Math.abs(fatToLose)*7700)/timelineWeeks)/7;dailyCalories=tdee+dailySurplus;phase='bulk';} else{phase='recomp';} dailyCalories=Math.max(1200,Math.min(4000,dailyCalories)); let proteinMult=2.0; if(phase==='cut') proteinMult=2.2; else if(phase==='bulk') proteinMult=1.8; let proteinGrams=leanMassKg*proteinMult; let fatPct=0.25; if(selectedEatingStyle==='high-fat') fatPct=0.35; let fatGrams=(dailyCalories*fatPct)/9; let carbGrams=Math.max(50,(dailyCalories-(proteinGrams*4)-(fatGrams*9))/4); document.getElementById('result-calories').textContent=Math.round(dailyCalories); document.getElementById('result-protein').textContent=Math.round(proteinGrams)+'g'; document.getElementById('result-carbs').textContent=Math.round(carbGrams)+'g'; document.getElementById('result-fats').textContent=Math.round(fatGrams)+'g'; document.getElementById('results-metrics-row').innerHTML=`
    ${weightLbs} lbs
    Current Weight
    ${goalWeightLbs} lbs
    Goal Weight
    ${currentBFPercent}%
    Current Body Fat
    ${Math.round(leanMassKg*2.20462)} lbs
    Lean Mass
    `; document.getElementById('phase-recommendation').innerHTML=`
    ${phase.toUpperCase()}${phase==='cut'?'Calorie deficit for fat loss':phase==='bulk'?'Calorie surplus for muscle gain':'Maintenance for body recomposition'}
    ${timelineWeeks} weeks to goal
    ${Math.round(Math.abs(fatToLose*2.20462))} lbs fat to ${fatToLose>0?'lose':'gain'}
    `; document.getElementById('tips-list').innerHTML=`
  • ${phase==='cut'?'Focus on protein to preserve muscle mass':phase==='bulk'?'Prioritize complex carbs for energy':'Maintain balanced macros for consistent progress'}
  • Drink ${Math.round(weightLbs*0.5)} oz of water daily
  • Aim for 7-9 hours of quality sleep
  • Track progress weekly, not daily
  • `; updateBodyCompChart(currentBFPercent,goalBFPercent); document.getElementById('nutrition-results').style.display='block'; document.getElementById('settings-calories-goal').value=Math.round(dailyCalories); document.getElementById('settings-protein-goal').value=Math.round(proteinGrams); document.getElementById('settings-carbs-goal').value=Math.round(carbGrams); document.getElementById('settings-fats-goal').value=Math.round(fatGrams); } function updateBodyCompChart(current,goal){ const ctx=document.getElementById('body-comp-chart').getContext('2d'); if(window.bodyCompChart) window.bodyCompChart.destroy(); window.bodyCompChart=new Chart(ctx,{type:'bar',data:{labels:['Current','Goal'],datasets:[{label:'Lean Mass %',data:[100-current,100-goal],backgroundColor:'#4CAF50',borderRadius:6},{label:'Body Fat %',data:[current,goal],backgroundColor:'#FF6B6B',borderRadius:6}]},options:{responsive:true,maintainAspectRatio:true,scales:{x:{stacked:true},y:{stacked:true,max:100,title:{display:true,text:'Percentage (%)'}}},plugins:{tooltip:{callbacks:{label:(ctx)=>`${ctx.dataset.label}: ${ctx.raw}%`}}}}}); } function saveCalculatedGoal(){ const cal=parseInt(document.getElementById('result-calories').textContent)||2000; const pro=parseInt(document.getElementById('result-protein').textContent)||50; const carbs=parseInt(document.getElementById('result-carbs').textContent)||250; const fats=parseInt(document.getElementById('result-fats').textContent)||65; nutritionGoals={calories:cal,protein:pro,carbs:carbs,fats:fats,sugar:nutritionGoals.sugar}; document.getElementById('settings-calories-goal').value=cal; document.getElementById('settings-protein-goal').value=pro; document.getElementById('settings-carbs-goal').value=carbs; document.getElementById('settings-fats-goal').value=fats; document.getElementById('goal-saved-banner').style.display='block'; document.getElementById('banner-cal').textContent=cal; document.getElementById('banner-pro').textContent=pro+'g'; document.getElementById('banner-car').textContent=carbs+'g'; document.getElementById('banner-fat').textContent=fats+'g'; setTimeout(()=>{document.getElementById('goal-saved-banner').style.display='none';},3000); saveToLocalStorage(); showToast('Goals saved!','success'); } function applyNutritionGoals(){saveCalculatedGoal();showMainTab('calendar');updateDailyNutrition(formatDate(currentDate));showToast('Nutrition goals applied!');} function resetNutritionCalculator(){ document.getElementById('user-age').value=''; document.getElementById('height-ft').value=''; document.getElementById('height-in').value=''; document.getElementById('current-weight').value=''; document.getElementById('goal-weight').value=''; document.getElementById('timeline').value=''; selectedGender=''; selectedActivityLevel=''; selectedEatingStyle=''; selectedRestrictions=[]; currentBFPercent=20; goalBFPercent=15; document.getElementById('current-bf-slider').value=20; document.getElementById('current-bf-display').textContent='20%'; document.getElementById('goal-bf-slider').value=15; document.getElementById('goal-bf-display').textContent='15%'; document.querySelectorAll('.gender-option').forEach(o=>o.classList.remove('selected')); document.querySelectorAll('#activity-levels .checkbox-item').forEach(i=>i.classList.remove('selected')); document.querySelectorAll('.activity-checkbox').forEach(c=>c.checked=false); document.querySelectorAll('#eating-styles .checkbox-item').forEach(i=>i.classList.remove('selected')); document.querySelectorAll('.style-checkbox').forEach(c=>c.checked=false); document.querySelectorAll('#diet-restrictions .restriction-item').forEach(i=>i.classList.remove('selected')); document.querySelectorAll('#diet-restrictions input[type="checkbox"]').forEach(c=>c.checked=false); buildBFSelectors(); document.getElementById('nutrition-results').style.display='none'; showToast('Calculator reset'); } // ============================================================ // HABITS TRACKER // ============================================================ function getTodayStr(){return formatDate(habitsDate);} function getHabitsForDate(dateStr){ if(!habitsData[dateStr]){habitsData[dateStr]={morningWeight:null,eveningWeight:null,waterCups:0,waterGoal:8,habits:{},notes:''};} return habitsData[dateStr]; } function renderHabitsTab(){ const dateStr=getTodayStr(); const habits=getHabitsForDate(dateStr); document.getElementById('habits-date-title').textContent=formatDateLong(habitsDate); const morningInput=document.getElementById('weight-morning'); const eveningInput=document.getElementById('weight-evening'); if(morningInput) morningInput.value=habits.morningWeight||''; if(eveningInput) eveningInput.value=habits.eveningWeight||''; const waterGoal=habits.waterGoal||8; document.getElementById('water-goal-cups').value=waterGoal; renderWaterCups(); renderEatingHabitsGrid(habits.habits||{}); const notesArea=document.getElementById('habit-daily-notes'); if(notesArea) notesArea.value=habits.notes||''; updateWeightStats(); renderWeightChart(); renderStreakGrid(); } function habitsNavDay(delta){habitsDate.setDate(habitsDate.getDate()+delta);renderHabitsTab();} function saveWeightEntry(){ const dateStr=getTodayStr(); const habits=getHabitsForDate(dateStr); const morning=parseFloat(document.getElementById('weight-morning').value); const evening=parseFloat(document.getElementById('weight-evening').value); if(!isNaN(morning)) habits.morningWeight=morning; if(!isNaN(evening)) habits.eveningWeight=evening; habitsData[dateStr]=habits; saveToLocalStorage(); updateWeightStats(); renderWeightChart(); } function setWeightUnit(unit){ weightUnit=unit; document.querySelectorAll('.weight-unit-btn').forEach(btn=>btn.classList.remove('active')); document.getElementById(`unit-${unit}`).classList.add('active'); renderWeightChart(); saveToLocalStorage(); } function updateWeightStats(){ const weights=[]; for(let i=6;i>=0;i--){ const d=new Date(habitsDate); d.setDate(habitsDate.getDate()-i); const h=habitsData[formatDate(d)]; if(h&&h.morningWeight) weights.push(h.morningWeight); } const todayWeight=weights[weights.length-1]; const avgWeight=weights.length?weights.reduce((a,b)=>a+b,0)/weights.length:0; const change=weights.length>=2?(weights[weights.length-1]-weights[0]).toFixed(1):0; document.getElementById('wstat-current').textContent=todayWeight?todayWeight.toFixed(1)+(weightUnit==='lbs'?' lbs':' kg'):'โ€”'; document.getElementById('wstat-7avg').textContent=avgWeight?avgWeight.toFixed(1)+(weightUnit==='lbs'?' lbs':' kg'):'โ€”'; document.getElementById('wstat-change').textContent=change?(change>0?'+':'')+change+(weightUnit==='lbs'?' lbs':' kg'):'โ€”'; document.getElementById('wstat-goal').textContent=weightGoal?weightGoal+(weightUnit==='lbs'?' lbs':' kg'):'โ€”'; } function renderWeightChart(){ const ctx=document.getElementById('weight-chart').getContext('2d'); if(window.weightChart) window.weightChart.destroy(); const labels=[],data=[]; for(let i=30;i>=0;i--){ const d=new Date(habitsDate); d.setDate(habitsDate.getDate()-i); const h=habitsData[formatDate(d)]; if(h&&h.morningWeight){labels.push(formatDate(d).slice(5));data.push(h.morningWeight);} } window.weightChart=new Chart(ctx,{type:'line',data:{labels,datasets:[{label:`Weight (${weightUnit})`,data,borderColor:'#4285F4',backgroundColor:'rgba(66,133,244,0.1)',fill:true,tension:0.3}]},options:{responsive:true,maintainAspectRatio:true,plugins:{legend:{position:'top'}},scales:{y:{title:{display:true,text:`Weight (${weightUnit})`}}}}}); } function renderWaterCups(){ const dateStr=getTodayStr(); const habits=getHabitsForDate(dateStr); const goal=parseInt(document.getElementById('water-goal-cups').value)||8; habits.waterGoal=goal; const current=habits.waterCups||0; const container=document.getElementById('water-cups-grid'); container.innerHTML=''; for(let i=1;i<=goal;i++){ const cup=document.createElement('div'); cup.className='water-cup'+(i<=current?' filled':''); cup.innerHTML=``; cup.onclick=()=>toggleWaterCup(i); container.appendChild(cup); } const percentage=(current/goal)*100; document.getElementById('water-progress-fill').style.width=percentage+'%'; document.getElementById('water-summary').textContent=`${current} of ${goal} cups ยท ${current*8} oz`; habitsData[dateStr]=habits; saveToLocalStorage(); } function toggleWaterCup(cupIndex){ const dateStr=getTodayStr(); const habits=getHabitsForDate(dateStr); let current=habits.waterCups||0; if(cupIndex===current+1) current++; else if(cupIndex===current) current--; habits.waterCups=Math.max(0,Math.min(current,habits.waterGoal||8)); habitsData[dateStr]=habits; renderWaterCups(); saveToLocalStorage(); } function renderEatingHabitsGrid(savedHabits){ const container=document.getElementById('eating-habits-grid'); container.innerHTML=''; DEFAULT_EATING_HABITS.forEach(habit=>{ const card=document.createElement('div'); card.className='habit-toggle-card'+(savedHabits[habit.id]?' checked':''); card.innerHTML=`
    ${habit.icon}
    ${habit.name}
    ${habit.detail}
    `; card.onclick=()=>toggleHabit(habit.id); container.appendChild(card); }); const customRow=document.createElement('div'); customRow.className='habit-custom-row'; customRow.innerHTML=``; container.appendChild(customRow); } function toggleHabit(habitId){ const dateStr=getTodayStr(); const habits=getHabitsForDate(dateStr); if(!habits.habits) habits.habits={}; habits.habits[habitId]=!habits.habits[habitId]; habitsData[dateStr]=habits; renderEatingHabitsGrid(habits.habits); renderStreakGrid(); saveToLocalStorage(); } function addCustomHabit(){ const input=document.getElementById('custom-habit-name'); const name=input.value.trim(); if(!name) return; const id='custom_'+Date.now(); DEFAULT_EATING_HABITS.push({id,icon:'โญ',name,detail:'Custom habit'}); input.value=''; renderEatingHabitsGrid(getHabitsForDate(getTodayStr()).habits||{}); showToast('Custom habit added!'); } function saveHabitsEntry(){ const dateStr=getTodayStr(); const habits=getHabitsForDate(dateStr); habits.notes=document.getElementById('habit-daily-notes').value; habitsData[dateStr]=habits; saveToLocalStorage(); } function renderStreakGrid(){ const container=document.getElementById('streak-grid'); container.innerHTML=''; const days=['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; for(let i=6;i>=0;i--){ const d=new Date(habitsDate); d.setDate(habitsDate.getDate()-i); const dateStr=formatDate(d); const habits=habitsData[dateStr]; const isToday=(i===0); let completedCount=0; const totalHabits=DEFAULT_EATING_HABITS.length; if(habits&&habits.habits) completedCount=Object.values(habits.habits).filter(v=>v===true).length; let statusClass='', statusText='โ—‹'; if(completedCount===totalHabits&&totalHabits>0){statusClass='active';statusText='โœ“';} else if(completedCount>0){statusClass='partial';statusText=completedCount;} const dayDiv=document.createElement('div'); dayDiv.className='streak-day'; dayDiv.innerHTML=`
    ${days[d.getDay()]}
    ${statusText}
    `; container.appendChild(dayDiv); } } // ============================================================ // DATA IMPORT/EXPORT // ============================================================ function exportData(){ const exportObj={mealData,mealPlan,shoppingList,stockItems,nutritionGoals,habitsData,version:'2.0'}; const dataStr=JSON.stringify(exportObj,null,2); const blob=new Blob([dataStr],{type:'application/json'}); const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download=`keetok_backup_${formatDate(new Date())}.json`; a.click(); URL.revokeObjectURL(url); showToast('Data exported!'); } document.getElementById('import-data-file').addEventListener('change',function(e){ const file=e.target.files[0]; if(!file) return; const reader=new FileReader(); reader.onload=function(ev){ try{ const data=JSON.parse(ev.target.result); if(data.mealData){ for(const cat in data.mealData){ mealData[cat]=data.mealData[cat].map(m=>({photo:null,...m})); } } if(data.mealPlan) mealPlan=data.mealPlan; if(data.shoppingList) shoppingList=data.shoppingList; if(data.stockItems) stockItems=data.stockItems; if(data.nutritionGoals) nutritionGoals=data.nutritionGoals; if(data.habitsData) habitsData=data.habitsData; updateMealLibraryDisplay(); updateAddMealModalLibrary(); updateCalendar(); updateShoppingList(); updateStockList(); updateDailyNutrition(formatDate(currentDate)); renderHabitsTab(); saveToLocalStorage(); showToast('Data imported!'); }catch(err){showToast('Invalid file','warning');} }; reader.readAsText(file); }); // ============================================================ // INITIALIZATION // ============================================================ function init(){ loadFromLocalStorage(); loadSettings(); selectLibraryView(currentLibraryView, false); updateMealLibraryDisplay(); updateAddMealModalLibrary(); updateCalendar(); updateShoppingList(); updateStockList(); buildBFSelectors(); setupNutritionChart(); applyDisplayOptions(); const savedTab=localStorage.getItem('keetokMainTab'); if(savedTab&&['calendar','nutrition','shopping','stock','habits'].includes(savedTab)) showMainTab(savedTab); renderHabitsTab(); showToast('Welcome to KeeTok Meals Planner! ๐Ÿฝ๏ธ','success',3000); } init();

    '; w.document.write(html); w.document.close(); w.focus(); w.print(); w.close(); } function shareShoppingList(){ document.getElementById('share-modal').classList.add('flex'); } function shareViaEmail(){ let body="Shopping List:\n\n"; const by={}; shoppingList.forEach(i=>{const s=i.store||'General';if(!by[s])by[s]=[];by[s].push(i.name);}); for(const s in by){body+=`${s}:\n`;by[s].forEach((item,idx)=>{body+=`${idx+1}. ${item}\n`;});body+='\n';} window.location.href=`mailto:?subject=${encodeURIComponent('My Shopping List')}&body=${encodeURIComponent(body)}`; } function copyShareLink(){ const el=document.getElementById('share-link'); el.select(); document.execCommand('copy'); showToast('Link copied!'); } function closeShareModal(){ document.getElementById('share-modal').classList.remove('flex'); } // ============================================================ // STOCK MANAGEMENT // ============================================================ function updateStockList(){ const cont=document.getElementById('stock-items'); if(!cont) return; cont.innerHTML=''; if(!stockItems.length){cont.innerHTML='

    Pantry is empty

    ';return;} stockItems.forEach((item,idx)=>{ const mealsLeft=item.usagePerMeal>0?Math.floor(item.quantity/item.usagePerMeal):0; let statusClass='stock-status-ok', statusText='OK'; if(item.quantity<=0||mealsLeft<=0){statusClass='stock-status-out';statusText='Out';} else if(item.quantity<=item.reorderPoint||mealsLeft<=5){statusClass='stock-status-low';statusText='Low';} const bp=getBestStorePrice(item); const priceInfo=bp?`Best: ${bp.store} $${bp.price.toFixed(2)}${bp.isSale?' ๐Ÿท':''}${bp.isBulk?' ๐Ÿ“ฆ':''}`:''; const el=document.createElement('div'); el.className='stock-item'; el.innerHTML=`
    ${item.name}
    ${item.category||''}${item.quantity} ${item.unit}${priceInfo}
    ${mealsLeft} meals ${statusText}
    `; el.querySelector('.stock-item-details').addEventListener('click',()=>showStockDetails(idx)); el.querySelector('.stock-item-status').addEventListener('click',()=>showStockDetails(idx)); el.querySelector('.stock-shopping-btn').addEventListener('click',e=>{e.stopPropagation();addStockItemToShoppingList(idx);}); el.querySelector('.inc-btn').addEventListener('click',e=>{e.stopPropagation();increaseStockQuantity(idx);}); el.querySelector('.dec-btn').addEventListener('click',e=>{e.stopPropagation();decreaseStockQuantity(idx);}); cont.appendChild(el); }); updateStockAwareness(); updateStoreFilterOptions(); saveToLocalStorage(); } function getBestStorePrice(item){ if(!item.storePrices||!item.storePrices.length) return null; let best=null; item.storePrices.forEach(sp=>{ const eff=(sp.isSale&&sp.bulkPrice&&parseFloat(sp.bulkPrice)>0)?Math.min(sp.price,parseFloat(sp.bulkPrice)):sp.price; if(!best||eff{ const bp=getBestStorePrice(item); totalVal+=item.quantity*(bp?bp.price:0); const m=item.usagePerMeal>0?Math.floor(item.quantity/item.usagePerMeal):0; if(item.quantity<=0||m<=0){out++;outItems.push({name:item.name,idx});} else if(item.quantity<=item.reorderPoint||m<=5){low++;lowItems.push({name:item.name,idx});} }); let html=`
    Total Items:${stockItems.length}
    Running Low:${low}
    Out of Stock:${out}
    Inventory Value:$${totalVal.toFixed(2)}
    `; const attentionItems=[...outItems,...lowItems.filter(li=>!outItems.some(oi=>oi.name===li.name))]; if(attentionItems.length){ html+=`
    Needs Attention
    `; attentionItems.forEach(a=>{ const bp=getBestStorePrice(stockItems[a.idx]); const storeName=bp?bp.store:'General'; html+=`
    ${a.name}
    `; }); } cont.innerHTML=html; cont.querySelectorAll('.attention-add-btn').forEach(btn=>{ btn.addEventListener('click',function(){ const idx=parseInt(this.dataset.idx); const item=stockItems[idx]; const label=`${item.quantity} ${item.unit} ${item.name}`; addToShoppingList(label,this.dataset.store,this.dataset.sale==='true',this.dataset.bulk==='true',this.dataset.cheapest==='true'); }); }); } function addStockItem(){ const name=document.getElementById('stock-item-input').value.trim(); const qty=parseFloat(document.getElementById('stock-quantity-input').value)||0; const unit=document.getElementById('stock-unit-input').value; if(!name) return; const existing=stockItems.findIndex(i=>i.name.toLowerCase()===name.toLowerCase()&&i.unit===unit); if(existing>=0){stockItems[existing].quantity+=qty;stockItems[existing].lastUpdated=formatDate(new Date());} else{stockItems.push({id:Date.now(),name,quantity:qty,unit,usagePerMeal:1,minimumStock:qty*0.2,reorderPoint:qty*0.3,category:'General',tags:[],notes:'',usedInMeals:[],storePrices:[],lastUpdated:formatDate(new Date())});} document.getElementById('stock-item-input').value=''; document.getElementById('stock-quantity-input').value=''; updateStockList(); } function addStockItemToShoppingList(idx){ const item=stockItems[idx]; const bp=getBestStorePrice(item); const storeName=bp?bp.store:'General'; const label=`${item.quantity} ${item.unit} ${item.name}`; addToShoppingList(label,storeName,bp?bp.isSale:false,bp?bp.isBulk:false,!!bp); } function increaseStockQuantity(idx){stockItems[idx].quantity+=1;updateStockList();saveToLocalStorage();} function decreaseStockQuantity(idx){stockItems[idx].quantity=Math.max(0,stockItems[idx].quantity-1);updateStockList();saveToLocalStorage();} function showStockDetails(idx){ editingStockIndex=idx; const item=stockItems[idx]; document.getElementById('stock-modal-title').textContent=item.name; document.getElementById('stock-detail-name').value=item.name; document.getElementById('stock-detail-quantity').value=item.quantity; document.getElementById('stock-detail-unit').value=item.unit; document.getElementById('stock-detail-category').value=item.category||''; document.getElementById('stock-detail-tags').value=Array.isArray(item.tags)?item.tags.join(', '):item.tags||''; document.getElementById('stock-detail-usage-per-meal').value=item.usagePerMeal; document.getElementById('stock-detail-minimum-stock').value=item.minimumStock; document.getElementById('stock-detail-reorder-point').value=item.reorderPoint; document.getElementById('stock-detail-notes').value=item.notes||''; const m=item.usagePerMeal>0?Math.floor(item.quantity/item.usagePerMeal):0; document.getElementById('stock-meals-remaining').textContent=m; document.getElementById('stock-days-supply').textContent=m; const bp=getBestStorePrice(item); document.getElementById('stock-best-price').textContent=bp?`${bp.store} $${bp.price.toFixed(2)}`:'โ€”'; renderStorePriceRows(item.storePrices||[]); updateBestStoreSummary(item.storePrices||[]); const mc=document.getElementById('stock-used-in-meals'); mc.innerHTML=''; (item.usedInMeals||[]).forEach(mn=>{ const el=document.createElement('div'); el.className='stock-meal-item'; el.innerHTML=`${mn}`; const rmBtn=document.createElement('button'); rmBtn.className='button small'; rmBtn.textContent='Remove'; rmBtn.addEventListener('click',()=>removeMealFromStock(mn,idx)); el.appendChild(rmBtn); mc.appendChild(el); }); if(!item.usedInMeals||!item.usedInMeals.length) mc.innerHTML='

    Not used in any meals

    '; document.getElementById('stock-details-modal').classList.add('flex'); } function renderStorePriceRows(prices){const cont=document.getElementById('store-prices-list');cont.innerHTML='';prices.forEach((sp,i)=>renderOneStoreRow(sp,i));} function renderOneStoreRow(sp,i){ const cont=document.getElementById('store-prices-list'); const row=document.createElement('div'); row.className='store-price-row'; row.dataset.rowIndex=i; row.innerHTML=`
    `; cont.appendChild(row); } function updateStorePriceField(rowIdx,field,value){ if(editingStockIndex<0) return; const prices=stockItems[editingStockIndex].storePrices||[]; if(!prices[rowIdx]) prices[rowIdx]={store:'',price:0,bulkPrice:'',isSale:false,isBulk:false}; if(field==='price'||field==='bulkPrice') prices[rowIdx][field]=parseFloat(value)||0; else prices[rowIdx][field]=value; updateBestStoreSummary(prices); } function addStorePriceRow(){ if(editingStockIndex<0) return; if(!stockItems[editingStockIndex].storePrices) stockItems[editingStockIndex].storePrices=[]; const newEntry={store:'',price:0,bulkPrice:'',isSale:false,isBulk:false}; stockItems[editingStockIndex].storePrices.push(newEntry); renderOneStoreRow(newEntry,stockItems[editingStockIndex].storePrices.length-1); updateBestStoreSummary(stockItems[editingStockIndex].storePrices); } function removeStorePriceRow(i){ if(editingStockIndex<0) return; stockItems[editingStockIndex].storePrices.splice(i,1); renderStorePriceRows(stockItems[editingStockIndex].storePrices); updateBestStoreSummary(stockItems[editingStockIndex].storePrices); } function updateBestStoreSummary(prices){ const el=document.getElementById('best-store-summary'); if(!el) return; if(!prices||!prices.length){el.classList.remove('visible');return;} let best=null; prices.forEach(sp=>{ if(!sp.store) return; const eff=(sp.isSale&&parseFloat(sp.bulkPrice)>0)?Math.min(sp.price,parseFloat(sp.bulkPrice)):sp.price; if(!best||effCheapest: ${best.store} at $${best.eff.toFixed(2)}${best.isSale?'SALE':''}${best.isBulk&&best.bulkPrice?`BULK $${parseFloat(best.bulkPrice).toFixed(2)}`:''}`; } function saveStockItem(){ if(editingStockIndex<0) return; const item=stockItems[editingStockIndex]; item.name=document.getElementById('stock-detail-name').value.trim(); item.quantity=parseFloat(document.getElementById('stock-detail-quantity').value)||0; item.unit=document.getElementById('stock-detail-unit').value; item.category=document.getElementById('stock-detail-category').value.trim(); item.tags=document.getElementById('stock-detail-tags').value.split(',').map(t=>t.trim()).filter(Boolean); item.usagePerMeal=parseFloat(document.getElementById('stock-detail-usage-per-meal').value)||1; item.minimumStock=parseFloat(document.getElementById('stock-detail-minimum-stock').value)||0; item.reorderPoint=parseFloat(document.getElementById('stock-detail-reorder-point').value)||0; item.notes=document.getElementById('stock-detail-notes').value.trim(); item.lastUpdated=formatDate(new Date()); const rows=document.querySelectorAll('#store-prices-list .store-price-row'); item.storePrices=[]; rows.forEach(row=>{ const inputs=row.querySelectorAll('input'); const store=inputs[0].value.trim(); const price=parseFloat(inputs[1].value)||0; const bulkPrice=inputs[2].value; const isSale=inputs[3].checked; const isBulk=inputs[4].checked; if(store) item.storePrices.push({store,price,bulkPrice,isSale,isBulk}); }); updateStockList(); closeStockDetails(); saveToLocalStorage(); showToast('Stock item saved!'); } function deleteStockItem(){ if(editingStockIndex<0) return; if(confirm(`Delete "${stockItems[editingStockIndex].name}"?`)){stockItems.splice(editingStockIndex,1);updateStockList();closeStockDetails();saveToLocalStorage();} } function removeMealFromStock(mealName,stockIdx){stockItems[stockIdx].usedInMeals=(stockItems[stockIdx].usedInMeals||[]).filter(m=>m!==mealName);saveToLocalStorage();showStockDetails(stockIdx);} function closeStockDetails(){editingStockIndex=-1;document.getElementById('stock-details-modal').classList.remove('flex');} // ============================================================ // RECIPE DETAILS // ============================================================ function showRecipeDetails(category, name){ const meal=getMealInfo(category,name); if(!meal){showToast(`Recipe not found: ${name}`,'warning');return;} editingRecipeCategory=category; editingRecipeIndex=mealData[category].findIndex(m=>m.name===name); pendingRecipePhoto=null; document.getElementById('recipe-modal-title').textContent=name; document.getElementById('recipe-basic-info-grid').innerHTML=`
    ${category}
    ${meal.prepTime||0}
    ${meal.cookTime||0}
    ${meal.servings||1}
    ${meal.difficulty||'Easy'}
    ${meal.cost.toFixed(2)}
    `; document.getElementById('recipe-tags').textContent=meal.tags||''; document.getElementById('recipe-serving-suggestions').textContent=meal.servingSuggestions||''; // Photo tab const photoBox=document.getElementById('recipe-photo-preview-box'); if(meal.photo){ photoBox.innerHTML=`blank`; } else { photoBox.innerHTML=``; } // Ingredients const list=document.getElementById('recipe-ingredients-list'); list.innerHTML=''; (meal.ingredients||[]).forEach(ing=>{ const li=document.createElement('li'); const cb=document.createElement('input'); cb.type='checkbox'; const span=document.createElement('span'); span.textContent=ing; const shopBtn=document.createElement('button'); shopBtn.className='add-to-shopping-btn'; shopBtn.textContent='+ Shopping'; shopBtn.dataset.ingredient=ing; shopBtn.addEventListener('click',function(){addIngredientToShoppingList(this.dataset.ingredient);}); const stockBtn=document.createElement('button'); stockBtn.className='add-to-stock-btn'; stockBtn.textContent='+ Stock'; stockBtn.dataset.ingredient=ing; stockBtn.addEventListener('click',function(){addIngredientToStock(this.dataset.ingredient);}); li.appendChild(cb); li.appendChild(span); li.appendChild(shopBtn); li.appendChild(stockBtn); list.appendChild(li); }); document.getElementById('recipe-instructions').value=meal.instructions||''; document.getElementById('recipe-notes').value=meal.notes||''; ['calories','protein','carbs','fats','fiber','sugar'].forEach(k=>{ document.getElementById(`recipe-${k}`).textContent=(meal.nutrition[k]||0)+(k!=='calories'?'g':''); document.getElementById(`edit-${k}`).value=meal.nutrition[k]||0; }); updateRecipeNutritionChart(meal.nutrition.protein,meal.nutrition.carbs,meal.nutrition.fats); isEditingRecipe=false; document.getElementById('recipe-edit-btn').style.display=''; document.getElementById('recipe-save-btn').style.display='none'; document.getElementById('recipe-cancel-btn').style.display='none'; document.getElementById('recipe-instructions').readOnly=true; document.getElementById('recipe-notes').readOnly=true; showRecipeTab('details'); document.getElementById('recipe-details-modal').classList.add('flex'); } function updateRecipeNutritionChart(p,c,f){ const ctx=document.getElementById('recipe-nutrition-chart').getContext('2d'); if(window.recipeNutritionChart) window.recipeNutritionChart.destroy(); window.recipeNutritionChart=new Chart(ctx,{type:'doughnut',data:{labels:['Protein','Carbs','Fats'],datasets:[{data:[p,c,f],backgroundColor:['#4285F4','#FBBC05','#EA4335'],borderWidth:1}]},options:{responsive:true,maintainAspectRatio:false,cutout:'70%',plugins:{legend:{position:'bottom',labels:{boxWidth:12,padding:10,font:{size:10}}}}}}); } function closeRecipeDetails(){document.getElementById('recipe-details-modal').classList.remove('flex');} function showRecipeTab(tab){ document.querySelectorAll('#recipe-details-modal .modal-tab').forEach(t=>t.classList.remove('active')); const activeTab=document.querySelector(`#recipe-details-modal .modal-tab[onclick="showRecipeTab('${tab}')"]`); if(activeTab) activeTab.classList.add('active'); document.querySelectorAll('#recipe-details-modal .modal-tab-content').forEach(c=>c.classList.remove('active')); const cont=document.getElementById(`recipe-${tab}-tab`); if(cont) cont.classList.add('active'); } function toggleRecipeEdit(){ isEditingRecipe=true; document.getElementById('recipe-edit-btn').style.display='none'; document.getElementById('recipe-save-btn').style.display=''; document.getElementById('recipe-cancel-btn').style.display=''; document.getElementById('recipe-instructions').readOnly=false; document.getElementById('recipe-notes').readOnly=false; const meal=mealData[editingRecipeCategory][editingRecipeIndex]; document.getElementById('recipe-basic-info-grid').innerHTML=`
    ${editingRecipeCategory}
    `; const tagsEl=document.getElementById('recipe-tags'); if(tagsEl){ const inp=document.createElement('input'); inp.type='text'; inp.className='detail-input'; inp.id='edit-tags'; inp.value=meal.tags||''; tagsEl.replaceWith(inp); } const ssEl=document.getElementById('recipe-serving-suggestions'); if(ssEl){ const inp=document.createElement('input'); inp.type='text'; inp.className='detail-input'; inp.id='edit-serving-suggestions'; inp.value=meal.servingSuggestions||''; ssEl.replaceWith(inp); } } function saveRecipeChanges(){ if(editingRecipeIndex<0) return; const meal=mealData[editingRecipeCategory][editingRecipeIndex]; meal.prepTime=parseInt(document.getElementById('edit-prep-time')?.value)||0; meal.cookTime=parseInt(document.getElementById('edit-cook-time')?.value)||0; meal.servings=parseInt(document.getElementById('edit-servings')?.value)||1; meal.difficulty=document.getElementById('edit-difficulty')?.value||'Easy'; meal.cost=parseFloat(document.getElementById('edit-cost')?.value)||0; meal.tags=document.getElementById('edit-tags')?.value||''; meal.servingSuggestions=document.getElementById('edit-serving-suggestions')?.value||''; meal.instructions=document.getElementById('recipe-instructions').value; meal.notes=document.getElementById('recipe-notes').value; ['calories','protein','carbs','fats','fiber','sugar'].forEach(k=>{meal.nutrition[k]=parseFloat(document.getElementById(`edit-${k}`)?.value)||0;}); updateMealLibraryDisplay(); updateAddMealModalLibrary(); updateDailyNutrition(formatDate(currentDate)); saveToLocalStorage(); showToast('Recipe saved!'); cancelRecipeEdit(); showRecipeDetails(editingRecipeCategory,meal.name); } function cancelRecipeEdit(){ isEditingRecipe=false; document.getElementById('recipe-edit-btn').style.display=''; document.getElementById('recipe-save-btn').style.display='none'; document.getElementById('recipe-cancel-btn').style.display='none'; document.getElementById('recipe-instructions').readOnly=true; document.getElementById('recipe-notes').readOnly=true; if(editingRecipeIndex>=0&&editingRecipeCategory){const m=mealData[editingRecipeCategory][editingRecipeIndex];if(m) showRecipeDetails(editingRecipeCategory,m.name);} } function addNewIngredient(){ const input=document.getElementById('new-ingredient-input'); const ing=input.value.trim(); if(!ing||editingRecipeIndex<0) return; mealData[editingRecipeCategory][editingRecipeIndex].ingredients.push(ing); const li=document.createElement('li'); const cb=document.createElement('input'); cb.type='checkbox'; const span=document.createElement('span'); span.textContent=ing; const shopBtn=document.createElement('button'); shopBtn.className='add-to-shopping-btn'; shopBtn.textContent='+ Shopping'; shopBtn.dataset.ingredient=ing; shopBtn.addEventListener('click',function(){addIngredientToShoppingList(this.dataset.ingredient);}); li.appendChild(cb); li.appendChild(span); li.appendChild(shopBtn); document.getElementById('recipe-ingredients-list').appendChild(li); input.value=''; saveToLocalStorage(); } function addIngredientToStock(ing){ const parts=ing.split(' '); let qty=1,unit='units',name=ing; if(parts.length>1&&!isNaN(parseFloat(parts[0]))){qty=parseFloat(parts[0]);unit=parts[1];name=parts.slice(2).join(' ');} const existing=stockItems.findIndex(i=>ing.toLowerCase().includes(i.name.toLowerCase())); if(existing>=0){showStockDetails(existing);} else{stockItems.push({id:Date.now(),name,quantity:qty,unit,usagePerMeal:1,minimumStock:qty*0.2,reorderPoint:qty*0.3,category:'General',tags:[],notes:`From recipe: ${ing}`,usedInMeals:[],storePrices:[],lastUpdated:formatDate(new Date())});updateStockList();showToast(`"${name}" added to pantry`);} } // ============================================================ // ADD MEAL MODAL // ============================================================ function showAddMealModal(){document.getElementById('add-meal-modal').classList.add('flex'); updateAddMealModalLibrary();} function closeAddMealModal(){document.getElementById('add-meal-modal').classList.remove('flex');} function showAddMealTab(tab){ document.querySelectorAll('#add-meal-modal .modal-tab').forEach(t=>t.classList.remove('active')); const el=document.querySelector(`#add-meal-modal .modal-tab[onclick="showAddMealTab('${tab}')"]`); if(el) el.classList.add('active'); document.querySelectorAll('#add-meal-modal .modal-tab-content').forEach(c=>c.classList.remove('active')); document.getElementById(`add-meal-${tab}-tab`).classList.add('active'); } function updateAddMealModalLibrary(){ const cont=document.getElementById('add-meal-library-tab'); if(!cont) return; cont.innerHTML=''; for(const cat in mealData){ if(!mealData[cat].length) continue; const sec=document.createElement('div'); let icon='fa-utensils'; if(cat==='breakfast') icon='fa-bread-slice'; else if(cat==='dinner') icon='fa-drumstick-bite'; else if(cat==='snacks') icon='fa-apple-alt'; sec.innerHTML=`
    ${cat.charAt(0).toUpperCase()+cat.slice(1)}
    `; const grid=document.createElement('div'); grid.className='add-meal-items'; mealData[cat].forEach(m=>{ const el=document.createElement('div'); el.className='add-meal-item'; el.dataset.cat=cat; el.dataset.name=m.name; if(m.photo){ el.innerHTML=`${m.name}${m.name}`; } else { el.innerHTML=`${m.name}`; } el.addEventListener('click',function(){addMealFromLibrary(this.dataset.cat,this.dataset.name);}); grid.appendChild(el); }); sec.appendChild(grid); cont.appendChild(sec); } } function addMealFromLibrary(cat,name){addMealToPlan(formatDate(currentDate),cat,name);closeAddMealModal();showToast(`"${name}" added to today's plan`);} function selectIcon(icon,event){ selectedIcon=icon; document.getElementById('icon-preview').innerHTML=``; document.querySelectorAll('.icon-selection-item').forEach(i=>i.classList.remove('selected')); if(event&&event.target) event.target.closest('.icon-selection-item').classList.add('selected'); } function addCustomMealToPlan(){ const name=document.getElementById('custom-meal-name').value.trim(); const type=document.getElementById('custom-meal-type').value; const cost=parseFloat(document.getElementById('custom-meal-cost').value)||0; if(!name) return; if(!mealData[type]) mealData[type]=[]; mealData[type].push({name,cost,icon:selectedIcon,photo:pendingCustomMealPhoto||null,nutrition:{calories:0,protein:0,carbs:0,fats:0,sugar:0,fiber:0},prepTime:0,cookTime:0,servings:1,difficulty:'Easy',ingredients:[],instructions:'',notes:'',tags:'',servingSuggestions:''}); pendingCustomMealPhoto=null; updateMealLibraryDisplay(); updateAddMealModalLibrary(); addMealToPlan(formatDate(currentDate),type,name); document.getElementById('custom-meal-name').value=''; document.getElementById('custom-meal-cost').value=''; document.getElementById('custom-meal-photo-box').innerHTML=``; closeAddMealModal(); saveToLocalStorage(); } // ============================================================ // DELETE HELPERS // ============================================================ function confirmDeleteMeal(cat,name){ selectedMealToDelete=name; selectedMealTypeToDelete=cat; pendingDeleteCallback=confirmDeleteMealAction; document.getElementById('delete-confirm-title').textContent='Delete from Library'; document.getElementById('delete-confirm-msg').textContent=`Remove "${name}" from the meal library?`; document.getElementById('delete-confirm-btn').textContent='Delete'; document.getElementById('delete-confirm-modal').classList.add('flex'); } function confirmDeleteMealAction(){ mealData[selectedMealTypeToDelete]=mealData[selectedMealTypeToDelete].filter(m=>m.name!==selectedMealToDelete); for(const d in mealPlan) mealPlan[d]=mealPlan[d].filter(m=>!(m.type===selectedMealTypeToDelete&&m.name===selectedMealToDelete)); updateMealLibraryDisplay(); updateAddMealModalLibrary(); updateCalendar(); updateDailyNutrition(formatDate(currentDate)); saveToLocalStorage(); } function confirmDeleteMealFromPlan(type,name,date){ if(confirm(`Remove "${name}" from this day?`)){ if(mealPlan[date]){mealPlan[date]=mealPlan[date].filter(m=>!(m.type===type&&m.name===name));updateCalendar();updateDailyNutrition(date);saveToLocalStorage();} } } function confirmDelete(){if(pendingDeleteCallback) pendingDeleteCallback();cancelDelete();} function cancelDelete(){selectedMealToDelete=null;selectedMealTypeToDelete=null;pendingDeleteCallback=null;document.getElementById('delete-confirm-modal').classList.remove('flex');} function showRestartDataConfirm(){ pendingDeleteCallback=resetData; document.getElementById('delete-confirm-title').textContent='Restart All Data'; document.getElementById('delete-confirm-msg').textContent='This will delete ALL data. Cannot be undone.'; document.getElementById('delete-confirm-btn').textContent='Restart'; document.getElementById('delete-confirm-modal').classList.add('flex'); } function resetData(){ mealData={breakfast:[{name:'Oatmeal Example',cost:2.50,icon:'fa-bread-slice',photo:null,nutrition:{calories:300,protein:10,carbs:55,fats:3,sugar:12,fiber:8},prepTime:10,cookTime:15,servings:2,difficulty:'Easy',ingredients:['1 cup rolled oats','2 cups water','1 banana','1 tbsp honey'],instructions:'1. Boil water.\n2. Add oats.\n3. Cook 5 min.',notes:'Use milk for creamier.',tags:'Quick, Healthy',servingSuggestions:'Serve hot'}],lunch:[],dinner:[],snacks:[],other:[]}; mealPlan={}; shoppingList=[]; stockItems=[{id:1,name:'Rolled oats',quantity:2,unit:'kg',usagePerMeal:0.1,minimumStock:0.5,reorderPoint:1,category:'Grains',tags:[],notes:'',usedInMeals:['Oatmeal Example'],storePrices:[{store:'Walmart',price:3.50,bulkPrice:'',isSale:false,isBulk:false}],lastUpdated:'2024-01-15'}]; nutritionGoals={calories:2000,protein:50,carbs:300,fats:65,sugar:25}; habitsData={}; updateMealLibraryDisplay(); updateAddMealModalLibrary(); updateCalendar(); updateShoppingList(); updateStockList(); updateDailyNutrition(formatDate(currentDate)); saveToLocalStorage(); showToast('Data reset!'); closeSettingsModal(); } // ============================================================ // PRO NUTRITION CALCULATOR // ============================================================ function selectActivityLevel(level,el){ selectedActivityLevel=level; document.querySelectorAll('#activity-levels .checkbox-item').forEach(i=>i.classList.remove('selected')); if(el) el.classList.add('selected'); document.querySelectorAll('.activity-checkbox').forEach(c=>c.checked=false); const cb=document.getElementById(`activity-${level}`); if(cb) cb.checked=true; } function selectEatingStyle(style,el){ selectedEatingStyle=style; document.querySelectorAll('#eating-styles .checkbox-item').forEach(i=>i.classList.remove('selected')); if(el) el.classList.add('selected'); document.querySelectorAll('.style-checkbox').forEach(c=>c.checked=false); const cb=document.getElementById(`style-${style}`); if(cb) cb.checked=true; } function toggleRestriction(r){ const cb=document.getElementById(`restriction-${r}`); const item=event.target.closest('.restriction-item'); if(r==='none'){ document.querySelectorAll('#diet-restrictions .restriction-item').forEach(i=>i.classList.remove('selected')); document.querySelectorAll('#diet-restrictions input[type="checkbox"]').forEach(c=>c.checked=false); if(cb) cb.checked=true; if(item) item.classList.add('selected'); selectedRestrictions=['none']; } else { const noneCb=document.getElementById('restriction-none'); if(noneCb&&noneCb.checked){noneCb.checked=false;document.querySelector('#diet-restrictions .restriction-item:first-child')?.classList.remove('selected');selectedRestrictions=[];} if(cb){ cb.checked=!cb.checked; if(item) item.classList.toggle('selected',cb.checked); if(cb.checked){selectedRestrictions=selectedRestrictions.filter(x=>x!=='none');if(!selectedRestrictions.includes(r)) selectedRestrictions.push(r);} else{selectedRestrictions=selectedRestrictions.filter(x=>x!==r);} } } } function calculateNutritionGoals(){ const age=parseInt(document.getElementById('user-age').value)||30; const heightFt=parseInt(document.getElementById('height-ft').value)||5; const heightIn=parseInt(document.getElementById('height-in').value)||9; const weightLbs=parseFloat(document.getElementById('current-weight').value)||180; const goalWeightLbs=parseFloat(document.getElementById('goal-weight').value)||160; const timelineWeeks=parseInt(document.getElementById('timeline').value)||12; const heightCm=((heightFt*12)+heightIn)*2.54; const weightKg=weightLbs*0.453592; let bmr; if(selectedGender==='male'){ bmr=88.362+(13.397*weightKg)+(4.799*heightCm)-(5.677*age); } else { bmr=447.593+(9.247*weightKg)+(3.098*heightCm)-(4.330*age); } let actMult=1.2; if(selectedActivityLevel==='light') actMult=1.375; else if(selectedActivityLevel==='active') actMult=1.55; else if(selectedActivityLevel==='athlete') actMult=1.725; let tdee=bmr*actMult; const currentBF=currentBFPercent/100, goalBF=goalBFPercent/100; const leanMassKg=weightKg*(1-currentBF); const targetWeightKg=goalWeightLbs*0.453592; const fatToLose=weightKg-targetWeightKg; let dailyCalories=tdee, phase='maintain'; if(fatToLose>0){const dailyDeficit=((fatToLose*7700)/timelineWeeks)/7;dailyCalories=tdee-dailyDeficit;phase='cut';} else if(fatToLose<0){const dailySurplus=((Math.abs(fatToLose)*7700)/timelineWeeks)/7;dailyCalories=tdee+dailySurplus;phase='bulk';} else{phase='recomp';} dailyCalories=Math.max(1200,Math.min(4000,dailyCalories)); let proteinMult=2.0; if(phase==='cut') proteinMult=2.2; else if(phase==='bulk') proteinMult=1.8; let proteinGrams=leanMassKg*proteinMult; let fatPct=0.25; if(selectedEatingStyle==='high-fat') fatPct=0.35; let fatGrams=(dailyCalories*fatPct)/9; let carbGrams=Math.max(50,(dailyCalories-(proteinGrams*4)-(fatGrams*9))/4); document.getElementById('result-calories').textContent=Math.round(dailyCalories); document.getElementById('result-protein').textContent=Math.round(proteinGrams)+'g'; document.getElementById('result-carbs').textContent=Math.round(carbGrams)+'g'; document.getElementById('result-fats').textContent=Math.round(fatGrams)+'g'; document.getElementById('results-metrics-row').innerHTML=`
    ${weightLbs} lbs
    Current Weight
    ${goalWeightLbs} lbs
    Goal Weight
    ${currentBFPercent}%
    Current Body Fat
    ${Math.round(leanMassKg*2.20462)} lbs
    Lean Mass
    `; document.getElementById('phase-recommendation').innerHTML=`
    ${phase.toUpperCase()}${phase==='cut'?'Calorie deficit for fat loss':phase==='bulk'?'Calorie surplus for muscle gain':'Maintenance for body recomposition'}
    ${timelineWeeks} weeks to goal
    ${Math.round(Math.abs(fatToLose*2.20462))} lbs fat to ${fatToLose>0?'lose':'gain'}
    `; document.getElementById('tips-list').innerHTML=`
  • ${phase==='cut'?'Focus on protein to preserve muscle mass':phase==='bulk'?'Prioritize complex carbs for energy':'Maintain balanced macros for consistent progress'}
  • Drink ${Math.round(weightLbs*0.5)} oz of water daily
  • Aim for 7-9 hours of quality sleep
  • Track progress weekly, not daily
  • `; updateBodyCompChart(currentBFPercent,goalBFPercent); document.getElementById('nutrition-results').style.display='block'; document.getElementById('settings-calories-goal').value=Math.round(dailyCalories); document.getElementById('settings-protein-goal').value=Math.round(proteinGrams); document.getElementById('settings-carbs-goal').value=Math.round(carbGrams); document.getElementById('settings-fats-goal').value=Math.round(fatGrams); } function updateBodyCompChart(current,goal){ const ctx=document.getElementById('body-comp-chart').getContext('2d'); if(window.bodyCompChart) window.bodyCompChart.destroy(); window.bodyCompChart=new Chart(ctx,{type:'bar',data:{labels:['Current','Goal'],datasets:[{label:'Lean Mass %',data:[100-current,100-goal],backgroundColor:'#4CAF50',borderRadius:6},{label:'Body Fat %',data:[current,goal],backgroundColor:'#FF6B6B',borderRadius:6}]},options:{responsive:true,maintainAspectRatio:true,scales:{x:{stacked:true},y:{stacked:true,max:100,title:{display:true,text:'Percentage (%)'}}},plugins:{tooltip:{callbacks:{label:(ctx)=>`${ctx.dataset.label}: ${ctx.raw}%`}}}}}); } function saveCalculatedGoal(){ const cal=parseInt(document.getElementById('result-calories').textContent)||2000; const pro=parseInt(document.getElementById('result-protein').textContent)||50; const carbs=parseInt(document.getElementById('result-carbs').textContent)||250; const fats=parseInt(document.getElementById('result-fats').textContent)||65; nutritionGoals={calories:cal,protein:pro,carbs:carbs,fats:fats,sugar:nutritionGoals.sugar}; document.getElementById('settings-calories-goal').value=cal; document.getElementById('settings-protein-goal').value=pro; document.getElementById('settings-carbs-goal').value=carbs; document.getElementById('settings-fats-goal').value=fats; document.getElementById('goal-saved-banner').style.display='block'; document.getElementById('banner-cal').textContent=cal; document.getElementById('banner-pro').textContent=pro+'g'; document.getElementById('banner-car').textContent=carbs+'g'; document.getElementById('banner-fat').textContent=fats+'g'; setTimeout(()=>{document.getElementById('goal-saved-banner').style.display='none';},3000); saveToLocalStorage(); showToast('Goals saved!','success'); } function applyNutritionGoals(){saveCalculatedGoal();showMainTab('calendar');updateDailyNutrition(formatDate(currentDate));showToast('Nutrition goals applied!');} function resetNutritionCalculator(){ document.getElementById('user-age').value=''; document.getElementById('height-ft').value=''; document.getElementById('height-in').value=''; document.getElementById('current-weight').value=''; document.getElementById('goal-weight').value=''; document.getElementById('timeline').value=''; selectedGender=''; selectedActivityLevel=''; selectedEatingStyle=''; selectedRestrictions=[]; currentBFPercent=20; goalBFPercent=15; document.getElementById('current-bf-slider').value=20; document.getElementById('current-bf-display').textContent='20%'; document.getElementById('goal-bf-slider').value=15; document.getElementById('goal-bf-display').textContent='15%'; document.querySelectorAll('.gender-option').forEach(o=>o.classList.remove('selected')); document.querySelectorAll('#activity-levels .checkbox-item').forEach(i=>i.classList.remove('selected')); document.querySelectorAll('.activity-checkbox').forEach(c=>c.checked=false); document.querySelectorAll('#eating-styles .checkbox-item').forEach(i=>i.classList.remove('selected')); document.querySelectorAll('.style-checkbox').forEach(c=>c.checked=false); document.querySelectorAll('#diet-restrictions .restriction-item').forEach(i=>i.classList.remove('selected')); document.querySelectorAll('#diet-restrictions input[type="checkbox"]').forEach(c=>c.checked=false); buildBFSelectors(); document.getElementById('nutrition-results').style.display='none'; showToast('Calculator reset'); } // ============================================================ // HABITS TRACKER // ============================================================ function getTodayStr(){return formatDate(habitsDate);} function getHabitsForDate(dateStr){ if(!habitsData[dateStr]){habitsData[dateStr]={morningWeight:null,eveningWeight:null,waterCups:0,waterGoal:8,habits:{},notes:''};} return habitsData[dateStr]; } function renderHabitsTab(){ const dateStr=getTodayStr(); const habits=getHabitsForDate(dateStr); document.getElementById('habits-date-title').textContent=formatDateLong(habitsDate); const morningInput=document.getElementById('weight-morning'); const eveningInput=document.getElementById('weight-evening'); if(morningInput) morningInput.value=habits.morningWeight||''; if(eveningInput) eveningInput.value=habits.eveningWeight||''; const waterGoal=habits.waterGoal||8; document.getElementById('water-goal-cups').value=waterGoal; renderWaterCups(); renderEatingHabitsGrid(habits.habits||{}); const notesArea=document.getElementById('habit-daily-notes'); if(notesArea) notesArea.value=habits.notes||''; updateWeightStats(); renderWeightChart(); renderStreakGrid(); } function habitsNavDay(delta){habitsDate.setDate(habitsDate.getDate()+delta);renderHabitsTab();} function saveWeightEntry(){ const dateStr=getTodayStr(); const habits=getHabitsForDate(dateStr); const morning=parseFloat(document.getElementById('weight-morning').value); const evening=parseFloat(document.getElementById('weight-evening').value); if(!isNaN(morning)) habits.morningWeight=morning; if(!isNaN(evening)) habits.eveningWeight=evening; habitsData[dateStr]=habits; saveToLocalStorage(); updateWeightStats(); renderWeightChart(); } function setWeightUnit(unit){ weightUnit=unit; document.querySelectorAll('.weight-unit-btn').forEach(btn=>btn.classList.remove('active')); document.getElementById(`unit-${unit}`).classList.add('active'); renderWeightChart(); saveToLocalStorage(); } function updateWeightStats(){ const weights=[]; for(let i=6;i>=0;i--){ const d=new Date(habitsDate); d.setDate(habitsDate.getDate()-i); const h=habitsData[formatDate(d)]; if(h&&h.morningWeight) weights.push(h.morningWeight); } const todayWeight=weights[weights.length-1]; const avgWeight=weights.length?weights.reduce((a,b)=>a+b,0)/weights.length:0; const change=weights.length>=2?(weights[weights.length-1]-weights[0]).toFixed(1):0; document.getElementById('wstat-current').textContent=todayWeight?todayWeight.toFixed(1)+(weightUnit==='lbs'?' lbs':' kg'):'โ€”'; document.getElementById('wstat-7avg').textContent=avgWeight?avgWeight.toFixed(1)+(weightUnit==='lbs'?' lbs':' kg'):'โ€”'; document.getElementById('wstat-change').textContent=change?(change>0?'+':'')+change+(weightUnit==='lbs'?' lbs':' kg'):'โ€”'; document.getElementById('wstat-goal').textContent=weightGoal?weightGoal+(weightUnit==='lbs'?' lbs':' kg'):'โ€”'; } function renderWeightChart(){ const ctx=document.getElementById('weight-chart').getContext('2d'); if(window.weightChart) window.weightChart.destroy(); const labels=[],data=[]; for(let i=30;i>=0;i--){ const d=new Date(habitsDate); d.setDate(habitsDate.getDate()-i); const h=habitsData[formatDate(d)]; if(h&&h.morningWeight){labels.push(formatDate(d).slice(5));data.push(h.morningWeight);} } window.weightChart=new Chart(ctx,{type:'line',data:{labels,datasets:[{label:`Weight (${weightUnit})`,data,borderColor:'#4285F4',backgroundColor:'rgba(66,133,244,0.1)',fill:true,tension:0.3}]},options:{responsive:true,maintainAspectRatio:true,plugins:{legend:{position:'top'}},scales:{y:{title:{display:true,text:`Weight (${weightUnit})`}}}}}); } function renderWaterCups(){ const dateStr=getTodayStr(); const habits=getHabitsForDate(dateStr); const goal=parseInt(document.getElementById('water-goal-cups').value)||8; habits.waterGoal=goal; const current=habits.waterCups||0; const container=document.getElementById('water-cups-grid'); container.innerHTML=''; for(let i=1;i<=goal;i++){ const cup=document.createElement('div'); cup.className='water-cup'+(i<=current?' filled':''); cup.innerHTML=``; cup.onclick=()=>toggleWaterCup(i); container.appendChild(cup); } const percentage=(current/goal)*100; document.getElementById('water-progress-fill').style.width=percentage+'%'; document.getElementById('water-summary').textContent=`${current} of ${goal} cups ยท ${current*8} oz`; habitsData[dateStr]=habits; saveToLocalStorage(); } function toggleWaterCup(cupIndex){ const dateStr=getTodayStr(); const habits=getHabitsForDate(dateStr); let current=habits.waterCups||0; if(cupIndex===current+1) current++; else if(cupIndex===current) current--; habits.waterCups=Math.max(0,Math.min(current,habits.waterGoal||8)); habitsData[dateStr]=habits; renderWaterCups(); saveToLocalStorage(); } function renderEatingHabitsGrid(savedHabits){ const container=document.getElementById('eating-habits-grid'); container.innerHTML=''; DEFAULT_EATING_HABITS.forEach(habit=>{ const card=document.createElement('div'); card.className='habit-toggle-card'+(savedHabits[habit.id]?' checked':''); card.innerHTML=`
    ${habit.icon}
    ${habit.name}
    ${habit.detail}
    `; card.onclick=()=>toggleHabit(habit.id); container.appendChild(card); }); const customRow=document.createElement('div'); customRow.className='habit-custom-row'; customRow.innerHTML=``; container.appendChild(customRow); } function toggleHabit(habitId){ const dateStr=getTodayStr(); const habits=getHabitsForDate(dateStr); if(!habits.habits) habits.habits={}; habits.habits[habitId]=!habits.habits[habitId]; habitsData[dateStr]=habits; renderEatingHabitsGrid(habits.habits); renderStreakGrid(); saveToLocalStorage(); } function addCustomHabit(){ const input=document.getElementById('custom-habit-name'); const name=input.value.trim(); if(!name) return; const id='custom_'+Date.now(); DEFAULT_EATING_HABITS.push({id,icon:'โญ',name,detail:'Custom habit'}); input.value=''; renderEatingHabitsGrid(getHabitsForDate(getTodayStr()).habits||{}); showToast('Custom habit added!'); } function saveHabitsEntry(){ const dateStr=getTodayStr(); const habits=getHabitsForDate(dateStr); habits.notes=document.getElementById('habit-daily-notes').value; habitsData[dateStr]=habits; saveToLocalStorage(); } function renderStreakGrid(){ const container=document.getElementById('streak-grid'); container.innerHTML=''; const days=['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; for(let i=6;i>=0;i--){ const d=new Date(habitsDate); d.setDate(habitsDate.getDate()-i); const dateStr=formatDate(d); const habits=habitsData[dateStr]; const isToday=(i===0); let completedCount=0; const totalHabits=DEFAULT_EATING_HABITS.length; if(habits&&habits.habits) completedCount=Object.values(habits.habits).filter(v=>v===true).length; let statusClass='', statusText='โ—‹'; if(completedCount===totalHabits&&totalHabits>0){statusClass='active';statusText='โœ“';} else if(completedCount>0){statusClass='partial';statusText=completedCount;} const dayDiv=document.createElement('div'); dayDiv.className='streak-day'; dayDiv.innerHTML=`
    ${days[d.getDay()]}
    ${statusText}
    `; container.appendChild(dayDiv); } } // ============================================================ // DATA IMPORT/EXPORT // ============================================================ function exportData(){ const exportObj={mealData,mealPlan,shoppingList,stockItems,nutritionGoals,habitsData,version:'2.0'}; const dataStr=JSON.stringify(exportObj,null,2); const blob=new Blob([dataStr],{type:'application/json'}); const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download=`keetok_backup_${formatDate(new Date())}.json`; a.click(); URL.revokeObjectURL(url); showToast('Data exported!'); } document.getElementById('import-data-file').addEventListener('change',function(e){ const file=e.target.files[0]; if(!file) return; const reader=new FileReader(); reader.onload=function(ev){ try{ const data=JSON.parse(ev.target.result); if(data.mealData){ for(const cat in data.mealData){ mealData[cat]=data.mealData[cat].map(m=>({photo:null,...m})); } } if(data.mealPlan) mealPlan=data.mealPlan; if(data.shoppingList) shoppingList=data.shoppingList; if(data.stockItems) stockItems=data.stockItems; if(data.nutritionGoals) nutritionGoals=data.nutritionGoals; if(data.habitsData) habitsData=data.habitsData; updateMealLibraryDisplay(); updateAddMealModalLibrary(); updateCalendar(); updateShoppingList(); updateStockList(); updateDailyNutrition(formatDate(currentDate)); renderHabitsTab(); saveToLocalStorage(); showToast('Data imported!'); }catch(err){showToast('Invalid file','warning');} }; reader.readAsText(file); }); // ============================================================ // INITIALIZATION // ============================================================ function init(){ loadFromLocalStorage(); loadSettings(); selectLibraryView(currentLibraryView, false); updateMealLibraryDisplay(); updateAddMealModalLibrary(); updateCalendar(); updateShoppingList(); updateStockList(); buildBFSelectors(); setupNutritionChart(); applyDisplayOptions(); const savedTab=localStorage.getItem('keetokMainTab'); if(savedTab&&['calendar','nutrition','shopping','stock','habits'].includes(savedTab)) showMainTab(savedTab); renderHabitsTab(); showToast('Welcome to KeeTok Meals Planner! ๐Ÿฝ๏ธ','success',3000); } init();