From 0e0cb75d3e723f9747026944e419e4a5e91c0aff Mon Sep 17 00:00:00 2001 From: "Eugene \"Pebbles\" Akiwumi" <62018288+akiwumi@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:52:14 +0200 Subject: [PATCH 1/5] Add files via upload --- README.md | 1 - index.html | 57 +++++++++++++ script.js | 109 +++++++++++++++++++++++++ styles.css | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 395 insertions(+), 1 deletion(-) create mode 100644 index.html create mode 100644 script.js create mode 100644 styles.css diff --git a/README.md b/README.md index 58f1a8a66..e69de29bb 100644 --- a/README.md +++ b/README.md @@ -1 +0,0 @@ -# js-project-recipe-library diff --git a/index.html b/index.html new file mode 100644 index 000000000..af1eecc3e --- /dev/null +++ b/index.html @@ -0,0 +1,57 @@ + + + + + + + + Recipe Finder + + +

Recipe Finder

+ +
+ + + + + + + + + +
+ +
+ + + + + diff --git a/script.js b/script.js new file mode 100644 index 000000000..87b9c5fa5 --- /dev/null +++ b/script.js @@ -0,0 +1,109 @@ +const apiKey = "a229b49155dd40a782013d0a5e906f70"; + +// Cached DOM references +const inputEl = document.getElementById("ingredient-input"); +const mealTypeEl = document.getElementById("meal-type"); +const countryEl = document.getElementById("country-select"); +const resultsEl = document.getElementById("results"); +const detailsEl = document.getElementById("recipe-details"); +const detailsContentEl = document.getElementById("recipe-content"); + +// Search function with filters +async function searchRecipes() { + const ingredient = (inputEl?.value || "").trim(); + const mealType = mealTypeEl.value; + const cuisine = countryEl.value; + + if (!ingredient) { + resultsEl.innerHTML = "

Please enter an ingredient.

"; + return; + } + + try { + resultsEl.innerHTML = "

Searching…

"; + + // Use complexSearch for cuisine & type filters + const url = `https://api.spoonacular.com/recipes/complexSearch?query=${encodeURIComponent( + ingredient + )}&type=${encodeURIComponent(mealType)}&cuisine=${encodeURIComponent( + cuisine + )}&number=6&addRecipeInformation=true&apiKey=${apiKey}`; + + const response = await fetch(url); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + + const data = await response.json(); + + if (!data.results || data.results.length === 0) { + resultsEl.innerHTML = "

No recipes found.

"; + return; + } + + // Render recipe cards + resultsEl.innerHTML = ""; + data.results.forEach((recipe) => { + const card = document.createElement("div"); + card.className = "recipe-item"; + + const img = document.createElement("img"); + img.src = recipe.image; + img.alt = recipe.title; + + const title = document.createElement("h3"); + title.textContent = recipe.title; + + const link = document.createElement("a"); + link.href = "#"; + link.textContent = "View Recipe"; + link.addEventListener("click", async (e) => { + e.preventDefault(); + await showRecipeDetails(recipe.id); + }); + + card.append(img, title, link); + resultsEl.appendChild(card); + }); + } catch (err) { + console.error(err); + resultsEl.innerHTML = "

Something went wrong. Please try again.

"; + } +} + +// Fetch and display detailed recipe info +async function showRecipeDetails(recipeId) { + try { + detailsContentEl.innerHTML = "

Loading…

"; + detailsEl.style.display = "flex"; + + const response = await fetch( + `https://api.spoonacular.com/recipes/${recipeId}/information?apiKey=${apiKey}` + ); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + + const recipe = await response.json(); + + const ingredients = Array.isArray(recipe.extendedIngredients) + ? recipe.extendedIngredients.map((i) => i.original).join(", ") + : "Not available"; + + // Add Close button directly in the card + detailsContentEl.innerHTML = ` + +

${recipe.title}

+ ${recipe.title} +

Cuisine: ${recipe.cuisines.join(", ") || "N/A"}

+

Meal Type: ${recipe.dishTypes.join(", ") || "N/A"}

+

Ingredients: ${ingredients}

+

Instructions: ${ + recipe.instructions || "No instructions provided." + }

+ `; + } catch (err) { + console.error(err); + detailsContentEl.innerHTML = "

Couldn't load recipe details.

"; + } +} + +function closeDetails() { + detailsEl.style.display = "none"; +} diff --git a/styles.css b/styles.css new file mode 100644 index 000000000..43afaefa2 --- /dev/null +++ b/styles.css @@ -0,0 +1,229 @@ +:root { + --primary-color: #fafdff; + --secondary-color: #d4b126; + --header-height: 70px; + --search-height: 50px; + --border-color: #d0cccc; +} + +/* RESET */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* BODY */ +body { + height: 100vh; + width: 100vw; + font-family: Arial, sans-serif; + background-color: var(--primary-color); + display: flex; + flex-direction: column; + align-items: center; +} + +/* TITLE */ +h1 { + margin-top: 20px; + color: #333; + font-size: 2rem; +} + +/* SEARCH FORM */ +#search-form { + display: flex; + justify-content: center; + align-items: center; + gap: 10px; + height: var(--search-height); + margin: 20px 0; +} + +#ingredient-input { + padding: 8px 10px; + font-size: 1rem; + border: 1px solid var(--border-color); + border-radius: 4px; + outline: none; + width: 60%; + transition: all 0.3s ease-in-out; +} + +#ingredient-input:focus { + border-color: var(--secondary-color); + width: 62%; +} + +#search-button { + padding: 8px 16px; + font-size: 1rem; + border-radius: 4px; + background-color: var(--secondary-color); + color: #fff; + border: none; + cursor: pointer; + transition: background 0.3s ease-in-out; +} + +#search-button:hover { + background-color: #b5971f; +} + +/* RESULTS AREA */ +#results { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: flex-start; + gap: 20px; + width: 100%; + max-width: 1200px; + padding: 20px; +} + +/* INDIVIDUAL RECIPE CARD */ +.recipe-item { + width: 280px; + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); + overflow: hidden; + background: #fff; + transition: all 0.2s ease-in-out; +} + +.recipe-item:hover { + transform: translateY(-5px); + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); +} + +.recipe-item img { + width: 100%; + height: 180px; + object-fit: cover; +} + +.recipe-item h3 { + font-size: 1.1rem; + padding: 10px; + text-align: center; + color: #333; +} + +.recipe-item a { + display: block; + text-align: center; + padding: 8px; + background-color: var(--secondary-color); + color: white; + text-decoration: none; + border-top: 1px solid var(--border-color); + transition: background-color 0.3s ease-in-out; +} + +.recipe-item a:hover { + background-color: #b5971f; +} + +/* RECIPE DETAILS MODAL */ +#recipe-details { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.6); + display: none; + align-items: center; + justify-content: center; + z-index: 1000; +} + +#recipe-content { + background: #fff; + padding: 20px; + width: 90%; + max-width: 700px; + border-radius: 10px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); + overflow-y: auto; + max-height: 80vh; +} + +#recipe-content img { + width: 100%; + border-radius: 8px; + margin-bottom: 10px; +} + +#statusbar { + display: flex; + justify-content: flex-end; + padding: 10px; +} + +#close-button { + font-size: 1.5rem; + color: var(--secondary-color); + cursor: pointer; + transition: color 0.3s ease-in-out; +} + +#close-button:hover { + color: #b5971f; +} + +/* RESPONSIVE */ +@media (max-width: 768px) { + #ingredient-input { + width: 70%; + } + + .recipe-item { + width: 90%; + } + + #recipe-content { + width: 95%; + } +} + + +/* Close button inside recipe card */ +#close-card-btn { + display: inline-block; + background-color: var(--secondary-color); + color: #fff; + border: none; + border-radius: 4px; + padding: 8px 14px; + font-size: 1rem; + cursor: pointer; + margin-bottom: 10px; + transition: background-color 0.3s ease; +} + +#close-card-btn:hover { + background-color: #b5971f; +} + + +/* Dropdown styling */ +#meal-type, +#country-select { + padding: 8px 10px; + font-size: 1rem; + border: 1px solid var(--border-color); + border-radius: 4px; + outline: none; + cursor: pointer; + background-color: #fff; + transition: border-color 0.3s ease-in-out; +} + +#meal-type:hover, +#country-select:hover { + border-color: var(--secondary-color); +} From ede0b164c19a96cf46b8d79579d202af261e7b6c Mon Sep 17 00:00:00 2001 From: "Eugene \"Pebbles\" Akiwumi" <62018288+akiwumi@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:53:34 +0200 Subject: [PATCH 2/5] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e69de29bb..f7319bc9c 100644 --- a/README.md +++ b/README.md @@ -0,0 +1 @@ +https://pebblesrecipefinder.netlify.app From 92ef8ae1e2a1f9c7b55b70c35a5eb87c3ede28f7 Mon Sep 17 00:00:00 2001 From: "Eugene \"Pebbles\" Akiwumi" <62018288+akiwumi@users.noreply.github.com> Date: Fri, 10 Oct 2025 12:21:45 +0200 Subject: [PATCH 3/5] Add files via upload --- README.md | 2 +- index.html | 21 ++++++-- script.js | 153 +++++++++++++++++++++++++++++++++++++++-------------- styles.css | 37 ++++++++++++- 4 files changed, 167 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index f7319bc9c..0b8c8b5b7 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -https://pebblesrecipefinder.netlify.app +https://pebblesrecipefinder.netlify.app \ No newline at end of file diff --git a/index.html b/index.html index af1eecc3e..cb441485b 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,7 @@

Recipe Finder

- + - + + + + + + + +
- diff --git a/script.js b/script.js index 87b9c5fa5..bd00c0d75 100644 --- a/script.js +++ b/script.js @@ -1,18 +1,60 @@ -const apiKey = "a229b49155dd40a782013d0a5e906f70"; +const apiKey = "9aa66e4ff117445592b772f3a4b89e80"; -// Cached DOM references +// DOM refs const inputEl = document.getElementById("ingredient-input"); const mealTypeEl = document.getElementById("meal-type"); const countryEl = document.getElementById("country-select"); +const dietEl = document.getElementById("diet-select"); +const sortEl = document.getElementById("sort-select"); const resultsEl = document.getElementById("results"); const detailsEl = document.getElementById("recipe-details"); const detailsContentEl = document.getElementById("recipe-content"); -// Search function with filters +// Map diet selection to Spoonacular params +function buildDietParams(selected) { + // vegetarian, vegan, gluten free → diet= + // dairy free → intolerances=dairy + const params = new URLSearchParams(); + if (!selected) return params; + + if (selected === "dairy free") { + params.set("intolerances", "dairy"); + } else { + // Spoonacular accepts "gluten free" as a diet too + params.set("diet", selected); + } + return params; +} + +function card(recipe) { + const div = document.createElement("div"); + div.className = "recipe-item"; + + const img = document.createElement("img"); + img.src = recipe.image; + img.alt = recipe.title; + + const title = document.createElement("h3"); + title.textContent = recipe.title; + + const link = document.createElement("a"); + link.href = "#"; + link.textContent = "View Recipe"; + link.addEventListener("click", async (e) => { + e.preventDefault(); + await showRecipeDetails(recipe.id); + }); + + div.append(img, title, link); + return div; +} + async function searchRecipes() { const ingredient = (inputEl?.value || "").trim(); - const mealType = mealTypeEl.value; + const type = mealTypeEl.value; const cuisine = countryEl.value; + const diet = dietEl.value; + const sort = sortEl.value; // "popularity" or "" if (!ingredient) { resultsEl.innerHTML = "

Please enter an ingredient.

"; @@ -22,54 +64,81 @@ async function searchRecipes() { try { resultsEl.innerHTML = "

Searching…

"; - // Use complexSearch for cuisine & type filters - const url = `https://api.spoonacular.com/recipes/complexSearch?query=${encodeURIComponent( - ingredient - )}&type=${encodeURIComponent(mealType)}&cuisine=${encodeURIComponent( - cuisine - )}&number=6&addRecipeInformation=true&apiKey=${apiKey}`; + const qs = new URLSearchParams({ + query: ingredient, + number: "9", + addRecipeInformation: "true", + apiKey + }); + + if (type) qs.set("type", type); + if (cuisine) qs.set("cuisine", cuisine); + if (sort) qs.set("sort", sort); + // diet / intolerances + const dietParams = buildDietParams(diet); + dietParams.forEach((v, k) => qs.set(k, v)); + + const url = `https://api.spoonacular.com/recipes/complexSearch?${qs.toString()}`; const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); + const items = data.results || []; - if (!data.results || data.results.length === 0) { + if (items.length === 0) { resultsEl.innerHTML = "

No recipes found.

"; return; } - // Render recipe cards resultsEl.innerHTML = ""; - data.results.forEach((recipe) => { - const card = document.createElement("div"); - card.className = "recipe-item"; - - const img = document.createElement("img"); - img.src = recipe.image; - img.alt = recipe.title; - - const title = document.createElement("h3"); - title.textContent = recipe.title; - - const link = document.createElement("a"); - link.href = "#"; - link.textContent = "View Recipe"; - link.addEventListener("click", async (e) => { - e.preventDefault(); - await showRecipeDetails(recipe.id); - }); - - card.append(img, title, link); - resultsEl.appendChild(card); - }); + items.forEach((r) => resultsEl.appendChild(card(r))); } catch (err) { console.error(err); resultsEl.innerHTML = "

Something went wrong. Please try again.

"; } } -// Fetch and display detailed recipe info +async function randomRecipe() { + const type = mealTypeEl.value; + const cuisine = countryEl.value; + const diet = dietEl.value; + + try { + resultsEl.innerHTML = "

Picking a random recipe…

"; + + // Build tags for /random + const tags = []; + if (type) tags.push(type); + if (cuisine) tags.push(cuisine); + if (diet) tags.push(diet); // Spoonacular accepts "dairy free" as a tag too + + const qs = new URLSearchParams({ + number: "1", + apiKey + }); + if (tags.length) qs.set("tags", tags.join(",")); + + const url = `https://api.spoonacular.com/recipes/random?${qs.toString()}`; + const response = await fetch(url); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + + const data = await response.json(); + const recipes = data.recipes || []; + if (recipes.length === 0) { + resultsEl.innerHTML = "

No random recipe found. Try different filters.

"; + return; + } + + // Render single card + resultsEl.innerHTML = ""; + resultsEl.appendChild(card(recipes[0])); + } catch (err) { + console.error(err); + resultsEl.innerHTML = "

Couldn’t fetch a random recipe.

"; + } +} + async function showRecipeDetails(recipeId) { try { detailsContentEl.innerHTML = "

Loading…

"; @@ -86,17 +155,14 @@ async function showRecipeDetails(recipeId) { ? recipe.extendedIngredients.map((i) => i.original).join(", ") : "Not available"; - // Add Close button directly in the card detailsContentEl.innerHTML = `

${recipe.title}

${recipe.title} -

Cuisine: ${recipe.cuisines.join(", ") || "N/A"}

-

Meal Type: ${recipe.dishTypes.join(", ") || "N/A"}

+

Cuisine: ${recipe.cuisines?.join(", ") || "N/A"}

+

Meal Type: ${recipe.dishTypes?.join(", ") || "N/A"}

Ingredients: ${ingredients}

-

Instructions: ${ - recipe.instructions || "No instructions provided." - }

+

Instructions: ${recipe.instructions || "No instructions provided."}

`; } catch (err) { console.error(err); @@ -107,3 +173,8 @@ async function showRecipeDetails(recipeId) { function closeDetails() { detailsEl.style.display = "none"; } + +// Optional: Enter key to search +inputEl?.addEventListener("keyup", (e) => { + if (e.key === "Enter") searchRecipes(); +}); diff --git a/styles.css b/styles.css index 43afaefa2..cb747d8c0 100644 --- a/styles.css +++ b/styles.css @@ -61,7 +61,7 @@ h1 { font-size: 1rem; border-radius: 4px; background-color: var(--secondary-color); - color: #fff; + color: #d0e1ed; border: none; cursor: pointer; transition: background 0.3s ease-in-out; @@ -227,3 +227,38 @@ h1 { #country-select:hover { border-color: var(--secondary-color); } + + +/* Reuse styling for new dropdowns & random button */ +#diet-select, +#sort-select { + padding: 8px 10px; + font-size: 1rem; + border: 1px solid var(--border-color); + border-radius: 4px; + outline: none; + cursor: pointer; + background-color: #fff; + transition: border-color 0.3s ease-in-out; +} + +#diet-select:hover, +#sort-select:hover { + border-color: var(--secondary-color); +} + +#random-button { + padding: 8px 16px; + font-size: 1rem; + border-radius: 4px; + background-color: transparent; + border: 1px solid var(--secondary-color); + color: #333; + cursor: pointer; + transition: background 0.3s ease-in-out, color 0.3s ease-in-out; +} + +#random-button:hover { + background-color: var(--secondary-color); + color: #fff; +} From 55f72b6293a4c37a2b44dbf1501ee4dcd2472ff9 Mon Sep 17 00:00:00 2001 From: "Eugene \"Pebbles\" Akiwumi" <62018288+akiwumi@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:10:23 +0200 Subject: [PATCH 4/5] Changed the layout of the mobile search --- index.html | 5 +- script.js | 118 +++++++++++++++++--------- styles.css | 238 ++++++++++++++++++++++++++++------------------------- 3 files changed, 209 insertions(+), 152 deletions(-) diff --git a/index.html b/index.html index cb441485b..0255250ba 100644 --- a/index.html +++ b/index.html @@ -59,12 +59,13 @@

Recipe Finder

- - + +
+ diff --git a/script.js b/script.js index bd00c0d75..ff45c9e1d 100644 --- a/script.js +++ b/script.js @@ -1,6 +1,8 @@ -const apiKey = "9aa66e4ff117445592b772f3a4b89e80"; +// Replace this with your valid Spoonacular API key +const apiKey = "edf4c3d520ec43ff9f8def394e4820e9"; // DOM refs +const formEl = document.getElementById("search-form"); const inputEl = document.getElementById("ingredient-input"); const mealTypeEl = document.getElementById("meal-type"); const countryEl = document.getElementById("country-select"); @@ -9,19 +11,18 @@ const sortEl = document.getElementById("sort-select"); const resultsEl = document.getElementById("results"); const detailsEl = document.getElementById("recipe-details"); const detailsContentEl = document.getElementById("recipe-content"); +const searchBtn = document.getElementById("search-button"); +const randomBtn = document.getElementById("random-button"); // Map diet selection to Spoonacular params function buildDietParams(selected) { - // vegetarian, vegan, gluten free → diet= - // dairy free → intolerances=dairy const params = new URLSearchParams(); if (!selected) return params; if (selected === "dairy free") { params.set("intolerances", "dairy"); } else { - // Spoonacular accepts "gluten free" as a diet too - params.set("diet", selected); + params.set("diet", selected); // Spoonacular accepts "gluten free" here too } return params; } @@ -49,20 +50,35 @@ function card(recipe) { return div; } +function setLoading(loading, message = "Searching…") { + if (loading) { + resultsEl.innerHTML = `

${message}

`; + searchBtn.disabled = true; + randomBtn.disabled = true; + searchBtn.style.opacity = 0.7; + randomBtn.style.opacity = 0.7; + } else { + searchBtn.disabled = false; + randomBtn.disabled = false; + searchBtn.style.opacity = ""; + randomBtn.style.opacity = ""; + } +} + async function searchRecipes() { const ingredient = (inputEl?.value || "").trim(); - const type = mealTypeEl.value; - const cuisine = countryEl.value; - const diet = dietEl.value; - const sort = sortEl.value; // "popularity" or "" - if (!ingredient) { resultsEl.innerHTML = "

Please enter an ingredient.

"; return; } + const type = mealTypeEl.value; + const cuisine = countryEl.value; + const diet = dietEl.value; + const sort = sortEl.value; + try { - resultsEl.innerHTML = "

Searching…

"; + setLoading(true, "Searching…"); const qs = new URLSearchParams({ query: ingredient, @@ -75,27 +91,31 @@ async function searchRecipes() { if (cuisine) qs.set("cuisine", cuisine); if (sort) qs.set("sort", sort); - // diet / intolerances const dietParams = buildDietParams(diet); dietParams.forEach((v, k) => qs.set(k, v)); const url = `https://api.spoonacular.com/recipes/complexSearch?${qs.toString()}`; const response = await fetch(url); - if (!response.ok) throw new Error(`HTTP ${response.status}`); + if (!response.ok) { + const msg = response.status === 402 || response.status === 401 + ? "API key error or quota reached. Please check your Spoonacular API key." + : `Request failed (HTTP ${response.status}).`; + throw new Error(msg); + } const data = await response.json(); const items = data.results || []; - if (items.length === 0) { - resultsEl.innerHTML = "

No recipes found.

"; - return; - } + resultsEl.innerHTML = items.length + ? "" + : "

No recipes found. Try another ingredient or adjust filters.

"; - resultsEl.innerHTML = ""; items.forEach((r) => resultsEl.appendChild(card(r))); } catch (err) { console.error(err); - resultsEl.innerHTML = "

Something went wrong. Please try again.

"; + resultsEl.innerHTML = `

${err.message || "Something went wrong. Please try again."}

`; + } finally { + setLoading(false); } } @@ -105,37 +125,38 @@ async function randomRecipe() { const diet = dietEl.value; try { - resultsEl.innerHTML = "

Picking a random recipe…

"; + setLoading(true, "Picking a random recipe…"); - // Build tags for /random const tags = []; if (type) tags.push(type); if (cuisine) tags.push(cuisine); - if (diet) tags.push(diet); // Spoonacular accepts "dairy free" as a tag too + if (diet) tags.push(diet); // Spoonacular accepts "dairy free" tag too - const qs = new URLSearchParams({ - number: "1", - apiKey - }); + const qs = new URLSearchParams({ number: "1", apiKey }); if (tags.length) qs.set("tags", tags.join(",")); const url = `https://api.spoonacular.com/recipes/random?${qs.toString()}`; const response = await fetch(url); - if (!response.ok) throw new Error(`HTTP ${response.status}`); + if (!response.ok) { + const msg = response.status === 402 || response.status === 401 + ? "API key error or quota reached. Please check your Spoonacular API key." + : `Request failed (HTTP ${response.status}).`; + throw new Error(msg); + } const data = await response.json(); const recipes = data.recipes || []; - if (recipes.length === 0) { - resultsEl.innerHTML = "

No random recipe found. Try different filters.

"; - return; - } - // Render single card - resultsEl.innerHTML = ""; - resultsEl.appendChild(card(recipes[0])); + resultsEl.innerHTML = recipes.length + ? "" + : "

No random recipe found. Try different filters.

"; + + if (recipes.length) resultsEl.appendChild(card(recipes[0])); } catch (err) { console.error(err); - resultsEl.innerHTML = "

Couldn’t fetch a random recipe.

"; + resultsEl.innerHTML = `

${err.message || "Couldn’t fetch a random recipe."}

`; + } finally { + setLoading(false); } } @@ -143,6 +164,7 @@ async function showRecipeDetails(recipeId) { try { detailsContentEl.innerHTML = "

Loading…

"; detailsEl.style.display = "flex"; + detailsEl.setAttribute("aria-hidden", "false"); const response = await fetch( `https://api.spoonacular.com/recipes/${recipeId}/information?apiKey=${apiKey}` @@ -156,7 +178,7 @@ async function showRecipeDetails(recipeId) { : "Not available"; detailsContentEl.innerHTML = ` - +

${recipe.title}

${recipe.title}

Cuisine: ${recipe.cuisines?.join(", ") || "N/A"}

@@ -172,9 +194,27 @@ async function showRecipeDetails(recipeId) { function closeDetails() { detailsEl.style.display = "none"; + detailsEl.setAttribute("aria-hidden", "true"); } -// Optional: Enter key to search -inputEl?.addEventListener("keyup", (e) => { - if (e.key === "Enter") searchRecipes(); +// Close modal when clicking outside content (mobile-friendly) +detailsEl.addEventListener("click", (e) => { + if (e.target === detailsEl) closeDetails(); +}); + +// Close with Escape key +document.addEventListener("keydown", (e) => { + if (e.key === "Escape") closeDetails(); }); + +/* --- Event bindings --- */ + +// Submit form triggers search (so Enter works) +formEl.addEventListener("submit", (e) => { + e.preventDefault(); + searchRecipes(); +}); + +// Buttons +searchBtn.addEventListener("click", searchRecipes); +randomBtn.addEventListener("click", randomRecipe); diff --git a/styles.css b/styles.css index cb747d8c0..c964d2dec 100644 --- a/styles.css +++ b/styles.css @@ -7,21 +7,26 @@ } /* RESET */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; +* { margin: 0; padding: 0; box-sizing: border-box; } + +/* Utility: visually hidden but screen-reader readable */ +.sr-only { + position: absolute !important; + height: 1px; width: 1px; + overflow: hidden; clip: rect(1px, 1px, 1px, 1px); + white-space: nowrap; border: 0; padding: 0; margin: -1px; } /* BODY */ body { - height: 100vh; - width: 100vw; + min-height: 100vh; + width: 100%; font-family: Arial, sans-serif; background-color: var(--primary-color); display: flex; flex-direction: column; align-items: center; + overflow-x: hidden; } /* TITLE */ @@ -29,48 +34,79 @@ h1 { margin-top: 20px; color: #333; font-size: 2rem; + text-align: center; } -/* SEARCH FORM */ +/* SEARCH FORM (desktop/tablet defaults) */ #search-form { display: flex; justify-content: center; align-items: center; gap: 10px; - height: var(--search-height); + + /* Let it grow so dropdowns can open and layout can adapt */ + height: auto; /* was fixed before */ + min-height: var(--search-height); /* preserves desktop feel */ margin: 20px 0; + flex-wrap: wrap; + position: relative; + z-index: 5; /* keep dropdowns above results */ + padding: 0 10px; } -#ingredient-input { +#ingredient-input, +#meal-type, +#country-select, +#diet-select, +#sort-select { padding: 8px 10px; font-size: 1rem; border: 1px solid var(--border-color); border-radius: 4px; outline: none; + background-color: #fff; + transition: border-color 0.3s ease-in-out; +} + +#ingredient-input { width: 60%; - transition: all 0.3s ease-in-out; + transition: border-color 0.3s ease-in-out, width 0.3s ease-in-out; } -#ingredient-input:focus { +#ingredient-input:focus, +#meal-type:focus, +#country-select:focus, +#diet-select:focus, +#sort-select:focus { border-color: var(--secondary-color); - width: 62%; } -#search-button { +/* BUTTONS (original palette) */ +#search-button, +#random-button { padding: 8px 16px; font-size: 1rem; border-radius: 4px; + cursor: pointer; + transition: background 0.3s ease-in-out, color 0.3s ease-in-out, opacity 0.15s ease-in-out; +} + +#search-button { background-color: var(--secondary-color); - color: #d0e1ed; + color: #fafdff; border: none; - cursor: pointer; - transition: background 0.3s ease-in-out; } -#search-button:hover { - background-color: #b5971f; +#search-button:hover { background-color: #b5971f; } + +#random-button { + background-color: transparent; + border: 1px solid var(--secondary-color); + color: #333; } +#random-button:hover { background-color: var(--secondary-color); color: #fff; } + /* RESULTS AREA */ #results { display: flex; @@ -81,9 +117,11 @@ h1 { width: 100%; max-width: 1200px; padding: 20px; + position: relative; /* stays below the form */ + z-index: 1; } -/* INDIVIDUAL RECIPE CARD */ +/* RECIPE CARD */ .recipe-item { width: 280px; border: 1px solid var(--border-color); @@ -91,12 +129,12 @@ h1 { box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); overflow: hidden; background: #fff; - transition: all 0.2s ease-in-out; + transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; } .recipe-item:hover { transform: translateY(-5px); - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); + box-shadow: 0 4px 10px rgba(0,0,0,0.2); } .recipe-item img { @@ -123,22 +161,19 @@ h1 { transition: background-color 0.3s ease-in-out; } -.recipe-item a:hover { - background-color: #b5971f; -} +.recipe-item a:hover { background-color: #b5971f; } /* RECIPE DETAILS MODAL */ #recipe-details { position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; + inset: 0; + width: 100%; height: 100%; background: rgba(0, 0, 0, 0.6); - display: none; + display: none; /* toggled to flex in JS */ align-items: center; justify-content: center; z-index: 1000; + padding: 10px; } #recipe-content { @@ -147,7 +182,7 @@ h1 { width: 90%; max-width: 700px; border-radius: 10px; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); + box-shadow: 0 4px 10px rgba(0,0,0,0.3); overflow-y: auto; max-height: 80vh; } @@ -158,40 +193,7 @@ h1 { margin-bottom: 10px; } -#statusbar { - display: flex; - justify-content: flex-end; - padding: 10px; -} - -#close-button { - font-size: 1.5rem; - color: var(--secondary-color); - cursor: pointer; - transition: color 0.3s ease-in-out; -} - -#close-button:hover { - color: #b5971f; -} - -/* RESPONSIVE */ -@media (max-width: 768px) { - #ingredient-input { - width: 70%; - } - - .recipe-item { - width: 90%; - } - - #recipe-content { - width: 95%; - } -} - - -/* Close button inside recipe card */ +/* Close button inside details */ #close-card-btn { display: inline-block; background-color: var(--secondary-color); @@ -205,60 +207,74 @@ h1 { transition: background-color 0.3s ease; } -#close-card-btn:hover { - background-color: #b5971f; -} +#close-card-btn:hover { background-color: #b5971f; } +/* ------------------------- + RESPONSIVE BREAKPOINTS + ------------------------- */ -/* Dropdown styling */ -#meal-type, -#country-select { - padding: 8px 10px; - font-size: 1rem; - border: 1px solid var(--border-color); - border-radius: 4px; - outline: none; - cursor: pointer; - background-color: #fff; - transition: border-color 0.3s ease-in-out; +/* Tablets / medium screens */ +@media (max-width: 900px) { + #ingredient-input { width: 70%; } + .recipe-item { width: 45%; } + #recipe-content { width: 95%; } } -#meal-type:hover, -#country-select:hover { - border-color: var(--secondary-color); -} +/* Phones — unified two-row block for input + dropdowns; buttons below */ +@media (max-width: 600px) { + h1 { + font-size: 1.5rem; + margin-top: 15px; + } + /* Unified grid block: input (full), two rows of dropdowns, then buttons */ + #search-form { + display: grid !important; + grid-template-columns: repeat(2, 1fr); + grid-template-rows: auto auto auto; + gap: 8px; + width: 95%; + max-width: 600px; + padding: 10px; -/* Reuse styling for new dropdowns & random button */ -#diet-select, -#sort-select { - padding: 8px 10px; - font-size: 1rem; - border: 1px solid var(--border-color); - border-radius: 4px; - outline: none; - cursor: pointer; - background-color: #fff; - transition: border-color 0.3s ease-in-out; -} + background: var(--primary-color); + border: 1px solid var(--border-color); + border-radius: 8px; -#diet-select:hover, -#sort-select:hover { - border-color: var(--secondary-color); -} + height: auto !important; /* crucial so dropdown lists can open */ + } -#random-button { - padding: 8px 16px; - font-size: 1rem; - border-radius: 4px; - background-color: transparent; - border: 1px solid var(--secondary-color); - color: #333; - cursor: pointer; - transition: background 0.3s ease-in-out, color 0.3s ease-in-out; -} + #ingredient-input, + #meal-type, + #country-select, + #diet-select, + #sort-select { + width: 100%; + font-size: 0.9rem; + padding: 8px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: #fff; + } -#random-button:hover { - background-color: var(--secondary-color); - color: #fff; + /* Layout rows */ + #ingredient-input { grid-column: 1 / 3; } /* full width row 1 */ + #meal-type { grid-column: 1; } /* row 2 left */ + #country-select { grid-column: 2; } /* row 2 right */ + #diet-select { grid-column: 1; } /* row 3 left */ + #sort-select { grid-column: 2; } /* row 3 right */ + + /* Buttons in their own rows (clear & thumb-friendly) */ + #search-button, + #random-button { + grid-column: 1 / 3; + font-size: 0.95rem; + padding: 10px; + height: 42px; + border-radius: 6px; + } + + #results { padding: 10px; } + .recipe-item { width: 100%; } + #recipe-content { width: 100%; max-height: 85vh; } } From 2885f38c4765a12dc6622f3138160d93844ccb61 Mon Sep 17 00:00:00 2001 From: "Eugene \"Pebbles\" Akiwumi" <62018288+akiwumi@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:15:25 +0200 Subject: [PATCH 5/5] Update script.js --- script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script.js b/script.js index ff45c9e1d..2568917b3 100644 --- a/script.js +++ b/script.js @@ -1,5 +1,5 @@ // Replace this with your valid Spoonacular API key -const apiKey = "edf4c3d520ec43ff9f8def394e4820e9"; +const apiKey = "cfc30adf3b0e422b9a0d562cf3181fa1"; // DOM refs const formEl = document.getElementById("search-form");