// API Configuration - API keys moved to server const API_URL = '/api/recipes'; // Use a relative URL that will be handled by our server const USE_PLACEHOLDERS = false; // Set to true to force using placeholders instead of API // DOM Elements const searchInput = document.getElementById('search-input'); const searchButton = document.getElementById('search-button'); const featuredRecipesContainer = document.getElementById('featured-recipes'); const searchResultsSection = document.getElementById('search-results-section'); const searchResultsContainer = document.getElementById('search-results'); const recipeDetailsSection = document.getElementById('recipe-details-section'); const recipeDetailsContainer = document.getElementById('recipe-details'); const backToHomeButton = document.getElementById('back-to-home'); const backToResultsButton = document.getElementById('back-to-results'); const printRecipeButton = document.getElementById('print-recipe'); const savePDFButton = document.getElementById('save-pdf'); const loadMoreButton = document.getElementById('load-more-btn'); const popularLink = document.querySelector('nav ul li a[href="#popular"]'); const homeLink = document.querySelector('nav ul li a[href="#"]'); const featuredSection = document.getElementById('featured'); // Global Variables let currentPage = 1; let recipesPerPage = 20; let totalRecipesLoaded = 0; let allPlaceholderRecipes = []; let currentSectionTitle = 'Featured Recipes'; // Extended placeholder recipes for load more functionality const allRecipesList = [ // Original placeholder recipes { id: 1, title: 'Spaghetti Carbonara', readyInMinutes: 30, servings: 4, image: 'https://images.unsplash.com/photo-1612874742237-6526221588e3?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1771&q=80' }, { id: 2, title: 'Chicken Tikka Masala', readyInMinutes: 45, servings: 6, image: 'https://images.unsplash.com/photo-1565557623262-b51c2513a641?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1674&q=80' }, { id: 3, title: 'Vegetable Stir Fry', readyInMinutes: 20, servings: 2, image: 'https://images.unsplash.com/photo-1512621776951-a57141f2eefd?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1770&q=80' }, { id: 4, title: 'Avocado Toast', readyInMinutes: 10, servings: 1, image: 'https://images.unsplash.com/photo-1541519227354-08fa5d50c44d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1772&q=80' }, { id: 5, title: 'Greek Salad', readyInMinutes: 15, servings: 2, image: 'https://images.unsplash.com/photo-1540420773420-3366772f4999?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1768&q=80' }, { id: 6, title: 'Chocolate Chip Cookies', readyInMinutes: 25, servings: 12, image: 'https://images.unsplash.com/photo-1499636136210-6f4ee915583e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1772&q=80' }, // Additional recipes for load more functionality { id: 7, title: 'Beef Burger', readyInMinutes: 35, servings: 4, image: 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1899&q=80' }, { id: 8, title: 'Margherita Pizza', readyInMinutes: 40, servings: 4, image: 'https://images.unsplash.com/photo-1604068549290-dea0e4a305ca?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1674&q=80' }, { id: 9, title: 'Caesar Salad', readyInMinutes: 15, servings: 2, image: 'https://images.unsplash.com/photo-1550304943-4f24f54ddde9?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1770&q=80' }, { id: 10, title: 'Banana Pancakes', readyInMinutes: 20, servings: 3, image: 'https://images.unsplash.com/photo-1567620905732-2d1ec7ab7445?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1780&q=80' }, { id: 11, title: 'Chicken Noodle Soup', readyInMinutes: 50, servings: 6, image: 'https://images.unsplash.com/photo-1582878826629-29b7ad1cdc43?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1674&q=80' }, { id: 12, title: 'Vegetable Lasagna', readyInMinutes: 65, servings: 8, image: 'https://images.unsplash.com/photo-1574894709920-11b28e7367e3?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1770&q=80' }, { id: 13, title: 'Blueberry Muffins', readyInMinutes: 30, servings: 12, image: 'https://images.unsplash.com/photo-1587244141530-6b6aceef93db?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1770&q=80' }, { id: 14, title: 'Beef Tacos', readyInMinutes: 25, servings: 4, image: 'https://images.unsplash.com/photo-1551504734-5ee1c4a1479b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1770&q=80' }, { id: 15, title: 'Chocolate Brownies', readyInMinutes: 35, servings: 9, image: 'https://images.unsplash.com/photo-1606313564200-e75d5e30476c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1772&q=80' }, { id: 16, title: 'Beef Stir Fry', readyInMinutes: 25, servings: 3, image: 'https://images.unsplash.com/photo-1625539990527-856f1e59cf9f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1770&q=80' }, { id: 17, title: 'Lemon Cheesecake', readyInMinutes: 70, servings: 10, image: 'https://images.unsplash.com/photo-1533134242443-d4fd215305ad?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1770&q=80' }, { id: 18, title: 'Chicken Curry', readyInMinutes: 45, servings: 5, image: 'https://images.unsplash.com/photo-1604652013484-28d9b5c66fd7?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1770&q=80' } ]; // Set placeholder recipes to use the expanded list const placeholderRecipes = allRecipesList.slice(0, recipesPerPage); // Recipe placeholder details const placeholderDetails = { 1: { title: 'Spaghetti Carbonara', readyInMinutes: 30, servings: 4, image: 'https://images.unsplash.com/photo-1612874742237-6526221588e3?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1771&q=80', summary: 'A classic Italian pasta dish with creamy egg sauce, pancetta or bacon, and cheese.', ingredients: [ '400g spaghetti', '200g pancetta or bacon, diced', '4 large eggs', '50g pecorino cheese, grated', '50g parmesan, grated', '2 garlic cloves, minced', 'Salt and black pepper to taste' ], instructions: [ 'Bring a large pot of salted water to boil and cook spaghetti according to package instructions.', 'While pasta is cooking, heat a large skillet over medium heat. Add pancetta and cook until crispy.', 'In a bowl, whisk together eggs, grated cheeses, and pepper.', 'Drain pasta, reserving 1/2 cup of pasta water.', 'Working quickly, add hot pasta to the skillet with pancetta, tossing to combine.', 'Remove skillet from heat and pour in egg mixture, tossing constantly to create a creamy sauce. Add pasta water as needed for consistency.', 'Season with salt and pepper to taste. Serve immediately with extra grated cheese.' ] }, 2: { title: 'Chicken Tikka Masala', readyInMinutes: 45, servings: 6, image: 'https://images.unsplash.com/photo-1565557623262-b51c2513a641?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1674&q=80', summary: 'A flavorful Indian curry dish made with marinated chicken pieces in a creamy tomato sauce.', ingredients: [ '800g boneless chicken breasts, cut into bite-sized pieces', '1 cup plain yogurt', '2 tbsp lemon juice', '2 tsp cumin', '2 tsp red chili powder', '2 tsp ground coriander', '2 tbsp ginger-garlic paste', '3 tbsp vegetable oil', '1 large onion, finely chopped', '2 tbsp tomato paste', '1 can (400g) tomato sauce', '1 cup heavy cream', 'Fresh cilantro for garnish' ], instructions: [ 'In a bowl, mix yogurt, lemon juice, cumin, red chili powder, coriander, and ginger-garlic paste.', 'Add chicken pieces to the marinade, cover, and refrigerate for at least 1 hour.', 'Heat oil in a large skillet over medium-high heat. Add marinated chicken and cook until no longer pink.', 'Remove chicken and set aside. In the same skillet, add onion and cook until soft.', 'Add tomato paste and cook for 2 minutes. Pour in tomato sauce and bring to a simmer.', 'Return chicken to the skillet, cover, and simmer for 10 minutes.', 'Stir in heavy cream and cook for another 5 minutes. Garnish with cilantro and serve with rice or naan.' ] }, 3: { title: 'Vegetable Stir Fry', readyInMinutes: 20, servings: 2, image: 'https://images.unsplash.com/photo-1512621776951-a57141f2eefd?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1770&q=80', summary: 'A quick and healthy vegetable stir fry with a savory sauce.', ingredients: [ '2 tbsp vegetable oil', '2 garlic cloves, minced', '1 tbsp ginger, minced', '1 bell pepper, sliced', '1 carrot, julienned', '1 cup broccoli florets', '1 cup snap peas', '1 cup mushrooms, sliced', '2 tbsp soy sauce', '1 tbsp oyster sauce', '1 tsp sesame oil', '1 tsp cornstarch mixed with 2 tbsp water', 'Green onions for garnish' ], instructions: [ 'Heat oil in a wok or large skillet over high heat.', 'Add garlic and ginger, stir for 30 seconds until fragrant.', 'Add vegetables, starting with the ones that take longer to cook (carrots, broccoli).', 'Stir fry for 4-5 minutes until vegetables begin to soften but remain crisp.', 'Add soy sauce, oyster sauce, and sesame oil. Toss to combine.', 'Pour in cornstarch slurry and cook until sauce thickens, about 1 minute.', 'Garnish with green onions and serve hot with rice or noodles.' ] }, 4: { title: 'Avocado Toast', readyInMinutes: 10, servings: 1, image: 'https://images.unsplash.com/photo-1541519227354-08fa5d50c44d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1772&q=80', summary: 'A simple and nutritious breakfast or snack made with mashed avocado on toasted bread.', ingredients: [ '2 slices of bread (sourdough or whole grain)', '1 ripe avocado', '1 tbsp lemon juice', 'Salt and pepper to taste', 'Red pepper flakes (optional)', '2 eggs (optional)', 'Fresh herbs like cilantro or chives (optional)' ], instructions: [ 'Toast the bread slices until golden and crisp.', 'Cut the avocado in half, remove the pit, and scoop the flesh into a bowl.', 'Add lemon juice, salt, and pepper to the avocado and mash with a fork to desired consistency.', 'Spread the mashed avocado evenly on the toast slices.', 'If desired, top with fried or poached eggs.', 'Sprinkle with red pepper flakes and fresh herbs if using.', 'Serve immediately.' ] }, 5: { title: 'Greek Salad', readyInMinutes: 15, servings: 2, image: 'https://images.unsplash.com/photo-1540420773420-3366772f4999?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1768&q=80', summary: 'A refreshing Mediterranean salad with crisp vegetables, olives, and feta cheese.', ingredients: [ '1 large cucumber, diced', '2 medium tomatoes, diced', '1 red onion, thinly sliced', '1 green bell pepper, diced', '1 cup kalamata olives', '200g feta cheese, cubed', '2 tbsp extra virgin olive oil', '1 tbsp red wine vinegar', '1 tsp dried oregano', 'Salt and pepper to taste' ], instructions: [ 'In a large bowl, combine cucumber, tomatoes, red onion, and bell pepper.', 'Add kalamata olives and feta cheese cubes.', 'In a small bowl, whisk together olive oil, red wine vinegar, dried oregano, salt, and pepper.', 'Pour the dressing over the salad and toss gently to combine.', 'Let the salad sit for about 10 minutes before serving to allow flavors to meld.', 'Serve chilled as a side dish or light meal.' ] }, 6: { title: 'Chocolate Chip Cookies', readyInMinutes: 25, servings: 12, image: 'https://images.unsplash.com/photo-1499636136210-6f4ee915583e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1772&q=80', summary: 'Classic homemade chocolate chip cookies with a soft center and crisp edges.', ingredients: [ '225g butter, softened', '150g granulated sugar', '165g brown sugar', '2 large eggs', '1 tsp vanilla extract', '280g all-purpose flour', '1 tsp baking soda', '1/2 tsp salt', '300g chocolate chips', '100g chopped nuts (optional)' ], instructions: [ 'Preheat oven to 375°F (190°C). Line baking sheets with parchment paper.', 'In a large bowl, cream together butter, granulated sugar, and brown sugar until light and fluffy.', 'Beat in eggs one at a time, then stir in vanilla extract.', 'In a separate bowl, combine flour, baking soda, and salt. Gradually add to the wet ingredients and mix well.', 'Fold in chocolate chips and nuts if using.', 'Drop rounded tablespoons of dough onto the prepared baking sheets, spacing them about 2 inches apart.', 'Bake for 9-11 minutes or until golden brown. Cool on baking sheets for 2 minutes, then transfer to wire racks.' ] } }; // Event Listeners document.addEventListener('DOMContentLoaded', initializeApp); searchButton.addEventListener('click', handleSearch); searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { handleSearch(); } }); backToHomeButton.addEventListener('click', showFeaturedRecipes); backToResultsButton.addEventListener('click', showSearchResults); printRecipeButton.addEventListener('click', printRecipe); savePDFButton.addEventListener('click', savePDF); loadMoreButton.addEventListener('click', loadMoreRecipes); popularLink.addEventListener('click', showPopularRecipes); homeLink.addEventListener('click', function(e) { e.preventDefault(); showFeaturedRecipes(); }); // Functions function initializeApp() { // Initialize the all recipes list to be used for "load more" functionality allPlaceholderRecipes = [...allRecipesList]; // Load initial featured recipes loadFeaturedRecipes(); // Update load more button state updateLoadMoreButtonState(); } function loadFeaturedRecipes() { // Reset pagination variables on initial load currentPage = 1; totalRecipesLoaded = 0; featuredRecipesContainer.innerHTML = '
'; // For tracking timeout let apiTimedOut = false; let apiResponseReceived = false; if (USE_PLACEHOLDERS) { // Use placeholder recipes if placeholders are forced const firstPageRecipes = allRecipesList.slice(0, recipesPerPage); totalRecipesLoaded = firstPageRecipes.length; displayRecipes(firstPageRecipes, featuredRecipesContainer, false); updateLoadMoreButtonState(); return; } // Set a timeout to detect if the API is taking too long const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { if (!apiResponseReceived) { apiTimedOut = true; reject(new Error('API request timed out after 5 seconds')); } }, 5000); }); // Actual API fetch const fetchPromise = fetch(`${API_URL}/random?number=${recipesPerPage}`) .then(response => { apiResponseReceived = true; if (!response.ok) { throw new Error(`API error: ${response.status} ${response.statusText}`); } return response.json(); }) .then(data => { // Check for both TheMealDB format (meals) and our expected format (recipes) let recipes = []; if (data && data.meals && data.meals.length > 0) { // TheMealDB format conversion recipes = data.meals.map(meal => ({ id: meal.idMeal, title: meal.strMeal, readyInMinutes: 30, // Default value as TheMealDB doesn't provide this servings: 4, // Default value as TheMealDB doesn't provide this image: meal.strMealThumb })); } else if (data && data.recipes && data.recipes.length > 0) { recipes = data.recipes; } else { throw new Error('No recipes found in API response'); } totalRecipesLoaded = recipes.length; displayRecipes(recipes, featuredRecipesContainer, false); updateLoadMoreButtonState(); }); // Race between timeout and successful fetch Promise.race([fetchPromise, timeoutPromise]) .catch(error => { console.error('Error loading recipes:', error); // Only show error if timed out if (apiTimedOut) { featuredRecipesContainer.innerHTML = `
⚠️

API Connection Failed

Unable to connect to the recipe database. We'll show you some sample recipes instead.

`; // Fall back to placeholder recipes after a brief delay setTimeout(() => { const firstPageRecipes = allRecipesList.slice(0, recipesPerPage); totalRecipesLoaded = firstPageRecipes.length; displayRecipes(firstPageRecipes, featuredRecipesContainer, false); updateLoadMoreButtonState(); }, 1000); } }); } function loadMoreRecipes() { // Show loading state loadMoreButton.textContent = 'Loading...'; loadMoreButton.classList.add('loading'); if (USE_PLACEHOLDERS) { // Load the next page of placeholder recipes const nextPage = currentPage + 1; const startIndex = totalRecipesLoaded; const endIndex = startIndex + recipesPerPage; // Check if there are more recipes to load if (startIndex >= allRecipesList.length) { updateLoadMoreButtonState(true); return; } // Get the next batch of recipes const nextBatchRecipes = allRecipesList.slice(startIndex, endIndex); // Simulate network delay setTimeout(() => { // Add more recipes to the container displayRecipes(nextBatchRecipes, featuredRecipesContainer, true); // Update state currentPage = nextPage; totalRecipesLoaded += nextBatchRecipes.length; // Update button state updateLoadMoreButtonState(); }, 800); return; } // If not using placeholders, load next batch of random recipes fetch(`${API_URL}/random?number=${recipesPerPage}`) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { // Check for TheMealDB format let moreRecipes = []; if (data && data.meals && data.meals.length > 0) { // Convert TheMealDB format moreRecipes = data.meals.map(meal => ({ id: meal.idMeal, title: meal.strMeal, readyInMinutes: 30, // Default value servings: 4, // Default value image: meal.strMealThumb })); } else if (data && data.recipes && data.recipes.length > 0) { moreRecipes = data.recipes; } else { throw new Error('No more recipes available'); } // Append more recipes to the container displayRecipes(moreRecipes, featuredRecipesContainer, true); // Update state currentPage++; totalRecipesLoaded += moreRecipes.length; // Update button state and reset button text updateLoadMoreButtonState(); loadMoreButton.textContent = 'Load More Recipes'; loadMoreButton.classList.remove('loading'); }) .catch(error => { console.error('Error fetching more recipes:', error); // Use placeholder recipes if API fails const nextPage = currentPage + 1; const startIndex = totalRecipesLoaded; const endIndex = startIndex + recipesPerPage; if (startIndex < allRecipesList.length) { const nextBatchRecipes = allRecipesList.slice(startIndex, endIndex); displayRecipes(nextBatchRecipes, featuredRecipesContainer, true); currentPage = nextPage; totalRecipesLoaded += nextBatchRecipes.length; } updateLoadMoreButtonState(); loadMoreButton.textContent = 'Load More Recipes'; loadMoreButton.classList.remove('loading'); }); } function updateLoadMoreButtonState(forceHide = false) { // Reset button style loadMoreButton.textContent = 'Load More Recipes'; loadMoreButton.classList.remove('loading'); // If we're using placeholder data, check if we've loaded all available recipes if (USE_PLACEHOLDERS && (totalRecipesLoaded >= allRecipesList.length || forceHide)) { loadMoreButton.style.display = 'none'; } else { loadMoreButton.style.display = 'block'; } } function handleSearch() { const query = searchInput.value.trim(); if (query === '') { alert('Please enter a search term'); return; } // Show searching state showSearchResults(); searchResultsContainer.innerHTML = '
'; // For tracking timeout let apiTimedOut = false; let apiResponseReceived = false; if (USE_PLACEHOLDERS) { // Filter placeholder recipes that match the search term const results = allRecipesList.filter(recipe => recipe.title.toLowerCase().includes(query.toLowerCase()) ); if (results.length === 0) { searchResultsContainer.innerHTML = `
🔍

No Matches Found

We couldn't find any recipes matching "${query}". Try a different search term.

`; } else { displayRecipes(results, searchResultsContainer); } return; } // Set a timeout to detect if the API is taking too long const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { if (!apiResponseReceived) { apiTimedOut = true; reject(new Error('API request timed out after 5 seconds')); } }, 5000); }); // Actual API fetch const fetchPromise = fetch(`${API_URL}/search?s=${encodeURIComponent(query)}`) .then(response => { apiResponseReceived = true; if (!response.ok) { throw new Error(`API error: ${response.status} ${response.statusText}`); } return response.json(); }) .then(data => { // Handle TheMealDB format let results = []; if (data && data.meals && data.meals.length > 0) { // TheMealDB format conversion results = data.meals.map(meal => ({ id: meal.idMeal, title: meal.strMeal, readyInMinutes: 30, // Default value servings: 4, // Default value image: meal.strMealThumb })); displayRecipes(results, searchResultsContainer); } else if (data && data.results && data.results.length > 0) { // Original format displayRecipes(data.results, searchResultsContainer); } else { searchResultsContainer.innerHTML = `
🔍

No Matches Found

We couldn't find any recipes matching "${query}". Try a different search term.

`; } }); // Race between timeout and successful fetch Promise.race([fetchPromise, timeoutPromise]) .catch(error => { console.error('Error searching recipes:', error); // Only show error if timed out if (apiTimedOut) { searchResultsContainer.innerHTML = `
⚠️

Search Failed

We couldn't complete your search. This could be due to a connection issue.

Trying to search for: "${query}"

`; } }); } function displayRecipes(recipes, container, append = false) { // If not appending, clear the container if (!append) { container.innerHTML = ''; } else { // Remove any loading indicators if present const loadingElements = container.querySelectorAll('.loading'); loadingElements.forEach(el => el.remove()); } // If no recipes found if (!recipes || recipes.length === 0) { if (!append) { container.innerHTML = '
No recipes found. Try a different search term.
'; } return; } recipes.forEach(recipe => { const recipeCard = document.createElement('div'); recipeCard.className = 'recipe-card'; recipeCard.innerHTML = `
${recipe.title}

${recipe.title}

${recipe.readyInMinutes || '30'} mins ${recipe.servings || '4'} servings
View Recipe
`; container.appendChild(recipeCard); // Add event listener to view recipe button const viewButton = recipeCard.querySelector('.view-recipe'); viewButton.addEventListener('click', (e) => { e.preventDefault(); const recipeId = viewButton.dataset.id; getRecipeDetails(recipeId); }); }); } function getRecipeDetails(recipeId) { recipeDetailsContainer.innerHTML = '
'; showRecipeDetails(); // For tracking timeout let apiTimedOut = false; let apiResponseReceived = false; if (USE_PLACEHOLDERS) { // Use placeholder recipe details const recipe = placeholderDetails[recipeId]; if (recipe) { displayRecipeDetails(recipe); } else { recipeDetailsContainer.innerHTML = `
⚠️

Recipe Not Found

Sorry, we couldn't find details for this recipe.

`; } return; } // Set a timeout to detect if the API is taking too long const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { if (!apiResponseReceived) { apiTimedOut = true; reject(new Error('API request timed out after 5 seconds')); } }, 5000); }); // Actual API fetch const fetchPromise = fetch(`${API_URL}/lookup?i=${recipeId}`) .then(response => { apiResponseReceived = true; if (!response.ok) { throw new Error(`API error: ${response.status} ${response.statusText}`); } return response.json(); }) .then(data => { // Handle TheMealDB format if (data && data.meals && data.meals.length > 0) { const meal = data.meals[0]; // Convert TheMealDB format to our expected format const recipe = { id: meal.idMeal, title: meal.strMeal, readyInMinutes: 30, // Default value servings: 4, // Default value image: meal.strMealThumb, summary: `A delicious ${meal.strMeal} recipe from ${meal.strArea} cuisine.`, // Extract ingredients ingredients: [] }; // Extract ingredients and measures for (let i = 1; i <= 20; i++) { const ingredient = meal[`strIngredient${i}`]; const measure = meal[`strMeasure${i}`]; if (ingredient && ingredient.trim() !== '') { recipe.ingredients.push(`${measure} ${ingredient}`.trim()); } } // Format instructions recipe.instructions = meal.strInstructions .split(/\r\n|\r|\n/) .filter(step => step.trim() !== ''); displayRecipeDetails(recipe); } else { displayRecipeDetails(data); } }); // Race between timeout and successful fetch Promise.race([fetchPromise, timeoutPromise]) .catch(error => { console.error('Error fetching recipe details:', error); // Only show error if timed out if (apiTimedOut) { recipeDetailsContainer.innerHTML = `
⚠️

Connection Error

We couldn't load the recipe details. This could be due to a network issue or the recipe might not exist anymore.

`; } }); } function displayRecipeDetails(recipe) { // Create the recipe header with image and title const recipeHeader = document.createElement('div'); recipeHeader.className = 'recipe-header'; recipeHeader.innerHTML = ` ${recipe.title}

${recipe.title}

⏱️ ${recipe.readyInMinutes} minutes
👥 ${recipe.servings} servings
`; // Create the recipe summary const recipeSummary = document.createElement('div'); recipeSummary.className = 'recipe-summary'; recipeSummary.innerHTML = `

${recipe.summary || 'A delicious recipe that you will love.'}

`; // Create the ingredients section const ingredientsContainer = document.createElement('div'); ingredientsContainer.className = 'ingredients-container'; ingredientsContainer.innerHTML = '

Ingredients

'; const ingredientsList = document.createElement('ul'); ingredientsList.className = 'ingredients-list'; // Handle both API and placeholder formats if (recipe.extendedIngredients) { recipe.extendedIngredients.forEach(ingredient => { const li = document.createElement('li'); li.textContent = ingredient.original; ingredientsList.appendChild(li); }); } else if (recipe.ingredients) { recipe.ingredients.forEach(ingredient => { const li = document.createElement('li'); li.textContent = ingredient; ingredientsList.appendChild(li); }); } ingredientsContainer.appendChild(ingredientsList); // Create the instructions section const instructionsContainer = document.createElement('div'); instructionsContainer.className = 'instructions-container'; instructionsContainer.innerHTML = '

Instructions

'; const instructionsList = document.createElement('ol'); instructionsList.className = 'instructions-list'; // Handle both API and placeholder formats if (recipe.analyzedInstructions && recipe.analyzedInstructions.length > 0) { recipe.analyzedInstructions[0].steps.forEach(step => { const li = document.createElement('li'); li.textContent = step.step; instructionsList.appendChild(li); }); } else if (recipe.instructions) { // If instructions is a string, split by periods or newlines if (typeof recipe.instructions === 'string') { const steps = recipe.instructions.split(/\.\s|\n/).filter(step => step.trim()); steps.forEach(step => { const li = document.createElement('li'); li.textContent = step.trim(); instructionsList.appendChild(li); }); } } else if (recipe.instruction) { // Alternate property name if (typeof recipe.instruction === 'string') { const steps = recipe.instruction.split(/\.\s|\n/).filter(step => step.trim()); steps.forEach(step => { const li = document.createElement('li'); li.textContent = step.trim(); instructionsList.appendChild(li); }); } } else if (Array.isArray(recipe.instructions)) { // If instructions is already an array (for placeholder data) recipe.instructions.forEach(step => { const li = document.createElement('li'); li.textContent = step; instructionsList.appendChild(li); }); } instructionsContainer.appendChild(instructionsList); // Assemble all sections recipeDetailsContainer.innerHTML = ''; recipeDetailsContainer.appendChild(recipeHeader); recipeDetailsContainer.appendChild(recipeSummary); recipeDetailsContainer.appendChild(ingredientsContainer); recipeDetailsContainer.appendChild(instructionsContainer); } function showFeaturedRecipes() { // Update section title const featuredTitle = document.querySelector('#featured h2'); currentSectionTitle = 'Featured Recipes'; featuredTitle.textContent = currentSectionTitle; // Update active link document.querySelectorAll('nav ul li a').forEach(link => { link.classList.remove('active'); }); document.querySelector('nav ul li a[href="#"]').classList.add('active'); searchResultsSection.classList.add('hidden'); recipeDetailsSection.classList.add('hidden'); featuredSection.classList.remove('hidden'); searchInput.value = ''; // Reset and reload featured recipes currentPage = 1; totalRecipesLoaded = 0; loadFeaturedRecipes(); updateLoadMoreButtonState(); } function showSearchResults() { document.getElementById('featured').classList.add('hidden'); recipeDetailsSection.classList.add('hidden'); searchResultsSection.classList.remove('hidden'); } function showRecipeDetails() { document.getElementById('featured').classList.add('hidden'); recipeDetailsSection.classList.remove('hidden'); } function printRecipe() { window.print(); } function savePDF() { alert('This feature would save the recipe as a PDF. In a real application, we would implement PDF generation with a library like jsPDF.'); } function showPopularRecipes(e) { e.preventDefault(); // Update section title const featuredTitle = document.querySelector('#featured h2'); currentSectionTitle = 'Popular Recipes'; featuredTitle.textContent = currentSectionTitle; // Reset pagination for the popular section currentPage = 1; totalRecipesLoaded = 0; // Make both featured and popular sections use the same container searchResultsSection.classList.add('hidden'); recipeDetailsSection.classList.add('hidden'); featuredSection.classList.remove('hidden'); // Show loading state featuredRecipesContainer.innerHTML = '
'; // Update active link document.querySelectorAll('nav ul li a').forEach(link => { link.classList.remove('active'); }); popularLink.classList.add('active'); // For tracking timeout let apiTimedOut = false; let apiResponseReceived = false; if (USE_PLACEHOLDERS) { // If using placeholders, pick 5 random recipes from the placeholder recipes const randomRecipes = getRandomPlaceholderRecipes(5); totalRecipesLoaded = randomRecipes.length; displayRecipes(randomRecipes, featuredRecipesContainer, false); // Hide the load more button for popular section loadMoreButton.style.display = 'none'; return; } // Set a timeout to detect if the API is taking too long const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { if (!apiResponseReceived) { apiTimedOut = true; reject(new Error('API request timed out after 5 seconds')); } }, 5000); }); // Actual API fetch const fetchPromise = fetch(`${API_URL}/random?number=5`) .then(response => { apiResponseReceived = true; if (!response.ok) { throw new Error(`API error: ${response.status} ${response.statusText}`); } return response.json(); }) .then(data => { // Check for both TheMealDB format (meals) and our expected format (recipes) let recipes = []; if (data && data.meals && data.meals.length > 0) { // TheMealDB format conversion recipes = data.meals.map(meal => ({ id: meal.idMeal, title: meal.strMeal, readyInMinutes: 30, // Default value as TheMealDB doesn't provide this servings: 4, // Default value as TheMealDB doesn't provide this image: meal.strMealThumb })); } else if (data && data.recipes && data.recipes.length > 0) { recipes = data.recipes; } else { throw new Error('No popular recipes found in API response'); } totalRecipesLoaded = recipes.length; displayRecipes(recipes, featuredRecipesContainer, false); // Hide the load more button for popular section loadMoreButton.style.display = 'none'; }); // Race between timeout and successful fetch Promise.race([fetchPromise, timeoutPromise]) .catch(error => { console.error('Error loading popular recipes:', error); // Only show error if timed out if (apiTimedOut) { featuredRecipesContainer.innerHTML = `
⚠️

Couldn't Load Popular Recipes

We'll show you some sample popular recipes instead.

`; // Fall back to placeholder recipes after a brief delay setTimeout(() => { const randomRecipes = getRandomPlaceholderRecipes(5); totalRecipesLoaded = randomRecipes.length; displayRecipes(randomRecipes, featuredRecipesContainer, false); loadMoreButton.style.display = 'none'; }, 1000); } }); } // Helper function to get random recipes from placeholder data function getRandomPlaceholderRecipes(count) { const shuffled = [...allRecipesList].sort(() => 0.5 - Math.random()); return shuffled.slice(0, count); }