Mary Berry’s Chocolate Cake

Servings 4
Jump to Ingredients Jump to Recipe

Ingredients

4 servings

Instructions

Shopping List

No items yet. Click "Add All" or use + buttons!

Cooking Mode

1 / 1
`); w.print(); w.close(); });// Add All Ingredients button const addAllBtn = document.getElementById('addAllIngredients');function getAllIngredientTexts() { const texts = []; document.querySelectorAll('.ingredients-list li').forEach(li => { let text = li.dataset.ingredient; if (!text) { text = li.cloneNode(true).textContent.trim().replace(/[\+✓✔]/g, '').trim(); } if (text) texts.push(text); }); return texts; }function updateAddAllButton() { const allIngredients = getAllIngredientTexts(); const allAdded = allIngredients.length > 0 && allIngredients.every(text => shoppingItems.find(x => x.text === text) ); if (addAllBtn) { if (allAdded) { addAllBtn.classList.add('added'); addAllBtn.innerHTML = ' All Added ✓'; } else { addAllBtn.classList.remove('added'); addAllBtn.innerHTML = ' Add All Ingredients'; } } }if (addAllBtn) { addAllBtn.addEventListener('click', function () { const allIngredients = getAllIngredientTexts(); allIngredients.forEach(text => { if (text && !shoppingItems.find(x => x.text === text)) { shoppingItems.push({ text: text, checked: false }); } }); updateShoppingListUI(); updateAddAllButton(); updateIngredientButtons(); }); }// Email Shopping List const emailBtn = document.getElementById('emailShoppingList'); if (emailBtn) { emailBtn.addEventListener('click', () => { const recipeTitle = document.querySelector('.recipe-hero h1, .recipe-title, h1')?.textContent || 'Shopping List'; const listText = shoppingItems.map(x => `${x.checked ? '✓' : '○'} ${x.text}`).join('%0A'); const subject = encodeURIComponent(`Shopping List: ${recipeTitle}`); const body = encodeURIComponent(`🛒 Shopping List for: ${recipeTitle}\n\n`) + listText + encodeURIComponent('\n\n---\nGenerated from Home Recipes'); window.location.href = `mailto:?subject=${subject}&body=${body}`; }); }function updateIngredientButtons() { document.querySelectorAll('.ingredients-list li').forEach(li => { const btn = li.querySelector('.add-to-list-btn'); if (btn) { const text = li.dataset.ingredient || li.textContent.replace(/[\+✓✔]/g, '').trim(); if (shoppingItems.find(x => x.text === text)) { btn.classList.add('added'); btn.innerHTML = ''; } else { btn.classList.remove('added'); btn.innerHTML = ''; } } }); }updateShoppingListUI();// Add plus buttons to ingredients and store clean text document.querySelectorAll('.ingredients-list li').forEach(li => { const cleanText = li.textContent.trim(); li.dataset.ingredient = cleanText;const btn = document.createElement('button'); btn.className = 'add-to-list-btn'; if (shoppingItems.find(item => item.text === cleanText)) { btn.classList.add('added'); btn.innerHTML = ''; } else { btn.innerHTML = ''; } btn.onclick = function () { const text = li.dataset.ingredient; const exists = shoppingItems.find(item => item.text === text); if (!exists) { shoppingItems.push({ text: text, checked: false }); this.classList.add('added'); this.innerHTML = ''; } else { shoppingItems = shoppingItems.filter(item => item.text !== text); this.classList.remove('added'); this.innerHTML = ''; } updateShoppingListUI(); updateAddAllButton(); }; li.appendChild(btn); });updateAddAllButton();// ============================================ // COOKING MODE // ============================================ const cookingModeOverlay = document.getElementById('cookingModeOverlay'); const openCookingMode = document.getElementById('openCookingMode'); const closeCookingMode = document.getElementById('closeCookingMode'); const currentStepText = document.getElementById('currentStepText'); const currentStepNum = document.getElementById('currentStepNum'); const totalStepsEl = document.getElementById('totalSteps'); const prevStepBtn = document.getElementById('prevStep'); const nextStepBtn = document.getElementById('nextStep'); const speakStepBtn = document.getElementById('speakStep');let cookingSteps = []; let currentStep = 0;// Collect all steps document.querySelectorAll('.step-text').forEach(el => { cookingSteps.push(el.textContent.trim()); });function updateCookingMode() { if (cookingSteps.length === 0) return; currentStepText.textContent = cookingSteps[currentStep]; currentStepNum.textContent = currentStep + 1; totalStepsEl.textContent = cookingSteps.length; prevStepBtn.disabled = currentStep === 0; nextStepBtn.disabled = currentStep === cookingSteps.length - 1; }if (openCookingMode) openCookingMode.addEventListener('click', () => { currentStep = 0; updateCookingMode(); cookingModeOverlay.classList.add('active'); document.body.style.overflow = 'hidden'; });if (closeCookingMode) closeCookingMode.addEventListener('click', () => { cookingModeOverlay.classList.remove('active'); document.body.style.overflow = ''; speechSynthesis.cancel(); });if (prevStepBtn) prevStepBtn.addEventListener('click', () => { if (currentStep > 0) { currentStep--; updateCookingMode(); } }); if (nextStepBtn) nextStepBtn.addEventListener('click', () => { if (currentStep < cookingSteps.length - 1) { currentStep++; updateCookingMode(); } });// ============================================ // TEXT-TO-SPEECH (English) // ============================================ function speakText(text, btn) { if ('speechSynthesis' in window) { speechSynthesis.cancel(); const utterance = new SpeechSynthesisUtterance(text); utterance.lang = 'en-US'; utterance.rate = 0.9;// Get English voice const voices = speechSynthesis.getVoices(); const englishVoice = voices.find(v => v.lang.startsWith('en')) || voices[0]; if (englishVoice) utterance.voice = englishVoice;if (btn) { btn.classList.add('speaking'); utterance.onend = () => btn.classList.remove('speaking'); }speechSynthesis.speak(utterance); } }if (speakStepBtn) speakStepBtn.addEventListener('click', function () { speakText(cookingSteps[currentStep], this); });// Add TTS buttons to each step document.querySelectorAll('.step-item').forEach(item => { const stepText = item.querySelector('.step-text'); if (stepText) { const btn = document.createElement('button'); btn.className = 'tts-btn'; btn.innerHTML = ''; btn.title = 'Read aloud'; btn.onclick = function () { speakText(stepText.textContent, this); }; item.appendChild(btn); } });// Load voices if ('speechSynthesis' in window) { speechSynthesis.getVoices(); }// Search Toggle const searchToggle = document.getElementById('searchToggle'); const searchDropdown = document.getElementById('searchDropdown'); if (searchToggle && searchDropdown) { searchToggle.addEventListener('click', function () { searchDropdown.classList.toggle('active'); if (searchDropdown.classList.contains('active')) { searchDropdown.querySelector('.search-input').focus(); } }); // Close on click outside document.addEventListener('click', function (e) { if (!searchDropdown.contains(e.target) && !searchToggle.contains(e.target)) { searchDropdown.classList.remove('active'); } }); }// Dark Mode Toggle const themeToggle = document.getElementById('themeToggle'); if (themeToggle) { // Load saved theme const savedTheme = localStorage.getItem('theme'); if (savedTheme) { document.documentElement.setAttribute('data-theme', savedTheme); }themeToggle.addEventListener('click', function () { const currentTheme = document.documentElement.getAttribute('data-theme'); const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', newTheme); localStorage.setItem('theme', newTheme);// Update icon const icon = this.querySelector('i'); if (icon) { icon.className = newTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon'; } }); }// Table of Contents Toggle const tocToggle = document.getElementById('tocToggle'); const tocContainer = document.querySelector('.toc-container'); if (tocToggle && tocContainer) { tocToggle.addEventListener('click', function () { tocContainer.classList.toggle('open'); }); }// Star Rating System const ratingStars = document.querySelectorAll('#ratingStars i'); const ratingValue = document.getElementById('ratingValue'); const ratingCount = document.getElementById('ratingCount'); const recipeId = '1852';// Load saved rating from localStorage const savedRating = localStorage.getItem('recipe_rating_' + recipeId); const savedTotalRatings = parseInt(localStorage.getItem('recipe_ratings_count_' + recipeId)) || 0; const savedAverage = parseFloat(localStorage.getItem('recipe_ratings_avg_' + recipeId)) || 0;if (savedRating) { ratingStars.forEach((star, index) => { if (index < parseInt(savedRating)) { star.classList.add('active'); star.classList.remove('far'); star.classList.add('fas'); } }); }if (ratingValue) ratingValue.textContent = savedAverage.toFixed(1); if (ratingCount) ratingCount.textContent = savedTotalRatings + ' ratings';ratingStars.forEach(star => { star.addEventListener('mouseenter', function () { const rating = parseInt(this.dataset.rating); ratingStars.forEach((s, index) => { if (index < rating) { s.classList.add('hovered'); } else { s.classList.remove('hovered'); } }); });star.addEventListener('mouseleave', function () { ratingStars.forEach(s => s.classList.remove('hovered')); });star.addEventListener('click', function () { const rating = parseInt(this.dataset.rating);// Update visual ratingStars.forEach((s, index) => { if (index < rating) { s.classList.add('active'); s.classList.remove('far'); s.classList.add('fas'); } else { s.classList.remove('active'); s.classList.remove('fas'); s.classList.add('far'); } });// Save to localStorage localStorage.setItem('recipe_rating_' + recipeId, rating);// Update count and average (simulated) const newCount = savedTotalRatings + 1; const newAverage = ((savedAverage * savedTotalRatings) + rating) / newCount; localStorage.setItem('recipe_ratings_count_' + recipeId, newCount); localStorage.setItem('recipe_ratings_avg_' + recipeId, newAverage);if (ratingValue) ratingValue.textContent = newAverage.toFixed(1); if (ratingCount) ratingCount.textContent = newCount + ' ratings';// Show thank you message const card = document.querySelector('.rate-recipe-card'); if (card && !card.querySelector('.rating-thanks')) { const thanks = document.createElement('div'); thanks.className = 'rating-thanks'; thanks.style.cssText = 'color: #059669; font-weight: 500; margin-top: 0.75rem;'; thanks.textContent = 'Thanks for rating!'; card.appendChild(thanks); setTimeout(() => thanks.remove(), 3000); } }); }); });