// ForkFolio Shared Recipe Viewer Logic let activeRecipe = null; let currentMultiplier = 1; let stopwatchInterval = null; let stopwatchSeconds = 0; let cookingActiveStep = 0; // DOM Cache const elements = { themeToggle: document.getElementById('theme-toggle'), shareRecipeBlock: document.getElementById('share-recipe-block'), recipeNotFoundBlock: document.getElementById('recipe-not-found-block'), // Recipe Details recipeDetailBanner: document.getElementById('recipe-detail-banner'), recipeDetailTitle: document.getElementById('recipe-detail-title'), recipeDetailCreatorName: document.getElementById('recipe-detail-creator-name'), tagServings: document.getElementById('tag-servings'), tagPrep: document.getElementById('tag-prep'), tagCook: document.getElementById('tag-cook'), tagSource: document.getElementById('tag-source'), recipeDetailDescription: document.getElementById('recipe-detail-description'), recipeDetailIngredients: document.getElementById('recipe-detail-ingredients'), recipeDetailInstructions: document.getElementById('recipe-detail-instructions'), // Scaler scalerMinus: document.getElementById('scaler-minus'), scalerPlus: document.getElementById('scaler-plus'), currentServingsVal: document.getElementById('current-servings-val'), btnTriggerCookmode: document.getElementById('btn-trigger-cookmode'), // Cooking Mode Modal cookingModeModal: document.getElementById('cooking-mode-modal'), cookRecipeTitle: document.getElementById('cook-recipe-title'), cookTimerVal: document.getElementById('cook-timer-val'), cookTimerPlay: document.getElementById('cook-timer-play'), cookTimerReset: document.getElementById('cook-timer-reset'), cookProgressBar: document.getElementById('cook-progress-bar'), cookStepIndex: document.getElementById('cook-step-index'), cookStepInstruction: document.getElementById('cook-step-instruction'), cookBtnPrev: document.getElementById('cook-btn-prev'), cookBtnNext: document.getElementById('cook-btn-next'), cookStepDots: document.getElementById('cook-step-dots') }; // Start Lifecycle window.addEventListener('DOMContentLoaded', () => { initTheme(); registerEvents(); fetchSharedRecipe(); }); function initTheme() { const savedTheme = localStorage.getItem('gourmet-theme') || 'dark'; if (savedTheme === 'dark') { document.body.classList.add('dark-theme'); } else { document.body.classList.remove('dark-theme'); } } function toggleTheme() { document.body.classList.toggle('dark-theme'); const isDark = document.body.classList.contains('dark-theme'); localStorage.setItem('gourmet-theme', isDark ? 'dark' : 'light'); } function registerEvents() { elements.themeToggle.addEventListener('click', toggleTheme); // Portions Adjustments elements.scalerMinus.addEventListener('click', () => adjustMultiplier(-0.5)); elements.scalerPlus.addEventListener('click', () => adjustMultiplier(0.5)); // Cooking steppers triggers elements.btnTriggerCookmode.addEventListener('click', enterCookingMode); elements.cookBtnPrev.addEventListener('click', prevCookingStep); elements.cookBtnNext.addEventListener('click', nextCookingStep); elements.cookTimerPlay.addEventListener('click', toggleTimer); elements.cookTimerReset.addEventListener('click', resetTimer); // Hotkeys for cooking stepper document.addEventListener('keydown', (e) => { if (elements.cookingModeModal.classList.contains('active')) { if (e.key === 'ArrowRight' || e.key === ' ') { e.preventDefault(); nextCookingStep(); } else if (e.key === 'ArrowLeft') { e.preventDefault(); prevCookingStep(); } else if (e.key === 'Escape') { exitCookingMode(); } } }); } // Fetch public shared recipe async function fetchSharedRecipe() { const token = new URLSearchParams(window.location.search).get('token'); if (!token) { showErrorBlock(); return; } try { const response = await fetch(`/api/share/recipes/${token}`); if (!response.ok) { throw new Error('Not found'); } activeRecipe = await response.json(); renderRecipePage(activeRecipe); } catch (err) { console.error('Failed to load shared recipe:', err); showErrorBlock(); } } function showErrorBlock() { elements.recipeNotFoundBlock.style.display = 'flex'; elements.shareRecipeBlock.style.display = 'none'; lucide.createIcons(); } function renderRecipePage(recipe) { elements.recipeNotFoundBlock.style.display = 'none'; elements.shareRecipeBlock.style.display = 'block'; // Banner Image Cover if (recipe.image) { elements.recipeDetailBanner.style.backgroundImage = `url('${recipe.image}')`; } else { elements.recipeDetailBanner.style.backgroundImage = 'none'; elements.recipeDetailBanner.style.backgroundColor = 'var(--bg-app)'; } elements.recipeDetailTitle.textContent = recipe.title; elements.recipeDetailCreatorName.textContent = `@${recipe.createdBy || 'gourmet_system'}`; elements.recipeDetailDescription.textContent = recipe.description || 'No description available for this recipe.'; // Servings and timings tags if (recipe.servings) { elements.tagServings.style.display = 'flex'; elements.tagServings.querySelector('.val').textContent = `${recipe.servings} Servings`; } else { elements.tagServings.style.display = 'none'; } if (recipe.prepTime) { elements.tagPrep.style.display = 'flex'; elements.tagPrep.querySelector('.val').textContent = recipe.prepTime; } else { elements.tagPrep.style.display = 'none'; } if (recipe.cookTime) { elements.tagCook.style.display = 'flex'; elements.tagCook.querySelector('.val').textContent = recipe.cookTime; } else { elements.tagCook.style.display = 'none'; } if (recipe.sourceUrl) { elements.tagSource.style.display = 'flex'; elements.tagSource.href = recipe.sourceUrl; } else { elements.tagSource.style.display = 'none'; } // Render lists renderScaledIngredients(); renderInstructions(); lucide.createIcons(); } // Ingredient Scaling Algorithms function adjustMultiplier(change) { const newMulti = currentMultiplier + change; if (newMulti < 0.5 || newMulti > 10) return; currentMultiplier = newMulti; elements.currentServingsVal.textContent = `${currentMultiplier}x`; renderScaledIngredients(); } function renderScaledIngredients() { elements.recipeDetailIngredients.innerHTML = ''; if (!activeRecipe || !activeRecipe.ingredients) return; activeRecipe.ingredients.forEach((ing) => { const scaledText = scaleIngredientText(ing, currentMultiplier); const li = document.createElement('li'); li.className = 'ingredient-checkbox-item'; li.innerHTML = `