From f7ad5ef9e2bf979771de2bfb2c835d0d5f5dbca4 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 24 Sep 2025 18:38:32 +0200 Subject: [PATCH 01/25] first commit --- index.html | 51 ++++++++++++++++++++++ script.js | 65 ++++++++++++++++++++++++++++ style.css | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 index.html create mode 100644 script.js create mode 100644 style.css diff --git a/index.html b/index.html new file mode 100644 index 000000000..0259b13cb --- /dev/null +++ b/index.html @@ -0,0 +1,51 @@ + + + + + + + Wok This Way + + + +
+ +
+

Recipe Library

+
+ +
+
+

Filter on kitchen

+ + + + +
+
+

Sort on time

+ + +
+
+ + + +
+ Image of the recipe + +

+ +
+

Cuisine:

+

Time:

+
+ +

Ingredients

+
    + +
+
+ + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 000000000..0b2e9b7ae --- /dev/null +++ b/script.js @@ -0,0 +1,65 @@ + +//Inställningar +//Göra en GET-response för att få recepten +//Sätta våra headers och parameters likt en adress lapp +const myHeaders = new Headers(); //Skapar ett headers objekt +myHeaders.append('x-api-key','8c1eb74ea4924057b8ad6bf0c4c9219c'); +myHeaders.append('Content-Type', 'application/json') + +//Vad vi ska hämta med våran adress +const requestOptions = { + method: 'GET', + redirect: 'follow', + headers: myHeaders +}; + +const URL = 'https://api.spoonacular.com/recipes/random?number=1' + +// En funktion för att hantera och logga receptdata. +// Denna funktion saknades i din kod, vilket orsakade "processRecipeData is not defined". +const processRecipeData = (result) => { + // Säkerställ att vi har ett giltigt resultat med recept + if (result && result.recipes && result.recipes.length > 0) { + const recipe = result.recipes[0]; //Hämtar ut hela första receptobjektet + + //Sparar ner alla egenskaper som jag skall använda + const title = recipe.title; + const cuisines = recipe.cuisines; //array + const time = recipe.readyInMinutes; //tid i minuter + const ingredients = recipe.extendedIngredients; // array + + console.log("Title:", title); + console.log("Cuisines:", cuisines); + console.log("Time:", time); + console.log("Ingredients:", ingredients); + + console.log("Complete object:", result); // Loggar hela resultatet för överblick + } else { + console.log("Could not process prescription data, result was empty or invalid"); + } +}; + + const savedRecipeJSON = localStorage.getItem("savedRecipe"); + +if (savedRecipeJSON) { + console.log("Fetching a recipe from localStorage..."); + const savedRecipe = JSON.parse(savedRecipeJSON); // Konvertera tillbaka från text till objekt + processRecipeData(savedRecipe); +} else { + console.log("Fetching another recipe from the API..."); + fetch(URL, requestOptions) + .then(response => response.json()) + .then(result => { + // Spara det nya receptet i localStorage för framtida användning + // konvertera objektet till en textsträng med JSON.stringify + localStorage.setItem('savedRecipe', JSON.stringify(result)); + + // Använder datan + processRecipeData(result); + }) + .catch(error => console.log('error', error)); +} + + + + diff --git a/style.css b/style.css new file mode 100644 index 000000000..6ca5fd91e --- /dev/null +++ b/style.css @@ -0,0 +1,122 @@ +:root{ + /* Colors */ + --clr-primary: rgba(0, 24, 164, 1); + --clr-primary-1: rgba(204, 255, 226, 1); + --clr-secondary: rgba(255, 101, 137, 1); + --clr-secondary-1: rgba(255, 236, 234, 1); + + --ff-: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; font-weight: 700; +} + +*{ + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body{ + min-height: 100vh; + font-family: var(--ff-); + display: flex; + justify-content: center; + flex-wrap: wrap; + background-color:orangered; +} + +.container{ + max-width: 1200px; + margin-left: 0.5rem; + width: 100%; + margin: 0 0.5rem; +} + +h1{ + font-style: bold; + color: var(--clr-primary); + margin-top: 1rem; +} + +.select-section{ + margin-top: 1rem; +} + +.select-section h2{ + margin-top:0.5rem; + margin-bottom: 0.5rem; + font-size: 1.1rem; +} + +.select-section button{ + font-weight: 500; + font-style: medium; + padding: 0.398rem 0.7rem 0.398rem 0.7rem; + border-radius: 19px; + border: none; + font-family: var(--ff-); +} + +.filter button{ + background-color: var(--clr-primary-1); + color: var(--clr-primary); +} + +.sort button{ + background-color: var(--clr-secondary-1); + color: var(--clr-primary); +} + +button:hover{ + border: 2px solid var(--clr-primary); +} + + +.recipe-card{ + margin-top: 0.5rem; + width: 300px; + height: 621px; + border-radius: 16px; + padding: 1rem; + display: flex; + flex-direction: column; + justify-content: space-between; + border: 1px solid rgba(233, 233, 233, 1); + background-color: peachpuff; +} + +.recipe-card:hover{ + transform: translateY(-4px); + box-shadow: 0px 0px 30px 0px rgba(0, 24, 164, 0.2); + border: 2px solid var(--clr-primary); +} + +.recipe-information{ + border-top: 1px solid rgba(233, 233, 233, 1); + border-bottom: 1px solid rgba(233, 233, 233, 1); + padding: 1rem 0 1rem 0; +} +.recipe-information p{ + display: flex; + flex-direction: column; + gap: 8px; +} +/* +@media (min-width: 600px) { + + .container{ + display: flex; + } + + h1{ + font-size: 54px; + padding: 16px; + } + + .recipe-card{ + margin-left: auto; + } + + .container{ + display: flex; + + } +} */ \ No newline at end of file From 5e2fb8f349fa492f0b8a79cc66133621bc6d39e2 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 24 Sep 2025 19:38:16 +0200 Subject: [PATCH 02/25] javascipt changes --- .vscode/settings.json | 3 +++ index.html | 22 ++++++++++++++++++---- script.js | 26 ++++++++++++++++++++++++-- 3 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..6f3a2913e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/index.html b/index.html index 0259b13cb..b2fa480c3 100644 --- a/index.html +++ b/index.html @@ -31,18 +31,32 @@

Sort on time

+
+ Image of the recipe + +

+
+

Cuisine:

+

Time: minutes

+
+ +

Ingredients

+
    + +
+
Image of the recipe -

+

-

Cuisine:

+

Cuisine:

Time:

-

Ingredients

-
    +

    Ingredients

    +
diff --git a/script.js b/script.js index 0b2e9b7ae..564d99fe1 100644 --- a/script.js +++ b/script.js @@ -23,20 +23,39 @@ const processRecipeData = (result) => { const recipe = result.recipes[0]; //Hämtar ut hela första receptobjektet //Sparar ner alla egenskaper som jag skall använda + const imgUrl = recipe.image; const title = recipe.title; const cuisines = recipe.cuisines; //array const time = recipe.readyInMinutes; //tid i minuter const ingredients = recipe.extendedIngredients; // array - + + /* console.log("img", imgUrl); console.log("Title:", title); console.log("Cuisines:", cuisines); console.log("Time:", time); - console.log("Ingredients:", ingredients); + console.log("Ingredients:", ingredients); */ + + const imageElement = document.getElementById('recipe-img'); + imageElement.src = imgUrl; + + const recipeTitleElement = document.getElementById("recipeTitle"); + recipeTitleElement.innerText = title; + + const recipeCuisines = document.getElementById("recipe-cuisine"); + recipeCuisines.innerText = cuisines; + + const recipeTime = document.getElementById("recipe-time"); + recipeTime.innerHTML = time; +/* + const recipeIngredients = document.getElementById("recipe-ingredients") + recipeIngredients.innerText = extendedIngredients; */ console.log("Complete object:", result); // Loggar hela resultatet för överblick } else { console.log("Could not process prescription data, result was empty or invalid"); } + + }; const savedRecipeJSON = localStorage.getItem("savedRecipe"); @@ -59,6 +78,9 @@ if (savedRecipeJSON) { }) .catch(error => console.log('error', error)); } + + + From bfb9ef062d4b487f1c7d05529f901299cb16a8bc Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Fri, 26 Sep 2025 17:13:09 +0200 Subject: [PATCH 03/25] finish the random recipe --- index.html | 62 +++++++-------------- script.js | 158 +++++++++++++++++++++++++++++++++-------------------- style.css | 89 +++++++++++++++++++++--------- 3 files changed, 182 insertions(+), 127 deletions(-) diff --git a/index.html b/index.html index b2fa480c3..954f32699 100644 --- a/index.html +++ b/index.html @@ -15,51 +15,29 @@

Recipe Library

-
-

Filter on kitchen

- - - - -
-
-

Sort on time

- - -
+
+

Filter on kitchen

+ + + + +
+
+

Sort on time

+ + +
+

Random Recipe

+
- + +
-
- Image of the recipe - -

-
-

Cuisine:

-

Time: minutes

-
- -

Ingredients

-
    - -
-
-
- Image of the recipe - -

- -
-

Cuisine:

-

Time:

-
- -

Ingredients

-
    - -
-
+
+
+
+ \ No newline at end of file diff --git a/script.js b/script.js index 564d99fe1..185d60764 100644 --- a/script.js +++ b/script.js @@ -1,87 +1,125 @@ - -//Inställningar -//Göra en GET-response för att få recepten -//Sätta våra headers och parameters likt en adress lapp -const myHeaders = new Headers(); //Skapar ett headers objekt -myHeaders.append('x-api-key','8c1eb74ea4924057b8ad6bf0c4c9219c'); +// ================================================== +// Settings & Constants +// API keys, URLs, and initial setup variables +// ================================================== +const myHeaders = new Headers(); +myHeaders.append('x-api-key', '8c1eb74ea4924057b8ad6bf0c4c9219c'); myHeaders.append('Content-Type', 'application/json') -//Vad vi ska hämta med våran adress const requestOptions = { method: 'GET', redirect: 'follow', headers: myHeaders }; -const URL = 'https://api.spoonacular.com/recipes/random?number=1' +const URL = 'https://api.spoonacular.com/recipes/random?number=100' -// En funktion för att hantera och logga receptdata. -// Denna funktion saknades i din kod, vilket orsakade "processRecipeData is not defined". -const processRecipeData = (result) => { - // Säkerställ att vi har ett giltigt resultat med recept - if (result && result.recipes && result.recipes.length > 0) { - const recipe = result.recipes[0]; //Hämtar ut hela första receptobjektet +document.addEventListener("DOMContentLoaded", () => { - //Sparar ner alla egenskaper som jag skall använda - const imgUrl = recipe.image; - const title = recipe.title; - const cuisines = recipe.cuisines; //array - const time = recipe.readyInMinutes; //tid i minuter - const ingredients = recipe.extendedIngredients; // array +const randomBtn = document.getElementById("random-recipe"); +const cardOverlay = document.getElementById("card-overlay"); +const cardContent = document.getElementById("card-content"); +let allRecipes = []; - /* console.log("img", imgUrl); - console.log("Title:", title); - console.log("Cuisines:", cuisines); - console.log("Time:", time); - console.log("Ingredients:", ingredients); */ - const imageElement = document.getElementById('recipe-img'); - imageElement.src = imgUrl; +// ================================================== +// Functions +// ================================================== - const recipeTitleElement = document.getElementById("recipeTitle"); - recipeTitleElement.innerText = title; +const createCardHTML = (recipe) => { + const imgUrl = recipe.image; + const title = recipe.title; + // En tom array som standard om cuisines saknas, för att undvika fel med .join() + const cuisines = recipe.cuisines || []; + const time = recipe.readyInMinutes; + const ingredients = recipe.extendedIngredients || []; + + const ingredientsHTML = ingredients.map(ingredient => { + return `
  • ${ingredient.original}
  • `; + }).join(''); + + return ` +
    + Bild på ${title} +
    +

    ${title}

    +

    Cuisine: ${cuisines.join(', ')}

    +

    Time: ${time} minuter

    +

    Ingredients:

    +
      + ${ingredientsHTML} +
    +
    +
    + `; +}; - const recipeCuisines = document.getElementById("recipe-cuisine"); - recipeCuisines.innerText = cuisines; - const recipeTime = document.getElementById("recipe-time"); - recipeTime.innerHTML = time; -/* - const recipeIngredients = document.getElementById("recipe-ingredients") - recipeIngredients.innerText = extendedIngredients; */ +const processRecipeData = (result) => { + if (result && result.recipes && result.recipes.length > 0) { + allRecipes = result.recipes; - console.log("Complete object:", result); // Loggar hela resultatet för överblick - } else { - console.log("Could not process prescription data, result was empty or invalid"); - } + const recipeContainer = document.getElementById("recipe-container"); + recipeContainer.innerHTML = ''; // Clear previous results + allRecipes.forEach(recipe => { + const cardHTML = createCardHTML(recipe); + recipeContainer.innerHTML += cardHTML; + }); + console.log("Complete object:", result); + } else { + console.log("Could not process recipe data, result was empty or invalid"); + } }; - const savedRecipeJSON = localStorage.getItem("savedRecipe"); +// Lyssna efter klick på "slumpa"-knappen +randomBtn.addEventListener("click", () => { -if (savedRecipeJSON) { - console.log("Fetching a recipe from localStorage..."); - const savedRecipe = JSON.parse(savedRecipeJSON); // Konvertera tillbaka från text till objekt - processRecipeData(savedRecipe); -} else { - console.log("Fetching another recipe from the API..."); - fetch(URL, requestOptions) - .then(response => response.json()) - .then(result => { - // Spara det nya receptet i localStorage för framtida användning - // konvertera objektet till en textsträng med JSON.stringify - localStorage.setItem('savedRecipe', JSON.stringify(result)); - - // Använder datan - processRecipeData(result); - }) - .catch(error => console.log('error', error)); -} + // Gör inget om listan är tom + if (!allRecipes || allRecipes.length === 0) { + + console.log("Inga recept att slumpa fram än."); + return; + } + + const randomIndex = Math.floor(Math.random() * allRecipes.length); + const randomRecipe = allRecipes[randomIndex]; + + const cardHTML = createCardHTML(randomRecipe); + cardContent.innerHTML = cardHTML; + cardOverlay.classList.add("visible"); +}); +// Lyssna efter klick på den mörka bakgrunden, för att stänga +cardOverlay.addEventListener("click", (event) => { + // Att man klickade på själva overlayen, och inte på innehållet + if (event.target === cardOverlay) { + cardOverlay.classList.remove("visible"); + } +}); - +// ================================================== +// Initialization +// This is the code that actually runs when the page loads. +// ================================================== +const savedRecipeJSON = localStorage.getItem("savedRecipe"); +if (savedRecipeJSON) { + console.log("Fetching a recipe from localStorage..."); + const savedRecipe = JSON.parse(savedRecipeJSON); + processRecipeData(savedRecipe); // Calling the function +} else { + console.log("Fetching another recipe from the API..."); + fetch(URL, requestOptions) + .then(response => response.json()) + .then(result => { + localStorage.setItem("savedRecipe", JSON.stringify(result)); + processRecipeData(result); // Calling the function + }) + .catch(error => console.log("error", error)); +} +}); diff --git a/style.css b/style.css index 6ca5fd91e..b206b802b 100644 --- a/style.css +++ b/style.css @@ -20,7 +20,6 @@ body{ display: flex; justify-content: center; flex-wrap: wrap; - background-color:orangered; } .container{ @@ -49,7 +48,7 @@ h1{ .select-section button{ font-weight: 500; font-style: medium; - padding: 0.398rem 0.7rem 0.398rem 0.7rem; + padding: 0.65rem 1rem 0.65rem 1rem; border-radius: 19px; border: none; font-family: var(--ff-); @@ -69,18 +68,25 @@ button:hover{ border: 2px solid var(--clr-primary); } +.filter-btn:active{ + background-color: var(--clr-primary); + color: white; +} + +.sort-btn:active { + background-color: var(--clr-secondary); /* Or any other color variable you have */ + color: white; +} .recipe-card{ - margin-top: 0.5rem; + margin-top: 1rem; width: 300px; - height: 621px; + min-height: 621px; border-radius: 16px; padding: 1rem; display: flex; flex-direction: column; - justify-content: space-between; - border: 1px solid rgba(233, 233, 233, 1); - background-color: peachpuff; + border: 1px solid rgb(179, 179, 179); } .recipe-card:hover{ @@ -89,34 +95,67 @@ button:hover{ border: 2px solid var(--clr-primary); } -.recipe-information{ - border-top: 1px solid rgba(233, 233, 233, 1); - border-bottom: 1px solid rgba(233, 233, 233, 1); - padding: 1rem 0 1rem 0; +.recipe-card h2{ + padding-top: 1rem; +} + +.recipe-cuisine{ + padding-top: 1rem; } -.recipe-information p{ + +.recipe-time{ + padding-bottom: 1rem; +} + +.recipe-ingredients{ + margin-top: 0.5rem; + list-style-type: none; + +} + +.recipe-ingredients li{ + font-weight: 500; + font-style: medium; + font-size: 1rem; +} + +.card-overlay{ + display: none; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(5px); +} + +.card-overlay.visible{ display: flex; - flex-direction: column; - gap: 8px; } -/* + +.card-content { + background-color: white; + padding: 20px; + border-radius: 10px; + min-width: 80%; + height: 80%; + overflow-y: auto; +} + @media (min-width: 600px) { - .container{ + .recipe-container{ display: flex; + flex-wrap: wrap; + gap: 0.8rem; } h1{ font-size: 54px; - padding: 16px; } - .recipe-card{ - margin-left: auto; - } - .container{ - display: flex; - - } -} */ \ No newline at end of file +} \ No newline at end of file From 52ba170c1919bc27a72eb5c36c5b4b442f271650 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Thu, 2 Oct 2025 08:32:11 +0200 Subject: [PATCH 04/25] filter button active --- backup-script.js | 200 +++++++++++++++++++++++++++++++++++++++++++++++ index.html | 33 ++++---- script.js | 14 ++-- style.css | 43 +++++++++- 4 files changed, 266 insertions(+), 24 deletions(-) create mode 100644 backup-script.js diff --git a/backup-script.js b/backup-script.js new file mode 100644 index 000000000..6ee7da33b --- /dev/null +++ b/backup-script.js @@ -0,0 +1,200 @@ +// ================================================== +// Settings & Constants +// API keys, URLs, and initial setup variables +// ================================================== +document.addEventListener("DOMContentLoaded", () => { + +const myHeaders = new Headers(); +myHeaders.append('x-api-key', '8c1eb74ea4924057b8ad6bf0c4c9219c'); +myHeaders.append('Content-Type', 'application/json') + +const requestOptions = { + method: 'GET', + redirect: 'follow', + headers: myHeaders +}; + +const URL = 'https://api.spoonacular.com/recipes/random?number=100'; + +const randomBtn = document.getElementById("random-recipe"); +const cardOverlay = document.getElementById("card-overlay"); +const cardContent = document.getElementById("card-content"); +let allRecipes = []; +const glutenBtn = document.getElementById("gluten-btn"); +const dairyBtn = document.getElementById("dairy-btn"); +const vegBtn = document.getElementById("veg-btn"); +const veganBtn = document.getElementById("vegan-btn"); +const allBtn = document.getElementById("filter-btn"); + + +// ================================================== +// Functions +// ================================================== + +// NY FUNKTION: Denna funktion kan visa vilken lista av recept som helst. +const displayRecipes = (recipesToShow) => { + const recipeContainer = document.getElementById("recipe-container"); + recipeContainer.innerHTML = ''; // Rensa alltid skärmen först + + // Använd den optimerade metoden för att bygga och visa korten + const allCardsHTML = recipesToShow.map(recipe => createGridCardHTML(recipe)).join(''); + recipeContainer.innerHTML = allCardsHTML; + + console.log(`Visar nu ${recipesToShow.length} recept.`); +}; + +// Funktion för att bygga de små korten i rutnätet +const createGridCardHTML = (recipe) => { + const imgUrl = recipe.image; + const title = recipe.title; + const time = recipe.readyInMinutes; + const diets = recipe.diets; + const ingredients = recipe.extendedIngredients || []; + + const ingredientsHTML = ingredients.map(ingredient => { + return `
  • ${ingredient.original}
  • `; + }).join(''); + + return ` +
    + Bild på ${title} +
    +

    ${title}

    +

    Cooking Time: ${time} minuter

    +

    Diets: ${diets}

    +

    Ingredients:

    +
      + ${ingredientsHTML} +
    +
    +
    + `; +}; + +// Funktion för att bygga det stora, detaljerade kortet i overlayen +const createOverlayCardHTML = (recipe) => { + const imgUrl = recipe.image; + const title = recipe.title; +/* const cuisines = recipe.cuisines || []; + */ const time = recipe.readyInMinutes; + const diets = recipe.diets; + const ingredients = recipe.extendedIngredients || []; + const instructions = recipe.instructions; + + const ingredientsHTML = ingredients.map(ingredient => { + return `
  • ${ingredient.original}
  • `; + }).join(''); + + return ` +
    + Bild på ${title} +

    ${title}

    +

    Time to cook: ${time} minuter

    +

    Diets: ${diets}

    +

    Ingredients:

    +
      + ${ingredientsHTML} +
    +

    ${instructions}

    +
    + `; +}; + +// Funktion som bearbetar API-svaret och ritar ut alla grid-kort +const processRecipeData = (result) => { + // Återställt för att hantera 'recipes'-arrayen från /random-slutpunkten + if (result && result.recipes && result.recipes.length > 0) { + allRecipes = result.recipes; + console.log("Alla recept:", allRecipes); + + // Använd vår nya, generella visningsfunktion för att visa ALLA recept från start + displayRecipes(allRecipes); + + } else { + console.log("Could not process recipe data, result was empty or invalid"); + } +}; + +// Lyssna efter klick på "slumpa"-knappen +randomBtn.addEventListener("click", () => { + if (!allRecipes || allRecipes.length === 0) { + console.log("No recipes available to choose from."); + return; + } + + // Enkel logik: välj ett slumpmässigt recept från den befintliga listan + const randomIndex = Math.floor(Math.random() * allRecipes.length); + const randomRecipe = allRecipes[randomIndex]; + + // Använd det valda receptet för att bygga och visa overlayen + const cardHTML = createOverlayCardHTML(randomRecipe); + cardContent.innerHTML = cardHTML; + cardOverlay.classList.add("visible"); +}); + +// Lyssna efter klick på den mörka bakgrunden, för att stänga +cardOverlay.addEventListener("click", (event) => { + if (event.target === cardOverlay) { + cardOverlay.classList.remove("visible"); + } +}); + +// ================================================== +// Event Listeners for Filtering +// ================================================== + +// Lyssna på "Gluten free"-knappen +glutenBtn.addEventListener("click", () => { + console.log("Filtrerar på 'gluten free'"); + // 1. Skapa en ny, filtrerad lista från vår masterlista + const filtered = allRecipes.filter(recipe => recipe.diets.includes('gluten free')); + + // 2. Använd vår visningsfunktion för att visa den nya listan + displayRecipes(filtered); +}); + +dairyBtn.addEventListener("click", () => { + console.log("Filtrerar på 'dairy free'"); + const filtered = allRecipes.filter(recipe => recipe.diets.includes('dairy free')); + displayRecipes(filtered); +}); + +veganBtn.addEventListener("click", () => { + const filtered = allRecipes.filter(recipe => recipe.diets.includes("vegan")) + displayRecipes(filtered); +}); + +vegBtn.addEventListener("click", () => { + console.log("Filtrerar på 'vegetarian'"); + const filtered = allRecipes.filter(recipe => recipe.vegetarian === true); // Vissa recept använder en boolean för detta + displayRecipes(filtered); +}); + +allBtn.addEventListener("click", () => { + console.log("Visar alla recept igen"); + // Visa bara hela masterlistan igen + displayRecipes(allRecipes); +}); + +// ================================================== +// Initialization +// This is the code that actually runs when the page loads. +// ================================================== + +const savedRecipeJSON = localStorage.getItem("savedRecipe"); + +if (savedRecipeJSON) { + console.log("Fetching recipes from localStorage..."); + const savedRecipe = JSON.parse(savedRecipeJSON); + processRecipeData(savedRecipe); +} else { + console.log("Fetching new recipes from the API..."); + fetch(URL, requestOptions) + .then(response => response.json()) + .then(result => { + localStorage.setItem("savedRecipe", JSON.stringify(result)); + processRecipeData(result); + }) + .catch(error => console.log("error", error)); +} +}); diff --git a/index.html b/index.html index 954f32699..445d97fbc 100644 --- a/index.html +++ b/index.html @@ -15,20 +15,23 @@

    Recipe Library

    -
    -

    Filter on kitchen

    - - - - -
    -
    -

    Sort on time

    - - -
    -

    Random Recipe

    - +
    +

    Filter on diets

    + + + + + +
    +
    +

    Sort on time

    + + +
    +
    +

    Random Recipe

    + +
    @@ -38,6 +41,6 @@

    Random Recipe

    - + \ No newline at end of file diff --git a/script.js b/script.js index 185d60764..d0f82511f 100644 --- a/script.js +++ b/script.js @@ -2,6 +2,9 @@ // Settings & Constants // API keys, URLs, and initial setup variables // ================================================== + +document.addEventListener("DOMContentLoaded", () => { + const myHeaders = new Headers(); myHeaders.append('x-api-key', '8c1eb74ea4924057b8ad6bf0c4c9219c'); myHeaders.append('Content-Type', 'application/json') @@ -14,8 +17,6 @@ const requestOptions = { const URL = 'https://api.spoonacular.com/recipes/random?number=100' -document.addEventListener("DOMContentLoaded", () => { - const randomBtn = document.getElementById("random-recipe"); const cardOverlay = document.getElementById("card-overlay"); const cardContent = document.getElementById("card-content"); @@ -26,7 +27,8 @@ let allRecipes = []; // Functions // ================================================== -const createCardHTML = (recipe) => { +//Funktion för de små korten +const createGridCardHTML = (recipe) => { const imgUrl = recipe.image; const title = recipe.title; // En tom array som standard om cuisines saknas, för att undvika fel med .join() @@ -44,7 +46,7 @@ const createCardHTML = (recipe) => {

    ${title}

    Cuisine: ${cuisines.join(', ')}

    -

    Time: ${time} minuter

    +

    Cooking Time: ${time} minuter

    Ingredients:

      ${ingredientsHTML} @@ -59,11 +61,13 @@ const processRecipeData = (result) => { if (result && result.recipes && result.recipes.length > 0) { allRecipes = result.recipes; + console.log("Alla recept:", allRecipes); + const recipeContainer = document.getElementById("recipe-container"); recipeContainer.innerHTML = ''; // Clear previous results allRecipes.forEach(recipe => { - const cardHTML = createCardHTML(recipe); + const cardHTML = createGridCardHTML(recipe); recipeContainer.innerHTML += cardHTML; }); diff --git a/style.css b/style.css index b206b802b..7fc13a821 100644 --- a/style.css +++ b/style.css @@ -23,7 +23,7 @@ body{ } .container{ - max-width: 1200px; + max-width: 1300px; margin-left: 0.5rem; width: 100%; margin: 0 0.5rem; @@ -79,6 +79,7 @@ button:hover{ } .recipe-card{ + background-color: pink; margin-top: 1rem; width: 300px; min-height: 621px; @@ -137,16 +138,45 @@ button:hover{ } .card-content { - background-color: white; - padding: 20px; + background-color: rgb(243, 236, 236); + padding: 1.5rem; border-radius: 10px; - min-width: 80%; + width: 90%; height: 80%; overflow-y: auto; + + display: flex; + flex-direction: column; + align-items: center; +} + +.overlay-card-content{ + width: 100%; + max-width: 500px; +} + +.overlay-card-content img{ + width: 100%; + height: 300px; + object-fit: cover; +} + +.overlay-card-content h3{ + margin-top: 1rem; + font-weight: 400; +} + +.overlay-card-content p{ + margin-top: 1rem; } @media (min-width: 600px) { + .select-section{ + display: flex; + gap: 1rem; + } + .recipe-container{ display: flex; flex-wrap: wrap; @@ -157,5 +187,10 @@ button:hover{ font-size: 54px; } + /* .card-content{ + width: 800px; + } + */ + } \ No newline at end of file From a2393f4105d5e9aceadf82224a6926351fc8fd16 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Fri, 3 Oct 2025 10:57:42 +0200 Subject: [PATCH 05/25] comments --- backup-script.js | 77 ++++++++++++++++++++++++++++++------------------ style.css | 55 ++++++++++++++++++++++++---------- 2 files changed, 88 insertions(+), 44 deletions(-) diff --git a/backup-script.js b/backup-script.js index 6ee7da33b..ffa10ec5b 100644 --- a/backup-script.js +++ b/backup-script.js @@ -6,55 +6,62 @@ document.addEventListener("DOMContentLoaded", () => { const myHeaders = new Headers(); myHeaders.append('x-api-key', '8c1eb74ea4924057b8ad6bf0c4c9219c'); +// Tells the API that we want the data in JSON format myHeaders.append('Content-Type', 'application/json') const requestOptions = { - method: 'GET', + method: 'GET', // We want to GET (fetch) data redirect: 'follow', headers: myHeaders }; +// The address of the API we fetch data from const URL = 'https://api.spoonacular.com/recipes/random?number=100'; +// === Connecting JavaScript variables to HTML elements === + const randomBtn = document.getElementById("random-recipe"); const cardOverlay = document.getElementById("card-overlay"); const cardContent = document.getElementById("card-content"); -let allRecipes = []; const glutenBtn = document.getElementById("gluten-btn"); const dairyBtn = document.getElementById("dairy-btn"); const vegBtn = document.getElementById("veg-btn"); const veganBtn = document.getElementById("vegan-btn"); const allBtn = document.getElementById("filter-btn"); +// An empty list that will act as our local "database" for all recipes +let allRecipes = []; // ================================================== // Functions // ================================================== -// NY FUNKTION: Denna funktion kan visa vilken lista av recept som helst. +// A "display engine" that can take any list of recipes and show it on the screen const displayRecipes = (recipesToShow) => { const recipeContainer = document.getElementById("recipe-container"); - recipeContainer.innerHTML = ''; // Rensa alltid skärmen först + recipeContainer.innerHTML = ''; // Always clears the screen first to make room for new cards - // Använd den optimerade metoden för att bygga och visa korten + // Builds all the cards in memory first and then adds them to the page all at once const allCardsHTML = recipesToShow.map(recipe => createGridCardHTML(recipe)).join(''); recipeContainer.innerHTML = allCardsHTML; console.log(`Visar nu ${recipesToShow.length} recept.`); }; -// Funktion för att bygga de små korten i rutnätet +// A function for building the HTML code for a small recipe card in the grid const createGridCardHTML = (recipe) => { const imgUrl = recipe.image; const title = recipe.title; const time = recipe.readyInMinutes; const diets = recipe.diets; - const ingredients = recipe.extendedIngredients || []; + const ingredients = recipe.extendedIngredients || []; // Use an empty list if ingredients are missing const ingredientsHTML = ingredients.map(ingredient => { return `
    • ${ingredient.original}
    • `; }).join(''); + + // Returns the finished HTML code as a text string return `
      Bild på ${title} @@ -71,7 +78,7 @@ const createGridCardHTML = (recipe) => { `; }; -// Funktion för att bygga det stora, detaljerade kortet i overlayen +// A function for building the HTML for the large, detailed card in the popup window const createOverlayCardHTML = (recipe) => { const imgUrl = recipe.image; const title = recipe.title; @@ -100,42 +107,45 @@ const createOverlayCardHTML = (recipe) => { `; }; -// Funktion som bearbetar API-svaret och ritar ut alla grid-kort +// A function that runs one time at the start to handle the incoming data const processRecipeData = (result) => { - // Återställt för att hantera 'recipes'-arrayen från /random-slutpunkten + // Checks that we actually received a valid list of recipes if (result && result.recipes && result.recipes.length > 0) { + // Saves all recipes to our local "database" allRecipes = result.recipes; console.log("Alla recept:", allRecipes); - // Använd vår nya, generella visningsfunktion för att visa ALLA recept från start + // Uses our "display engine" to show all recipes from the start displayRecipes(allRecipes); } else { + // If the data was empty or invalid console.log("Could not process recipe data, result was empty or invalid"); } }; -// Lyssna efter klick på "slumpa"-knappen +// Listens for clicks on the "random" button randomBtn.addEventListener("click", () => { if (!allRecipes || allRecipes.length === 0) { console.log("No recipes available to choose from."); return; } - // Enkel logik: välj ett slumpmässigt recept från den befintliga listan + // Selects a random index from our allrecipes list const randomIndex = Math.floor(Math.random() * allRecipes.length); const randomRecipe = allRecipes[randomIndex]; - // Använd det valda receptet för att bygga och visa overlayen + // Builds the HTML for the popup window with the random recipe const cardHTML = createOverlayCardHTML(randomRecipe); - cardContent.innerHTML = cardHTML; - cardOverlay.classList.add("visible"); + cardContent.innerHTML = cardHTML;// Puts the HTML into the white box + cardOverlay.classList.add("visible");// Makes the entire popup window visible }); -// Lyssna efter klick på den mörka bakgrunden, för att stänga +// Listens for clicks on the dark background to close the popup cardOverlay.addEventListener("click", (event) => { + // Checks if the clicked element was the background itself, and not the box inside it if (event.target === cardOverlay) { - cardOverlay.classList.remove("visible"); + cardOverlay.classList.remove("visible");// Hides the popup window. } }); @@ -143,36 +153,41 @@ cardOverlay.addEventListener("click", (event) => { // Event Listeners for Filtering // ================================================== -// Lyssna på "Gluten free"-knappen +// Listens for clicks on the "Gluten free" button glutenBtn.addEventListener("click", () => { console.log("Filtrerar på 'gluten free'"); - // 1. Skapa en ny, filtrerad lista från vår masterlista + // Creates a new list that only contains recipes where the 'diets' list includes 'gluten free' const filtered = allRecipes.filter(recipe => recipe.diets.includes('gluten free')); - // 2. Använd vår visningsfunktion för att visa den nya listan + // Uses our "display engine" to show the new, filtered list displayRecipes(filtered); }); +// Listens for clicks on the "Dairy free" button dairyBtn.addEventListener("click", () => { console.log("Filtrerar på 'dairy free'"); const filtered = allRecipes.filter(recipe => recipe.diets.includes('dairy free')); displayRecipes(filtered); }); +// Listens for clicks on the "Vegan" button veganBtn.addEventListener("click", () => { const filtered = allRecipes.filter(recipe => recipe.diets.includes("vegan")) displayRecipes(filtered); }); +// Listens for clicks on the "Vegetarian" button vegBtn.addEventListener("click", () => { console.log("Filtrerar på 'vegetarian'"); - const filtered = allRecipes.filter(recipe => recipe.vegetarian === true); // Vissa recept använder en boolean för detta + // Some recipes use 'vegetarian: true' instead of in the 'diets' list, so we check that property + const filtered = allRecipes.filter(recipe => recipe.vegetarian === true); displayRecipes(filtered); }); +// Listens for clicks on the "All" button to reset the filter allBtn.addEventListener("click", () => { console.log("Visar alla recept igen"); - // Visa bara hela masterlistan igen + // Calls the "display engine" with the entire, unfiltered allrecipes list displayRecipes(allRecipes); }); @@ -181,20 +196,26 @@ allBtn.addEventListener("click", () => { // This is the code that actually runs when the page loads. // ================================================== +// Checks if there are saved recipes in the browser's memory (localStorage) const savedRecipeJSON = localStorage.getItem("savedRecipe"); +// IF there are saved recipes... if (savedRecipeJSON) { console.log("Fetching recipes from localStorage..."); - const savedRecipe = JSON.parse(savedRecipeJSON); - processRecipeData(savedRecipe); + const savedRecipe = JSON.parse(savedRecipeJSON); // Converts the text back into an object + processRecipeData(savedRecipe);// Uses the saved + // ELSE (if it's the first visit or the memory has been cleared)... } else { - console.log("Fetching new recipes from the API..."); + console.log("Fetching new recipes from the API..."); // Make a call to the API fetch(URL, requestOptions) - .then(response => response.json()) + .then(response => response.json()) // Converts the response to a JavaScript object .then(result => { + // When we have the data: + // 1. Save it to the browser's memory for next time localStorage.setItem("savedRecipe", JSON.stringify(result)); + // 2. Use the data to build the page processRecipeData(result); }) - .catch(error => console.log("error", error)); + .catch(error => console.log("error", error)); // Catches any errors during the API call } }); diff --git a/style.css b/style.css index 7fc13a821..d0e42557d 100644 --- a/style.css +++ b/style.css @@ -1,3 +1,4 @@ +/* global settings panel for my CSS */ :root{ /* Colors */ --clr-primary: rgba(0, 24, 164, 1); @@ -8,35 +9,39 @@ --ff-: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; font-weight: 700; } +/* selects ALL elements on the page */ *{ margin: 0; padding: 0; box-sizing: border-box; } +/* Basic styling for the entire page */ body{ + /* Ensures the page is always at least as tall as the screen */ min-height: 100vh; font-family: var(--ff-); display: flex; justify-content: center; flex-wrap: wrap; -} - -.container{ - max-width: 1300px; - margin-left: 0.5rem; - width: 100%; - margin: 0 0.5rem; + /* Allows content to wrap to the next line if it doesn't fit */ } h1{ font-style: bold; color: var(--clr-primary); margin-top: 1rem; + margin-left: 1rem; +} + +.container{ + max-width: 1300px; + width: 100%; } .select-section{ margin-top: 1rem; + margin-left: 1rem; } .select-section h2{ @@ -45,6 +50,7 @@ h1{ font-size: 1.1rem; } +/* styling for all buttons in the button section */ .select-section button{ font-weight: 500; font-style: medium; @@ -52,30 +58,48 @@ h1{ border-radius: 19px; border: none; font-family: var(--ff-); + /* transform: translateY(-1px); */ } + .filter button{ background-color: var(--clr-primary-1); color: var(--clr-primary); + /* transform: translateY(-1px); */ +} + +.filter-btn:active{ + background-color: var(--clr-primary); + color: white; } .sort button{ background-color: var(--clr-secondary-1); color: var(--clr-primary); + margin-top: 0.6rem; +} + +.sort-btn:active { + background-color: var(--clr-secondary); /* Or any other color variable you have */ + color: white; +} + +.random-section button{ + margin-top: 0.6rem; } button:hover{ border: 2px solid var(--clr-primary); } -.filter-btn:active{ - background-color: var(--clr-primary); - color: white; +#vegan-btn{ + margin-top: 0.5rem; } -.sort-btn:active { - background-color: var(--clr-secondary); /* Or any other color variable you have */ - color: white; +.recipe-container{ + display: flex; + justify-content: center; + flex-wrap: wrap; } .recipe-card{ @@ -91,7 +115,7 @@ button:hover{ } .recipe-card:hover{ - transform: translateY(-4px); + transform: translateY(-1px); box-shadow: 0px 0px 30px 0px rgba(0, 24, 164, 0.2); border: 2px solid var(--clr-primary); } @@ -110,8 +134,7 @@ button:hover{ .recipe-ingredients{ margin-top: 0.5rem; - list-style-type: none; - + list-style-type: none; } .recipe-ingredients li{ From 5267dc9314ecd70ac3ce0afde0773606e22e71d3 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Fri, 3 Oct 2025 11:23:01 +0200 Subject: [PATCH 06/25] fixing active button --- backup-script.js | 36 ++++++++++++++++++++++++++---------- style.css | 2 +- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/backup-script.js b/backup-script.js index ffa10ec5b..8a680dac2 100644 --- a/backup-script.js +++ b/backup-script.js @@ -29,6 +29,9 @@ const vegBtn = document.getElementById("veg-btn"); const veganBtn = document.getElementById("vegan-btn"); const allBtn = document.getElementById("filter-btn"); +// Get all buttons in the filter section into a single list +const filterButtons = document.querySelectorAll(".filter button") + // An empty list that will act as our local "database" for all recipes let allRecipes = []; @@ -45,7 +48,7 @@ const displayRecipes = (recipesToShow) => { const allCardsHTML = recipesToShow.map(recipe => createGridCardHTML(recipe)).join(''); recipeContainer.innerHTML = allCardsHTML; - console.log(`Visar nu ${recipesToShow.length} recept.`); + /* console.log(`Visar nu ${recipesToShow.length} recept.`); */ }; // A function for building the HTML code for a small recipe card in the grid @@ -64,10 +67,10 @@ const createGridCardHTML = (recipe) => { // Returns the finished HTML code as a text string return `
      - Bild på ${title} + Picture of ${title}

      ${title}

      -

      Cooking Time: ${time} minuter

      +

      Cooking Time: ${time} minutes

      Diets: ${diets}

      Ingredients:

        @@ -94,9 +97,9 @@ const createOverlayCardHTML = (recipe) => { return `
        - Bild på ${title} + Picture of ${title}

        ${title}

        -

        Time to cook: ${time} minuter

        +

        Time to cook: ${time} minutes

        Diets: ${diets}

        Ingredients:

          @@ -113,7 +116,7 @@ const processRecipeData = (result) => { if (result && result.recipes && result.recipes.length > 0) { // Saves all recipes to our local "database" allRecipes = result.recipes; - console.log("Alla recept:", allRecipes); + console.log("All Recipes:", allRecipes); // Uses our "display engine" to show all recipes from the start displayRecipes(allRecipes); @@ -149,13 +152,22 @@ cardOverlay.addEventListener("click", (event) => { } }); +const updateActiveButton = (clickedButton) => { + + filterButtons.forEach(button => { + button.classList.remove("active"); + }); + clickedButton.classList.add("active"); +}; + // ================================================== // Event Listeners for Filtering // ================================================== // Listens for clicks on the "Gluten free" button glutenBtn.addEventListener("click", () => { - console.log("Filtrerar på 'gluten free'"); + updateActiveButton(glutenBtn); // Set this button as active + console.log("Filtering for 'gluten free'"); // Creates a new list that only contains recipes where the 'diets' list includes 'gluten free' const filtered = allRecipes.filter(recipe => recipe.diets.includes('gluten free')); @@ -165,20 +177,23 @@ glutenBtn.addEventListener("click", () => { // Listens for clicks on the "Dairy free" button dairyBtn.addEventListener("click", () => { - console.log("Filtrerar på 'dairy free'"); + updateActiveButton(dairyBtn); + console.log("Filtering for 'dairy free'"); const filtered = allRecipes.filter(recipe => recipe.diets.includes('dairy free')); displayRecipes(filtered); }); // Listens for clicks on the "Vegan" button veganBtn.addEventListener("click", () => { + updateActiveButton(veganBtn); const filtered = allRecipes.filter(recipe => recipe.diets.includes("vegan")) displayRecipes(filtered); }); // Listens for clicks on the "Vegetarian" button vegBtn.addEventListener("click", () => { - console.log("Filtrerar på 'vegetarian'"); + updateActiveButton(vegBtn); + console.log("Filtering for 'vegetarian'"); // Some recipes use 'vegetarian: true' instead of in the 'diets' list, so we check that property const filtered = allRecipes.filter(recipe => recipe.vegetarian === true); displayRecipes(filtered); @@ -186,7 +201,8 @@ vegBtn.addEventListener("click", () => { // Listens for clicks on the "All" button to reset the filter allBtn.addEventListener("click", () => { - console.log("Visar alla recept igen"); + updateActiveButton(allBtn); + console.log("Showing all recipes again"); // Calls the "display engine" with the entire, unfiltered allrecipes list displayRecipes(allRecipes); }); diff --git a/style.css b/style.css index d0e42557d..40f14dcdb 100644 --- a/style.css +++ b/style.css @@ -68,7 +68,7 @@ h1{ /* transform: translateY(-1px); */ } -.filter-btn:active{ +.filter button.active{ background-color: var(--clr-primary); color: white; } From b0bc77f107b49bfe19c404f75cc1cee950177dc7 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 7 Oct 2025 15:39:16 +0200 Subject: [PATCH 07/25] sort button, api error handling --- .vscode/settings.json | 5 +- backup-script.js | 69 ++++++++++++++++++---- index.html | 4 +- script.js | 129 ------------------------------------------ style.css | 32 ++++++++--- 5 files changed, 87 insertions(+), 152 deletions(-) delete mode 100644 script.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 6f3a2913e..250032a5e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "liveServer.settings.port": 5501 + "liveServer.settings.port": 5501, + "githubPullRequests.ignoredPullRequestBranches": [ + "main" + ] } \ No newline at end of file diff --git a/backup-script.js b/backup-script.js index 8a680dac2..b0914df90 100644 --- a/backup-script.js +++ b/backup-script.js @@ -16,7 +16,7 @@ const requestOptions = { }; // The address of the API we fetch data from -const URL = 'https://api.spoonacular.com/recipes/random?number=100'; +const URL = 'https://api.spoonacular.com/recipes/random?number=30'; // === Connecting JavaScript variables to HTML elements === @@ -28,9 +28,12 @@ const dairyBtn = document.getElementById("dairy-btn"); const vegBtn = document.getElementById("veg-btn"); const veganBtn = document.getElementById("vegan-btn"); const allBtn = document.getElementById("filter-btn"); +const descenBtn = document.getElementById("descending-btn"); +const ascenBtn = document.getElementById("ascending-btn"); // Get all buttons in the filter section into a single list const filterButtons = document.querySelectorAll(".filter button") +const sortButtons = document.querySelectorAll(".sort button") // An empty list that will act as our local "database" for all recipes let allRecipes = []; @@ -70,9 +73,9 @@ const createGridCardHTML = (recipe) => { Picture of ${title}

          ${title}

          -

          Cooking Time: ${time} minutes

          -

          Diets: ${diets}

          -

          Ingredients:

          +

          Cooking Time: ${time} minutes

          +

          Diets: ${diets}

          +

          Ingredients:

            ${ingredientsHTML}
          @@ -98,10 +101,10 @@ const createOverlayCardHTML = (recipe) => { return `
          Picture of ${title} -

          ${title}

          -

          Time to cook: ${time} minutes

          -

          Diets: ${diets}

          -

          Ingredients:

          +

          ${title}

          +

          Cooking Time: ${time} minutes

          +

          Diets: ${diets}

          +

          Ingredients:

            ${ingredientsHTML}
          @@ -160,6 +163,14 @@ const updateActiveButton = (clickedButton) => { clickedButton.classList.add("active"); }; +const updateActiveSortButton = (clickedButton) => { + + sortButtons.forEach(button => { + button.classList.remove("active"); + }); + clickedButton.classList.add("active"); +}; + // ================================================== // Event Listeners for Filtering // ================================================== @@ -207,9 +218,26 @@ allBtn.addEventListener("click", () => { displayRecipes(allRecipes); }); +descenBtn.addEventListener("click", () => { + updateActiveSortButton(descenBtn); + console.log("Sorting recipes by descending cooking time"); + // Skapa en kopia av listan och sortera den + const sortedRecipes = [...allRecipes].sort((a, b) => b.readyInMinutes - a.readyInMinutes); + displayRecipes(sortedRecipes); +}); + +ascenBtn.addEventListener("click", () => { + updateActiveSortButton(ascenBtn); + console.log("Sorting recipes by ascending cooking time"); + // Skapa en kopia av listan och sortera den + const sortedRecipes = [...allRecipes].sort((a, b) => a.readyInMinutes - b.readyInMinutes); + displayRecipes(sortedRecipes); +}); + + // ================================================== // Initialization -// This is the code that actually runs when the page loads. +// This is the code that runs when the page loads // ================================================== // Checks if there are saved recipes in the browser's memory (localStorage) @@ -224,14 +252,31 @@ if (savedRecipeJSON) { } else { console.log("Fetching new recipes from the API..."); // Make a call to the API fetch(URL, requestOptions) - .then(response => response.json()) // Converts the response to a JavaScript object + .then(response => { + // Om anropet INTE gick bra (t.ex. status 404 eller 402) + if (!response.ok) { + // Om felet är specifikt "API limit reached" + if (response.status === 402) { + // Skapa ett specifikt felmeddelande + throw new Error('API daily limit reached. Please try again tomorrow.'); + } + // För alla andra serverfel + throw new Error('Could not fetch recipes from the server.'); + } + // Om allt gick bra, fortsätt som vanligt + return response.json(); // Converts the response to a JavaScript object + }) .then(result => { // When we have the data: // 1. Save it to the browser's memory for next time - localStorage.setItem("savedRecipe", JSON.stringify(result)); + localStorage.setItem("savedRecipes", JSON.stringify(result.recipes)); // 2. Use the data to build the page processRecipeData(result); }) - .catch(error => console.log("error", error)); // Catches any errors during the API call + .catch(error => { + // Fånga felet vi skapade ovan och visa det för användaren + console.error("error", error); + document.getElementById("recipe-container").innerHTML = `

          ${error.message}

          `; + }); // Catches any errors during the API call } }); diff --git a/index.html b/index.html index 445d97fbc..8b2dc6a32 100644 --- a/index.html +++ b/index.html @@ -25,8 +25,8 @@

          Filter on diets

          Sort on time

          - - + +

          Random Recipe

          diff --git a/script.js b/script.js deleted file mode 100644 index d0f82511f..000000000 --- a/script.js +++ /dev/null @@ -1,129 +0,0 @@ -// ================================================== -// Settings & Constants -// API keys, URLs, and initial setup variables -// ================================================== - -document.addEventListener("DOMContentLoaded", () => { - -const myHeaders = new Headers(); -myHeaders.append('x-api-key', '8c1eb74ea4924057b8ad6bf0c4c9219c'); -myHeaders.append('Content-Type', 'application/json') - -const requestOptions = { - method: 'GET', - redirect: 'follow', - headers: myHeaders -}; - -const URL = 'https://api.spoonacular.com/recipes/random?number=100' - -const randomBtn = document.getElementById("random-recipe"); -const cardOverlay = document.getElementById("card-overlay"); -const cardContent = document.getElementById("card-content"); -let allRecipes = []; - - -// ================================================== -// Functions -// ================================================== - -//Funktion för de små korten -const createGridCardHTML = (recipe) => { - const imgUrl = recipe.image; - const title = recipe.title; - // En tom array som standard om cuisines saknas, för att undvika fel med .join() - const cuisines = recipe.cuisines || []; - const time = recipe.readyInMinutes; - const ingredients = recipe.extendedIngredients || []; - - const ingredientsHTML = ingredients.map(ingredient => { - return `
        • ${ingredient.original}
        • `; - }).join(''); - - return ` -
          - Bild på ${title} -
          -

          ${title}

          -

          Cuisine: ${cuisines.join(', ')}

          -

          Cooking Time: ${time} minuter

          -

          Ingredients:

          -
            - ${ingredientsHTML} -
          -
          -
          - `; -}; - - -const processRecipeData = (result) => { - if (result && result.recipes && result.recipes.length > 0) { - allRecipes = result.recipes; - - console.log("Alla recept:", allRecipes); - - const recipeContainer = document.getElementById("recipe-container"); - recipeContainer.innerHTML = ''; // Clear previous results - - allRecipes.forEach(recipe => { - const cardHTML = createGridCardHTML(recipe); - recipeContainer.innerHTML += cardHTML; - }); - - console.log("Complete object:", result); - } else { - console.log("Could not process recipe data, result was empty or invalid"); - } -}; - -// Lyssna efter klick på "slumpa"-knappen -randomBtn.addEventListener("click", () => { - - // Gör inget om listan är tom - if (!allRecipes || allRecipes.length === 0) { - - console.log("Inga recept att slumpa fram än."); - return; - } - - const randomIndex = Math.floor(Math.random() * allRecipes.length); - const randomRecipe = allRecipes[randomIndex]; - - const cardHTML = createCardHTML(randomRecipe); - cardContent.innerHTML = cardHTML; - - cardOverlay.classList.add("visible"); -}); - -// Lyssna efter klick på den mörka bakgrunden, för att stänga -cardOverlay.addEventListener("click", (event) => { - // Att man klickade på själva overlayen, och inte på innehållet - if (event.target === cardOverlay) { - cardOverlay.classList.remove("visible"); - } -}); - - -// ================================================== -// Initialization -// This is the code that actually runs when the page loads. -// ================================================== - -const savedRecipeJSON = localStorage.getItem("savedRecipe"); - -if (savedRecipeJSON) { - console.log("Fetching a recipe from localStorage..."); - const savedRecipe = JSON.parse(savedRecipeJSON); - processRecipeData(savedRecipe); // Calling the function -} else { - console.log("Fetching another recipe from the API..."); - fetch(URL, requestOptions) - .then(response => response.json()) - .then(result => { - localStorage.setItem("savedRecipe", JSON.stringify(result)); - processRecipeData(result); // Calling the function - }) - .catch(error => console.log("error", error)); -} -}); diff --git a/style.css b/style.css index 40f14dcdb..b18ce4394 100644 --- a/style.css +++ b/style.css @@ -30,8 +30,17 @@ body{ h1{ font-style: bold; color: var(--clr-primary); - margin-top: 1rem; +} + +h3{ + border-top: 1px solid rgba(0, 0, 0, 0.527); + padding-top: 0.5rem; + margin-top: 0.5rem; +} + +header{ margin-left: 1rem; + margin-top: 1rem; } .container{ @@ -79,7 +88,7 @@ h1{ margin-top: 0.6rem; } -.sort-btn:active { +.sort button.active { background-color: var(--clr-secondary); /* Or any other color variable you have */ color: white; } @@ -103,7 +112,7 @@ button:hover{ } .recipe-card{ - background-color: pink; + /* background-color: pink; */ margin-top: 1rem; width: 300px; min-height: 621px; @@ -124,12 +133,19 @@ button:hover{ padding-top: 1rem; } -.recipe-cuisine{ - padding-top: 1rem; +.recipe-title{ + padding-bottom: 0.5rem; + border-bottom: 1px solid rgba(0, 0, 0, 0.473); +} + +.diet-answer, +.time-answer{ + font-weight: 400; } .recipe-time{ - padding-bottom: 1rem; + padding-bottom: 0.5rem; + padding-top: 0.5rem; } .recipe-ingredients{ @@ -210,10 +226,10 @@ button:hover{ font-size: 54px; } - /* .card-content{ + .card-content{ width: 800px; } - */ + } \ No newline at end of file From 5367e1f11c898cc5effc676f83750ea785750c39 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 7 Oct 2025 15:43:48 +0200 Subject: [PATCH 08/25] comments --- backup-script.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/backup-script.js b/backup-script.js index b0914df90..b6ce36cf7 100644 --- a/backup-script.js +++ b/backup-script.js @@ -221,7 +221,7 @@ allBtn.addEventListener("click", () => { descenBtn.addEventListener("click", () => { updateActiveSortButton(descenBtn); console.log("Sorting recipes by descending cooking time"); - // Skapa en kopia av listan och sortera den + // Making a copy of allRecipes list and sorting it const sortedRecipes = [...allRecipes].sort((a, b) => b.readyInMinutes - a.readyInMinutes); displayRecipes(sortedRecipes); }); @@ -229,7 +229,7 @@ descenBtn.addEventListener("click", () => { ascenBtn.addEventListener("click", () => { updateActiveSortButton(ascenBtn); console.log("Sorting recipes by ascending cooking time"); - // Skapa en kopia av listan och sortera den + // Making a copy of allRecipes list and sorting it const sortedRecipes = [...allRecipes].sort((a, b) => a.readyInMinutes - b.readyInMinutes); displayRecipes(sortedRecipes); }); @@ -253,17 +253,17 @@ if (savedRecipeJSON) { console.log("Fetching new recipes from the API..."); // Make a call to the API fetch(URL, requestOptions) .then(response => { - // Om anropet INTE gick bra (t.ex. status 404 eller 402) + // If the response did not go wel ( status 402) if (!response.ok) { - // Om felet är specifikt "API limit reached" + // If the error is specifically "API limit reached" if (response.status === 402) { - // Skapa ett specifikt felmeddelande + // Error message throw new Error('API daily limit reached. Please try again tomorrow.'); } - // För alla andra serverfel + // Other errors throw new Error('Could not fetch recipes from the server.'); } - // Om allt gick bra, fortsätt som vanligt + // If everyhting went well return response.json(); // Converts the response to a JavaScript object }) .then(result => { @@ -274,7 +274,7 @@ if (savedRecipeJSON) { processRecipeData(result); }) .catch(error => { - // Fånga felet vi skapade ovan och visa det för användaren + // Catch the error created above and show it to the user console.error("error", error); document.getElementById("recipe-container").innerHTML = `

          ${error.message}

          `; }); // Catches any errors during the API call From bdbfc892253ca216443152cb3975c8aae3ffd3f9 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 7 Oct 2025 15:56:52 +0200 Subject: [PATCH 09/25] error handling --- backup-script.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backup-script.js b/backup-script.js index b6ce36cf7..6f94213cd 100644 --- a/backup-script.js +++ b/backup-script.js @@ -155,6 +155,7 @@ cardOverlay.addEventListener("click", (event) => { } }); +//activates the class "active", witch removes and ads the colors for the clicked buttons const updateActiveButton = (clickedButton) => { filterButtons.forEach(button => { @@ -163,6 +164,7 @@ const updateActiveButton = (clickedButton) => { clickedButton.classList.add("active"); }; +//activates the class "active", witch removes and ads the colors for the clicked buttons const updateActiveSortButton = (clickedButton) => { sortButtons.forEach(button => { From 3206f7f124c74b48e8f22e32edeab9ae22efcb39 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 7 Oct 2025 16:02:55 +0200 Subject: [PATCH 10/25] style changes --- index.html | 7 ++++--- style.css | 23 +++++++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/index.html b/index.html index 8b2dc6a32..e13fc1734 100644 --- a/index.html +++ b/index.html @@ -12,9 +12,7 @@

          Recipe Library

          -
          - -
          +

          Filter on diets

          @@ -33,6 +31,9 @@

          Random Recipe

          + + +
          diff --git a/style.css b/style.css index b18ce4394..3740e8ec4 100644 --- a/style.css +++ b/style.css @@ -30,7 +30,8 @@ body{ h1{ font-style: bold; color: var(--clr-primary); -} +/* margin-left: 1rem; + */} h3{ border-top: 1px solid rgba(0, 0, 0, 0.527); @@ -39,7 +40,7 @@ h3{ } header{ - margin-left: 1rem; + margin-left: 2.5rem; margin-top: 1rem; } @@ -50,8 +51,8 @@ header{ .select-section{ margin-top: 1rem; - margin-left: 1rem; -} +/* margin-left: 1rem; + */} .select-section h2{ margin-top:0.5rem; @@ -211,9 +212,19 @@ button:hover{ @media (min-width: 600px) { + header{ + margin-left: 1.6rem; + } + + h1{ + font-size: 54px; + margin: 0; + } + .select-section{ display: flex; gap: 1rem; + margin-left: 0.2rem; } .recipe-container{ @@ -222,10 +233,6 @@ button:hover{ gap: 0.8rem; } - h1{ - font-size: 54px; - } - .card-content{ width: 800px; } From 5c3c240e440c30b92c36c87cea6a43048a913fa6 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 7 Oct 2025 16:28:20 +0200 Subject: [PATCH 11/25] empty state message --- backup-script.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/backup-script.js b/backup-script.js index 6f94213cd..8d23a0fb6 100644 --- a/backup-script.js +++ b/backup-script.js @@ -44,14 +44,19 @@ let allRecipes = []; // A "display engine" that can take any list of recipes and show it on the screen const displayRecipes = (recipesToShow) => { + + // Finds the container for all the recipe cards const recipeContainer = document.getElementById("recipe-container"); - recipeContainer.innerHTML = ''; // Always clears the screen first to make room for new cards + + if (!recipesToShow || recipesToShow.length === 0) { + recipeContainer.innerHTML = '

          Sorry, no recipes match your filter. Please try another one!

          '; + return; // terminate the function + } // Builds all the cards in memory first and then adds them to the page all at once const allCardsHTML = recipesToShow.map(recipe => createGridCardHTML(recipe)).join(''); recipeContainer.innerHTML = allCardsHTML; - /* console.log(`Visar nu ${recipesToShow.length} recept.`); */ }; // A function for building the HTML code for a small recipe card in the grid From f7aa80143e92c72a0da55574ac4a031a6fed728e Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 7 Oct 2025 16:46:44 +0200 Subject: [PATCH 12/25] comments --- backup-script.js | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/backup-script.js b/backup-script.js index 8d23a0fb6..8c90f51f2 100644 --- a/backup-script.js +++ b/backup-script.js @@ -66,13 +66,12 @@ const createGridCardHTML = (recipe) => { const time = recipe.readyInMinutes; const diets = recipe.diets; const ingredients = recipe.extendedIngredients || []; // Use an empty list if ingredients are missing - + //Go through every ingredient in the list.. const ingredientsHTML = ingredients.map(ingredient => { - return `
        • ${ingredient.original}
        • `; - }).join(''); - + return `
        • ${ingredient.original}
        • `; //..and make an HTML-list
        • for each item + }).join(''); //then paste them together in one string - // Returns the finished HTML code as a text string + //Building the HTML were we paste the variables that was created above ${...} return `
          Picture of ${title} @@ -92,17 +91,18 @@ const createGridCardHTML = (recipe) => { // A function for building the HTML for the large, detailed card in the popup window const createOverlayCardHTML = (recipe) => { const imgUrl = recipe.image; - const title = recipe.title; -/* const cuisines = recipe.cuisines || []; - */ const time = recipe.readyInMinutes; + const title = recipe.title; + const time = recipe.readyInMinutes; const diets = recipe.diets; - const ingredients = recipe.extendedIngredients || []; + + const ingredients = recipe.extendedIngredients || [];//the list with ingrediens, if its empty make an empty list to avoid errors const instructions = recipe.instructions; - + //Go through every ingredient in the list.. const ingredientsHTML = ingredients.map(ingredient => { - return `
        • ${ingredient.original}
        • `; - }).join(''); + return `
        • ${ingredient.original}
        • `; //..and make an HTML-list
        • for each item + }).join(''); //Then paste them together in one string + //Building the HTML were we paste the variables that was created above ${...} return `
          Picture of ${title} @@ -137,8 +137,12 @@ const processRecipeData = (result) => { // Listens for clicks on the "random" button randomBtn.addEventListener("click", () => { + // First, a safety check to make sure there are recipes in our list + // If the list doesn't exist or is empty... if (!allRecipes || allRecipes.length === 0) { + // ...log a message to the console... console.log("No recipes available to choose from."); + // ...and stop the function here return; } @@ -162,19 +166,21 @@ cardOverlay.addEventListener("click", (event) => { //activates the class "active", witch removes and ads the colors for the clicked buttons const updateActiveButton = (clickedButton) => { - + // First, remove the "active" class from all sort buttons filterButtons.forEach(button => { button.classList.remove("active"); }); + // Then, add the "active" class to the one that was just clicked clickedButton.classList.add("active"); }; -//activates the class "active", witch removes and ads the colors for the clicked buttons +//activates the class "active", witch removes and ads/removes the colors for the clicked buttons const updateActiveSortButton = (clickedButton) => { - + // First, remove the "active" class from all sort buttons sortButtons.forEach(button => { button.classList.remove("active"); }); + // Then, add the "active" class to the one that was just clicked clickedButton.classList.add("active"); }; From e88501c664a5667a870e4d2ac32124652e3b87ad Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Tue, 7 Oct 2025 16:49:42 +0200 Subject: [PATCH 13/25] style changes --- style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/style.css b/style.css index 3740e8ec4..00cc5370a 100644 --- a/style.css +++ b/style.css @@ -213,7 +213,7 @@ button:hover{ @media (min-width: 600px) { header{ - margin-left: 1.6rem; + margin-left: 1.9rem; } h1{ From 819ec8b2d8224e46754e73f1b974608f3f65e13d Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 8 Oct 2025 10:13:08 +0200 Subject: [PATCH 14/25] fixed vegan btn --- backup-script.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backup-script.js b/backup-script.js index 8c90f51f2..c47ae1e96 100644 --- a/backup-script.js +++ b/backup-script.js @@ -210,7 +210,8 @@ dairyBtn.addEventListener("click", () => { // Listens for clicks on the "Vegan" button veganBtn.addEventListener("click", () => { updateActiveButton(veganBtn); - const filtered = allRecipes.filter(recipe => recipe.diets.includes("vegan")) + + const filtered = allRecipes.filter(recipe => recipe.vegan === true); displayRecipes(filtered); }); @@ -247,7 +248,6 @@ ascenBtn.addEventListener("click", () => { displayRecipes(sortedRecipes); }); - // ================================================== // Initialization // This is the code that runs when the page loads From 5cb8ae9317710e83f58f49ff233f40f5ad43da3e Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 8 Oct 2025 10:24:54 +0200 Subject: [PATCH 15/25] fix scroll problem --- style.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/style.css b/style.css index 00cc5370a..64a505d3c 100644 --- a/style.css +++ b/style.css @@ -25,6 +25,8 @@ body{ justify-content: center; flex-wrap: wrap; /* Allows content to wrap to the next line if it doesn't fit */ + overflow-y: scroll; + } h1{ From c2180e6db50b71c53d07af6123d1d14cf8f1229f Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 8 Oct 2025 11:00:51 +0200 Subject: [PATCH 16/25] bug fixes --- index.html | 2 +- style.css | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index e13fc1734..5b5f56f28 100644 --- a/index.html +++ b/index.html @@ -30,7 +30,7 @@

          Sort on time

          Random Recipe

          -
        • + diff --git a/style.css b/style.css index 64a505d3c..bff84d348 100644 --- a/style.css +++ b/style.css @@ -70,14 +70,13 @@ header{ border-radius: 19px; border: none; font-family: var(--ff-); - /* transform: translateY(-1px); */ + border: 2px solid transparent; } .filter button{ background-color: var(--clr-primary-1); color: var(--clr-primary); - /* transform: translateY(-1px); */ } .filter button.active{ @@ -88,7 +87,7 @@ header{ .sort button{ background-color: var(--clr-secondary-1); color: var(--clr-primary); - margin-top: 0.6rem; + } .sort button.active { @@ -97,7 +96,7 @@ header{ } .random-section button{ - margin-top: 0.6rem; + } button:hover{ @@ -115,7 +114,6 @@ button:hover{ } .recipe-card{ - /* background-color: pink; */ margin-top: 1rem; width: 300px; min-height: 621px; @@ -227,6 +225,7 @@ button:hover{ display: flex; gap: 1rem; margin-left: 0.2rem; + align-items: flex-end; } .recipe-container{ From 74418daafb4b34888f6e809338a76a15ce06025a Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 8 Oct 2025 11:32:20 +0200 Subject: [PATCH 17/25] comments and bug fix --- backup-script.js | 10 +-- index.html | 55 ++++++++++---- style.css | 189 +++++++++++++++++++++++++---------------------- 3 files changed, 140 insertions(+), 114 deletions(-) diff --git a/backup-script.js b/backup-script.js index c47ae1e96..eb4797f66 100644 --- a/backup-script.js +++ b/backup-script.js @@ -23,10 +23,10 @@ const URL = 'https://api.spoonacular.com/recipes/random?number=30'; const randomBtn = document.getElementById("random-recipe"); const cardOverlay = document.getElementById("card-overlay"); const cardContent = document.getElementById("card-content"); + const glutenBtn = document.getElementById("gluten-btn"); const dairyBtn = document.getElementById("dairy-btn"); const vegBtn = document.getElementById("veg-btn"); -const veganBtn = document.getElementById("vegan-btn"); const allBtn = document.getElementById("filter-btn"); const descenBtn = document.getElementById("descending-btn"); const ascenBtn = document.getElementById("ascending-btn"); @@ -207,14 +207,6 @@ dairyBtn.addEventListener("click", () => { displayRecipes(filtered); }); -// Listens for clicks on the "Vegan" button -veganBtn.addEventListener("click", () => { - updateActiveButton(veganBtn); - - const filtered = allRecipes.filter(recipe => recipe.vegan === true); - displayRecipes(filtered); -}); - // Listens for clicks on the "Vegetarian" button vegBtn.addEventListener("click", () => { updateActiveButton(vegBtn); diff --git a/index.html b/index.html index 5b5f56f28..bc85f6015 100644 --- a/index.html +++ b/index.html @@ -1,47 +1,70 @@ - + - + + Recipe Library - Wok This Way
          -
          + +

          Recipe Library

          -
          +
          + + +
          +

          Filter on diets

          -
          + +

          Sort on time

          + +

          Random Recipe

          -
          -
          -
          +
          + + + + + +
          - + + +
          + +
          +
          - -
          +
          -
          -
          -
          - + \ No newline at end of file diff --git a/style.css b/style.css index bff84d348..114856f48 100644 --- a/style.css +++ b/style.css @@ -1,119 +1,118 @@ -/* global settings panel for my CSS */ -:root{ +/* ================================================== + 1. Global Styles & Variables + ================================================== */ + +/* :root is a global settings panel for my CSS */ +:root { /* Colors */ --clr-primary: rgba(0, 24, 164, 1); --clr-primary-1: rgba(204, 255, 226, 1); --clr-secondary: rgba(255, 101, 137, 1); --clr-secondary-1: rgba(255, 236, 234, 1); - --ff-: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; font-weight: 700; + /* Font */ + --ff-: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-weight: 700; } -/* selects ALL elements on the page */ -*{ +/* '*' selects ALL elements on the page for a basic reset */ +* { margin: 0; padding: 0; box-sizing: border-box; } /* Basic styling for the entire page */ -body{ - /* Ensures the page is always at least as tall as the screen */ +body { min-height: 100vh; font-family: var(--ff-); display: flex; justify-content: center; flex-wrap: wrap; - /* Allows content to wrap to the next line if it doesn't fit */ overflow-y: scroll; - + /* Always show scrollbar to prevent layout shifts */ } -h1{ - font-style: bold; - color: var(--clr-primary); -/* margin-left: 1rem; - */} +/* ================================================== + 2. Main Layout & Header + ================================================== */ -h3{ - border-top: 1px solid rgba(0, 0, 0, 0.527); - padding-top: 0.5rem; - margin-top: 0.5rem; +.container { + max-width: 1300px; + width: 100%; } -header{ +header { margin-left: 2.5rem; margin-top: 1rem; } -.container{ - max-width: 1300px; - width: 100%; +h1 { + font-style: bold; + color: var(--clr-primary); } -.select-section{ +/* ================================================== + 3. Filter & Sort Section + ================================================== */ + +.select-section { margin-top: 1rem; -/* margin-left: 1rem; - */} +} -.select-section h2{ - margin-top:0.5rem; +.select-section h2 { + margin-top: 0.5rem; margin-bottom: 0.5rem; font-size: 1.1rem; } -/* styling for all buttons in the button section */ -.select-section button{ +/* General styling for all buttons in the filter/sort section */ +.select-section button { font-weight: 500; font-style: medium; - padding: 0.65rem 1rem 0.65rem 1rem; + padding: 0.65rem 1rem; border-radius: 19px; - border: none; font-family: var(--ff-); border: 2px solid transparent; + /* Transparent border prevents jumping on hover */ } - -.filter button{ +.filter button { background-color: var(--clr-primary-1); color: var(--clr-primary); } -.filter button.active{ - background-color: var(--clr-primary); - color: white; -} - -.sort button{ +.sort button { background-color: var(--clr-secondary-1); color: var(--clr-primary); - } -.sort button.active { - background-color: var(--clr-secondary); /* Or any other color variable you have */ - color: white; +/* States for buttons (hover, active) */ +button:hover { + border: 2px solid var(--clr-primary); } -.random-section button{ - +.filter button.active { + background-color: var(--clr-primary); + color: white; } -button:hover{ - border: 2px solid var(--clr-primary); +.sort button.active { + background-color: var(--clr-secondary); + color: white; } -#vegan-btn{ - margin-top: 0.5rem; -} +/* ================================================== + 4. Recipe Grid & Cards + ================================================== */ -.recipe-container{ +.recipe-container { display: flex; justify-content: center; flex-wrap: wrap; } -.recipe-card{ +.recipe-card { margin-top: 1rem; width: 300px; min-height: 621px; @@ -124,120 +123,132 @@ button:hover{ border: 1px solid rgb(179, 179, 179); } -.recipe-card:hover{ - transform: translateY(-1px); +.recipe-card:hover { + transform: translateY(-1px); box-shadow: 0px 0px 30px 0px rgba(0, 24, 164, 0.2); border: 2px solid var(--clr-primary); } -.recipe-card h2{ +/* Content inside the recipe cards */ +.recipe-card h2 { padding-top: 1rem; } -.recipe-title{ +.recipe-title { padding-bottom: 0.5rem; border-bottom: 1px solid rgba(0, 0, 0, 0.473); } -.diet-answer, -.time-answer{ +.recipe-time { + padding-bottom: 0.5rem; + padding-top: 0.5rem; +} + +.diet-answer, +.time-answer { font-weight: 400; } -.recipe-time{ - padding-bottom: 0.5rem; +.recipe-card h3 { + border-top: 1px solid rgba(0, 0, 0, 0.527); padding-top: 0.5rem; + margin-top: 0.5rem; } -.recipe-ingredients{ +.recipe-ingredients { margin-top: 0.5rem; - list-style-type: none; + list-style-type: none; } -.recipe-ingredients li{ +.recipe-ingredients li { font-weight: 500; font-style: medium; font-size: 1rem; } -.card-overlay{ +/* ================================================== + 5. Overlay / Modal + ================================================== */ + +.card-overlay { display: none; + /* Hidden by default */ justify-content: center; - align-items: center; - position: fixed; + align-items: center; + position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); - backdrop-filter: blur(5px); + backdrop-filter: blur(5px); } -.card-overlay.visible{ +.card-overlay.visible { display: flex; + /* Made visible with JavaScript */ } .card-content { background-color: rgb(243, 236, 236); padding: 1.5rem; border-radius: 10px; - width: 90%; - height: 80%; - overflow-y: auto; - + width: 90%; + height: 80%; + overflow-y: auto; display: flex; flex-direction: column; align-items: center; } -.overlay-card-content{ +/* Content inside the overlay */ +.overlay-card-content { width: 100%; max-width: 500px; } -.overlay-card-content img{ +.overlay-card-content img { width: 100%; height: 300px; object-fit: cover; } -.overlay-card-content h3{ +.overlay-card-content h3 { margin-top: 1rem; font-weight: 400; } -.overlay-card-content p{ - margin-top: 1rem; +.overlay-card-content p { + margin-top: 1rem; } -@media (min-width: 600px) { +/* ================================================== + 6. Responsive Styles (Media Queries) + ================================================== */ - header{ +@media (min-width: 600px) { + header { margin-left: 1.9rem; } - h1{ + h1 { font-size: 54px; margin: 0; } - .select-section{ + .select-section { display: flex; gap: 1rem; margin-left: 0.2rem; align-items: flex-end; + /* Aligns filter/sort/random sections along the bottom */ } - .recipe-container{ - display: flex; - flex-wrap: wrap; + .recipe-container { gap: 0.8rem; } - .card-content{ + .card-content { width: 800px; } - - - -} \ No newline at end of file +} \ No newline at end of file From 4f35fb553b4f904bae7b492554be083f0236440f Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Wed, 8 Oct 2025 11:36:29 +0200 Subject: [PATCH 18/25] bug fix --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index bc85f6015..4f3de61f9 100644 --- a/index.html +++ b/index.html @@ -15,7 +15,6 @@ ================================================== -->

          Recipe Library

          -
          + \ No newline at end of file From 47ad2466e857545d82aa35ce8108bfe2b5ac5cfe Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Thu, 9 Oct 2025 17:52:56 +0200 Subject: [PATCH 24/25] style changes --- backup-script.js | 4 ++-- style.css | 10 +++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/backup-script.js b/backup-script.js index 7acd98cb6..e052200f5 100644 --- a/backup-script.js +++ b/backup-script.js @@ -110,10 +110,10 @@ const createOverlayCardHTML = (recipe) => {

          Cooking Time: ${time} minutes

          Diets: ${diets}

          Ingredients:

          -
            +
              ${ingredientsHTML}
            -

            ${instructions}

            +

            ${instructions}

          `; }; diff --git a/style.css b/style.css index c05026877..1f3ef4dee 100644 --- a/style.css +++ b/style.css @@ -160,7 +160,8 @@ button:hover { font-weight: 400; } -.recipe-card h3 { +.recipe-card h3, +.overlay-card-content h3 { border-top: 1px solid rgba(0, 0, 0, 0.527); padding-top: 0.5rem; margin-top: 0.5rem; @@ -224,12 +225,7 @@ button:hover { object-fit: cover; } -.overlay-card-content h3 { - margin-top: 1rem; - font-weight: 400; -} - -.overlay-card-content p { +.recipe-instructions{ margin-top: 1rem; } From f798e56c45bc77ee906a50235e61598430c540d1 Mon Sep 17 00:00:00 2001 From: Rebecca Sighed Paemurd Date: Fri, 10 Oct 2025 11:00:58 +0200 Subject: [PATCH 25/25] comments --- backup-script.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backup-script.js b/backup-script.js index e052200f5..37ee9e808 100644 --- a/backup-script.js +++ b/backup-script.js @@ -12,7 +12,7 @@ myHeaders.append('Content-Type', 'application/json') const requestOptions = { method: 'GET', // We want to GET (fetch) data redirect: 'follow', - headers: myHeaders + headers: myHeaders // API-key sends as a HTTP-header, more safe without the key in the URL }; // The address of the API we fetch data from @@ -48,9 +48,11 @@ const displayRecipes = (recipesToShow) => { // Finds the container for all the recipe cards const recipeContainer = document.getElementById("recipe-container"); + //if (recipesToShow) is flase, undefined - length === 0, checking the list, if it contains any recipes + // || = if anyone of these two things is true - show empty-state-message if (!recipesToShow || recipesToShow.length === 0) { recipeContainer.innerHTML = '

          Sorry, no recipes match your filter. Please try another one!

          '; - return; // terminate the function + return; // terminate the function otherwise it would runt map() with an empty list } // Builds all the cards in memory first and then adds them to the page all at once