From 778fddef3a96fb309e314f3a1365e53a618e057b Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Tue, 23 Sep 2025 09:21:04 +0200 Subject: [PATCH 01/17] add HTML --- index.html | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++ script.js | 0 style.css | 0 3 files changed, 103 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..596ab83ad --- /dev/null +++ b/index.html @@ -0,0 +1,103 @@ + + + + + + + Recipe Library + + + + +
+

Recipe Library

+
+ +
+
+ +
+

Filter on kitchen

+ + + + + +
+ + +
+

Sort on time

+ + +
+
+ + +
+

Need inspiration?

+ +
+
+ + + + + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 000000000..e69de29bb diff --git a/style.css b/style.css new file mode 100644 index 000000000..e69de29bb From 70fda92887f19a23e0e519a3d8f46ab47909c18b Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Tue, 23 Sep 2025 17:17:04 +0200 Subject: [PATCH 02/17] add styling filter and sort --- index.html | 87 +++++++++++++++++++++++++++++++++++++------------ style.css | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 20 deletions(-) diff --git a/index.html b/index.html index 596ab83ad..5c238d4f2 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,10 @@ name="viewport" content="width=device-width, initial-scale=1.0" > + Recipe Library Recipe Library
-
- -
-

Filter on kitchen

+ +
+

Filter on kitchen

+
+
- -
-

Sort on time

+ +
+

Sort on time

+
- +
@@ -96,6 +102,47 @@

Need inspiration?

+ +
+
+
+ +

Some title

+

+
+
+ +

Some title

+

+
+
+ +

Some title

+

+
+
+ +

Some title

+

+
+
+
diff --git a/style.css b/style.css index e69de29bb..c6ae6af73 100644 --- a/style.css +++ b/style.css @@ -0,0 +1,95 @@ +* { + box-sizing: border-box; +} + +body { + font-family: "Jost", sans-serif; +} + +h1 { + color: #0018A4; + font-weight: 700; + font-size: 40px; + margin-bottom: 10px; +} + +h3 { + font-weight: 700; + font-size: 24px; +} + +.options-container { + display: flex; + flex-direction: column; + gap: 20px; +} + +.form h3 { + margin: 5px 0; +} + +.btn-group { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin: 5px 0 5px 0; +} + +input[type="radio"], +input[type="checkbox"] { + display: none; +} + +/* Styling för knappar (span inuti label) */ +.btn-filter span, +.btn-sort span { + display: inline-block; + padding: 8px 16px; + border-radius: 50px; + font-weight: 500; + font-size: 16px; + color: #0018A4; + background-color: #CCFFE2; + border: 2px solid transparent; + transition: all 0.2s ease; +} + +.btn-sort span { + background-color: #FFECEA; +} + +.btn-filter span:hover, +.btn-sort span:hover { + border: 2px solid #0018A4; + cursor: pointer; +} + +/* Selected/checked effekt */ +.btn-filter input[type="checkbox"]:checked+span { + background-color: #0018A4; + color: white; + border: none; +} + +.btn-sort input[type="radio"]:checked+span { + background-color: #FF6589; + color: white; + border: none; +} + +/* Surprise me */ +.btn-random { + background-image: linear-gradient(135deg, #ae88ff 0%, #7f9fff 51%, #aa88ff 100%); + color: yellow; + font-weight: 500; + font-size: 16px; + padding: 8px 16px; + border-radius: 50px; + border: none; + cursor: pointer; + transition: all 0.2s ease; +} + +.btn-random:hover { + transform: scale(1.05); +} \ No newline at end of file From 3426642bf14b494522c129903d096e1c15c170be Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Wed, 24 Sep 2025 16:03:07 +0200 Subject: [PATCH 03/17] add media query and const to JS --- images/pizza.jpg | Bin 0 -> 59587 bytes index.html | 169 +++++++++++++++++++------------------------- script.js | 4 ++ style.css | 178 ++++++++++++++++++++++++++++++++++++----------- 4 files changed, 212 insertions(+), 139 deletions(-) create mode 100644 images/pizza.jpg diff --git a/images/pizza.jpg b/images/pizza.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0a05c1819cd38da134ac40707ee0f4cc6ae15062 GIT binary patch literal 59587 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 literal 0 HcmV?d00001 diff --git a/index.html b/index.html index 5c238d4f2..0fed15280 100644 --- a/index.html +++ b/index.html @@ -24,122 +24,93 @@

Recipe Library

- -
-

Filter on kitchen

-
- - - - - + +
+

Filter on kitchen

+
+ + + + +
- -
-

Sort on time

-
- - + +
+

Sort on time

+
+ +
-
-

Need inspiration?

- +
+

Need inspiration?

+
+ +
-
- -

Some title

-

-
-
- -

Some title

-

-
-
- -

Some title

-

+
+

Temporary placeholder for filters/sorting options

+
-
+
-

Some title

-

+

Temporary placeholder recipe

+
+

Cuisine: Italian
+ Time: 40 minutes +

+
+

Ingredients
+ 500 g pack bread mix
+ 2 tbsp. olive oil, plus a little extra for
+ drizzling
+ 25 g parmesan (or vegetarian alternative), grated
+ 75 g dolcelatte cheese (or vegetarian alternative) +

diff --git a/script.js b/script.js index e69de29bb..aeeb6e1d9 100644 --- a/script.js +++ b/script.js @@ -0,0 +1,4 @@ +const box1 = document.getElementById("box1") +const filterButtons = document.querySelector("btn-filter") +const sortButtons = document.querySelector("btn-sort") +const randomButtons = document.getElementById("randomBtn") \ No newline at end of file diff --git a/style.css b/style.css index c6ae6af73..6b6b55334 100644 --- a/style.css +++ b/style.css @@ -4,77 +4,78 @@ body { font-family: "Jost", sans-serif; + padding: 0 14px; +} + +header, +.options-container, +.recipes-container { + margin: 0 auto; + max-width: 1200px; } h1 { color: #0018A4; font-weight: 700; font-size: 40px; - margin-bottom: 10px; } -h3 { +h2 { font-weight: 700; - font-size: 24px; + font-size: 20px; } .options-container { display: flex; flex-direction: column; gap: 20px; + margin: 0 auto; } -.form h3 { - margin: 5px 0; -} - -.btn-group { +.filter-buttons, +.sorting-buttons, +.random-buttons { display: flex; + flex-direction: row; flex-wrap: wrap; - gap: 10px; - margin: 5px 0 5px 0; + margin: 0; + gap: 12px; } -input[type="radio"], -input[type="checkbox"] { - display: none; -} - -/* Styling för knappar (span inuti label) */ -.btn-filter span, -.btn-sort span { - display: inline-block; - padding: 8px 16px; - border-radius: 50px; - font-weight: 500; - font-size: 16px; - color: #0018A4; +.btn-filter { background-color: #CCFFE2; + color: #0018A4; + border-radius: 50px; + padding: 8px 16px; + gap: 10px; + text-align: center; + font-size: 18px; border: 2px solid transparent; transition: all 0.2s ease; } -.btn-sort span { - background-color: #FFECEA; -} - -.btn-filter span:hover, -.btn-sort span:hover { +.btn-filter:hover { border: 2px solid #0018A4; cursor: pointer; + transform: scale(1.05); } -/* Selected/checked effekt */ -.btn-filter input[type="checkbox"]:checked+span { - background-color: #0018A4; - color: white; - border: none; +.btn-sort { + background-color: #FFECEA; + color: #0018A4; + border-radius: 50px; + padding: 8px 16px; + gap: 10px; + text-align: center; + font-size: 18px; + border: 2px solid transparent; + transition: all 0.2s ease; } -.btn-sort input[type="radio"]:checked+span { - background-color: #FF6589; - color: white; - border: none; +.btn-sort:hover { + border: 2px solid #0018A4; + cursor: pointer; + transform: scale(1.05); } /* Surprise me */ @@ -82,9 +83,11 @@ input[type="checkbox"] { background-image: linear-gradient(135deg, #ae88ff 0%, #7f9fff 51%, #aa88ff 100%); color: yellow; font-weight: 500; - font-size: 16px; + font-size: 18px; padding: 8px 16px; border-radius: 50px; + margin-bottom: 12px; + border: 2px solid transparent; border: none; cursor: pointer; transition: all 0.2s ease; @@ -92,4 +95,99 @@ input[type="checkbox"] { .btn-random:hover { transform: scale(1.05); +} + +.btn-filter.selected { + background-color: #0018A4; + color: white; +} + +.btn-sort.selected { + background-color: #FF6589; + color: white; +} + +.btn-random.selected { + backround-image: none; + background-color: #FFD700; + color: #0018A4; +} + +/* Recipe-container */ +.recipes-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + justify-content: center; + gap: 20px; + margin-top: 40px; +} + +.recipe-card { + border: 2px solid #e9e9e9; + background: white; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + padding: 12px; + border-radius: 16px; + transition: all 0.2s ease; +} + +.recipe-card:hover { + border: 2px solid #0018A4; + box-shadow: 0px 0px 30px 0px rgba(0, 24, 164, 0.2); +} + +hr.solid { + border: none; + border-top: 2px solid #E9E9E9; + height: 1px; + width: 100%; + margin: 10px 0; + align-self: stretch; +} + +.ingredients-title { + display: inline-block; + margin-bottom: 5px; +} + +.card-image { + display: block; + margin: 0 auto; + width: 100%; + border-radius: 12px; +} + +@media (min-width: 668px) { + h1 { + font-size: 64px; + } + + h2 { + font-size: 28px; + } + + header, + .options-container, + .recipes-container { + margin: 0; + padding-left: 14px; + } + + .options-container { + flex-direction: row; + align-items: flex-start; + gap: 100px; + margin-bottom: 40px; + } + + .recipes-container { + max-width: 800px; + } + + .recipe-card { + gap: 16px; + } } \ No newline at end of file From 8c67cd61d499512a802857e25bab36823a899e1f Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Thu, 25 Sep 2025 15:03:08 +0200 Subject: [PATCH 04/17] add js --- script.js | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- style.css | 3 +- 2 files changed, 116 insertions(+), 5 deletions(-) diff --git a/script.js b/script.js index aeeb6e1d9..5e7bc4d4b 100644 --- a/script.js +++ b/script.js @@ -1,4 +1,114 @@ -const box1 = document.getElementById("box1") -const filterButtons = document.querySelector("btn-filter") -const sortButtons = document.querySelector("btn-sort") -const randomButtons = document.getElementById("randomBtn") \ No newline at end of file +console.log("Hello js") + +//Get element +const filterButtons = document.querySelectorAll(".btn-filter") +const sortButtons = document.querySelectorAll(".btn-sort") +const randomButtons = document.getElementById("randomBtn") +const userSelection = document.getElementById("userSelection") +//Messages for kitchen filter +const filterMessages = { + italian: "How about spaghetti carbonara?", + thai: "Maybe some Pad Thai?", + mexican: "Up for spicy?", + korean: "Cold noodles are popular on TikTok" +} + +// Messages for sort +const sortMessages = { + ascending: "Are you in a hurry?", + descending: "Nice, let's take it chill" +} +const surpriseMessages = [ + "🍣 Sushi bowl", + "🍝 Creamy chicken pasta", + "🌮 Beef tacos", + "🍛 Vegan curry", + "🥪 BBQ pulled pork sandwich", + "🥚 Shakshuka", + "🥗 Greek salad", + "🍜 Ramen noodles", + "🍫 Chocolate lava cake" +] + +let selectedFilters = [] +let selectedSort = null + +const updateUserSelection = () => { + let messages = [] + + if (selectedFilters.length > 0) { + selectedFilters.forEach(f => { + if (filterMessages[f]) messages.push(filterMessages[f]) + }) + } + + if (selectedSort) { + messages.push(sortMessages[selectedSort]) + } + + if (messages.length === 0) { + userSelection.textContent = "" + } else { + userSelection.innerHTML = messages.join("
") + } +} + +filterButtons.forEach(button => { + button.addEventListener("click", () => { + const value = button.dataset.value + + if (value === "all") { + filterButtons.forEach(btn => btn.classList.remove("selected")) + + button.classList.add("selected") + selectedFilters = [] + userSelection.textContent = "You like everything, maybe try choosing the surprise me button?" + console.log(userSelection.textContent) + return + + } else { + const allButton = document.querySelector('.btn-filter[data-value="all"]') + allButton.classList.remove("selected") + + button.classList.toggle("selected") + + selectedFilters = Array.from(filterButtons) + .filter(btn => btn.classList.contains("selected")) + .map(btn => btn.dataset.value) + + if (selectedFilters.length === 0) { + userSelection.textContent = "" + + } else { + const messages = selectedFilters.map(f => filterMessages[f]).filter(Boolean) + + userSelection.innerHTML = messages.join("
") + } + } + updateUserSelection() + }) +}) + +sortButtons.forEach(button => { + button.addEventListener("click", () => { + if (selectedSort === button.dataset.value) { + selectedSort = null + button.classList.remove("selected") + } else { + selectedSort = button.dataset.value + sortButtons.forEach(btn => btn.classList.remove("selected")) + button.classList.add("selected") + } + updateUserSelection() + }) +}) + +randomButtons.addEventListener("click", () => { + const randomIndex = Math.floor(Math.random() * surpriseMessages.length) + const randomMessage = surpriseMessages[randomIndex] + userSelection.innerHTML = `🎉 Surprise! How about: ${randomMessage}?` +}) + + + + diff --git a/style.css b/style.css index 6b6b55334..2d3c8bf1e 100644 --- a/style.css +++ b/style.css @@ -105,10 +105,11 @@ h2 { .btn-sort.selected { background-color: #FF6589; color: white; + border: none; } .btn-random.selected { - backround-image: none; + background-image: none; background-color: #FFD700; color: #0018A4; } From 1960a64b9a61e4b99e6a2a0d93eda198954d279e Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Fri, 26 Sep 2025 12:52:12 +0200 Subject: [PATCH 05/17] add changed color when hover btn-sort --- script.js | 71 ++++++++++++++++++++++++++++++++++++++++++------------- style.css | 63 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 101 insertions(+), 33 deletions(-) diff --git a/script.js b/script.js index 5e7bc4d4b..5e29d3ebc 100644 --- a/script.js +++ b/script.js @@ -1,11 +1,10 @@ -console.log("Hello js") - -//Get element +// Get elements from HTML const filterButtons = document.querySelectorAll(".btn-filter") const sortButtons = document.querySelectorAll(".btn-sort") const randomButtons = document.getElementById("randomBtn") const userSelection = document.getElementById("userSelection") -//Messages for kitchen filter + +// Messages for kitchen const filterMessages = { italian: "How about spaghetti carbonara?", thai: "Maybe some Pad Thai?", @@ -18,6 +17,8 @@ const sortMessages = { ascending: "Are you in a hurry?", descending: "Nice, let's take it chill" } + +//Messages for surprise me const surpriseMessages = [ "🍣 Sushi bowl", "🍝 Creamy chicken pasta", @@ -30,60 +31,81 @@ const surpriseMessages = [ "🍫 Chocolate lava cake" ] -let selectedFilters = [] -let selectedSort = null +let selectedFilters = [] // All chosen filter-btns +let selectedSort = null // Chosen sort-method + const updateUserSelection = () => { + console.log("Updating user selection") let messages = [] + // Add message for chosen filter if (selectedFilters.length > 0) { selectedFilters.forEach(f => { if (filterMessages[f]) messages.push(filterMessages[f]) }) } + // Add message for chosen sort if (selectedSort) { messages.push(sortMessages[selectedSort]) } + // Show message or empty the text if nothing is chosen if (messages.length === 0) { userSelection.textContent = "" + } else { userSelection.innerHTML = messages.join("
") } + + console.log("Current messages:", messages) + console.log("Selected filters:", selectedFilters) + console.log("Selected sort:", selectedSort) } + +//Eventlistener filterButtons.forEach(button => { button.addEventListener("click", () => { const value = button.dataset.value + //If "All" is chosen -> clear everything else if (value === "all") { - filterButtons.forEach(btn => btn.classList.remove("selected")) + console.log("All button clicked = clear filters") + filterButtons.forEach(btn => btn.classList.remove("selected")) button.classList.add("selected") selectedFilters = [] - userSelection.textContent = "You like everything, maybe try choosing the surprise me button?" - console.log(userSelection.textContent) + randomButtons.classList.remove("selected") + userSelection.textContent = "You seem to like everything — why not try the 'Surprise Me' button?" + + console.log("Selected filters cleared") + console.log("Random button deselected") + return } else { + //Remove "All" if any other filter is chosen const allButton = document.querySelector('.btn-filter[data-value="all"]') allButton.classList.remove("selected") button.classList.toggle("selected") - selectedFilters = Array.from(filterButtons) - .filter(btn => btn.classList.contains("selected")) - .map(btn => btn.dataset.value) + // Maintain order of clicks + if (button.classList.contains("selected")) { - if (selectedFilters.length === 0) { - userSelection.textContent = "" + // Add filter if not chosen + if (selectedFilters.includes(value) === false) { + selectedFilters.push(value); + } } else { - const messages = selectedFilters.map(f => filterMessages[f]).filter(Boolean) - - userSelection.innerHTML = messages.join("
") + // Remove filter when unclicked + selectedFilters = selectedFilters.filter(f => f !== value); } + + console.log("Updated selected filters:", selectedFilters) } updateUserSelection() }) @@ -91,9 +113,13 @@ filterButtons.forEach(button => { sortButtons.forEach(button => { button.addEventListener("click", () => { + console.log("Sort button clicked:", button.dataset.value) + if (selectedSort === button.dataset.value) { selectedSort = null button.classList.remove("selected") + console.log("Sort cleared") + } else { selectedSort = button.dataset.value sortButtons.forEach(btn => btn.classList.remove("selected")) @@ -104,8 +130,19 @@ sortButtons.forEach(button => { }) randomButtons.addEventListener("click", () => { + randomButtons.classList.toggle("selected") + + // Deselect all kitchen filters + filterButtons.forEach(btn => btn.classList.remove("selected")) + + //Clear internal filter selection + selectedFilters = [] + const randomIndex = Math.floor(Math.random() * surpriseMessages.length) const randomMessage = surpriseMessages[randomIndex] + + console.log("Random selection:", randomMessage) + userSelection.innerHTML = `🎉 Surprise! How about: ${randomMessage}?` }) diff --git a/style.css b/style.css index 2d3c8bf1e..6cd70ff6c 100644 --- a/style.css +++ b/style.css @@ -25,6 +25,10 @@ h2 { font-size: 20px; } +/* =========================================== + Options container + =========================================== */ + .options-container { display: flex; flex-direction: column; @@ -42,6 +46,7 @@ h2 { gap: 12px; } +/* Kitchen filter buttons */ .btn-filter { background-color: #CCFFE2; color: #0018A4; @@ -60,6 +65,12 @@ h2 { transform: scale(1.05); } +.btn-filter.selected { + background-color: #0018A4; + color: white; +} + +/* Sort filter buttons */ .btn-sort { background-color: #FFECEA; color: #0018A4; @@ -73,12 +84,20 @@ h2 { } .btn-sort:hover { + background-color: #FF6589; + color: white; border: 2px solid #0018A4; cursor: pointer; transform: scale(1.05); } -/* Surprise me */ +.btn-sort.selected { + background-color: #FF6589; + color: white; + border: 2px solid #FF6589; +} + +/* Surprise me button */ .btn-random { background-image: linear-gradient(135deg, #ae88ff 0%, #7f9fff 51%, #aa88ff 100%); color: yellow; @@ -88,33 +107,24 @@ h2 { border-radius: 50px; margin-bottom: 12px; border: 2px solid transparent; - border: none; cursor: pointer; transition: all 0.2s ease; } .btn-random:hover { transform: scale(1.05); -} - -.btn-filter.selected { - background-color: #0018A4; - color: white; -} - -.btn-sort.selected { - background-color: #FF6589; - color: white; - border: none; + border: 2px solid #0018A4; } .btn-random.selected { background-image: none; - background-color: #FFD700; - color: #0018A4; + background-color: #6a529c; + color: white; } -/* Recipe-container */ +/* =========================================== + Recipe container + =========================================== */ .recipes-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); @@ -123,6 +133,7 @@ h2 { margin-top: 40px; } +/* Recipe cards */ .recipe-card { border: 2px solid #e9e9e9; background: white; @@ -161,6 +172,9 @@ hr.solid { border-radius: 12px; } +/* =========================================== + Media queries + =========================================== */ @media (min-width: 668px) { h1 { font-size: 64px; @@ -188,7 +202,24 @@ hr.solid { max-width: 800px; } + .placeholder-output { + font-size: 20px; + } + .recipe-card { gap: 16px; } + + .recipe-card h2 { + margin: 2px 0; + } + + .recipe-card p { + font-size: 18px; + margin: 0; + } + + hr.solid { + margin: 0; + } } \ No newline at end of file From bc66d5bb25b15d65f95ac8c94803236b57ea7808 Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Wed, 1 Oct 2025 17:14:46 +0200 Subject: [PATCH 06/17] add recipes in js and favorite-section --- .vscode/settings.json | 3 + index.html | 58 +++++----- script.js | 256 ++++++++++++++++++++++++++++++++---------- style.css | 82 +++++++++++++- 4 files changed, 307 insertions(+), 92 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..cbb7b3af2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5502 +} \ No newline at end of file diff --git a/index.html b/index.html index 0fed15280..d7418d796 100644 --- a/index.html +++ b/index.html @@ -21,6 +21,30 @@

Recipe Library

+ +
+ +
@@ -79,39 +103,11 @@

Need inspiration?

+
-
-
-

Temporary placeholder for filters/sorting options

-
-
-
- Pizza -

Temporary placeholder recipe

-
-

Cuisine: Italian
- Time: 40 minutes -

-
-

Ingredients
- 500 g pack bread mix
- 2 tbsp. olive oil, plus a little extra for
- drizzling
- 25 g parmesan (or vegetarian alternative), grated
- 75 g dolcelatte cheese (or vegetarian alternative) -

-
+
+
diff --git a/script.js b/script.js index 5e29d3ebc..9fd8aef5a 100644 --- a/script.js +++ b/script.js @@ -2,83 +2,216 @@ const filterButtons = document.querySelectorAll(".btn-filter") const sortButtons = document.querySelectorAll(".btn-sort") const randomButtons = document.getElementById("randomBtn") -const userSelection = document.getElementById("userSelection") - -// Messages for kitchen -const filterMessages = { - italian: "How about spaghetti carbonara?", - thai: "Maybe some Pad Thai?", - mexican: "Up for spicy?", - korean: "Cold noodles are popular on TikTok" -} +const recipesContainer = document.getElementById("recipeContainer") +const favBtn = document.getElementById("favBtn") + +// global state variabler +let selectedFilters = [] // All chosen filter-btns +let selectedSort = null // Chosen sort-method +let showFavoritesOnly = false + +// Data - recipes array +const recipes = [ + { + id: 1, + title: "Pesto Pasta", + image: "./pasta.jpg", + readyInMinutes: 25, + sourceUrl: "https://example.com/pesto-pasta", + cuisine: "Italian", + ingredients: [ + "pasta", + "basil", + "parmesan cheese", + "garlic", + "pine nuts", + "olive oil", + "salt", + "black pepper" + ], + isFavorite: false + }, + { + id: 2, + title: "Green Curry with Tofu", + image: "./thai-curry.jpg", + readyInMinutes: 40, + sourceUrl: "https://example.com/green-curry-tofu", + cuisine: "Thai", + ingredients: [ + "tofu", + "green curry paste", + "coconut milk", + "bamboo shoots", + "bell peppers", + "basil", + "lime leaves", + "rice", + "salt" + ], + isFavorite: false + }, + { + id: 3, + title: "Chicken Tacos", + image: "./mexican-tacos.jpg", + readyInMinutes: 20, + sourceUrl: "https://example.com/chicken-tacos", + cuisine: "Mexican", + ingredients: [ + "corn tortillas", + "chicken breast", + "taco seasoning", + "lettuce", + "tomato", + "avocado", + "cheddar cheese" + ], + isFavorite: false + }, + { + id: 4, + title: "Bibimbap", + image: "./korean-bibimbap.jpg", + readyInMinutes: 35, + sourceUrl: "https://example.com/bibimbap", + cuisine: "Korean", + ingredients: [ + "rice", + "spinach", + "carrots", + "bean sprouts", + "shiitake mushrooms", + "egg", + "gochujang", + "sesame oil" + ], + isFavorite: false + } +] -// Messages for sort -const sortMessages = { - ascending: "Are you in a hurry?", - descending: "Nice, let's take it chill" +const showRecipes = (recipesArray) => { + recipesContainer.innerHTML = "" + + if (recipesArray.length === 0) { + recipesContainer.innerHTML = `

😢 No recipes match your filter, try choosing another one

` + return + } + + recipesArray.forEach(recipe => { + recipesContainer.innerHTML += ` +
+

${recipe.title}

+ ${recipe.title} +
+

Cuisine: ${recipe.cuisine}
+ Time: ${recipe.readyInMinutes} minutes
+

+
+

Ingredients
+ ${recipe.ingredients.join("
")} +

+ +
+` + }) + + addFavoriteListeners() } -//Messages for surprise me -const surpriseMessages = [ - "🍣 Sushi bowl", - "🍝 Creamy chicken pasta", - "🌮 Beef tacos", - "🍛 Vegan curry", - "🥪 BBQ pulled pork sandwich", - "🥚 Shakshuka", - "🥗 Greek salad", - "🍜 Ramen noodles", - "🍫 Chocolate lava cake" -] -let selectedFilters = [] // All chosen filter-btns -let selectedSort = null // Chosen sort-method +const addFavoriteListeners = () => { + const favoriteButtons = document.querySelectorAll(".btn-fav") + favoriteButtons.forEach(button => { + button.addEventListener("click", (event) => { + event.stopPropagation() -const updateUserSelection = () => { - console.log("Updating user selection") - let messages = [] - // Add message for chosen filter - if (selectedFilters.length > 0) { - selectedFilters.forEach(f => { - if (filterMessages[f]) messages.push(filterMessages[f]) + const recipeId = parseInt(button.dataset.id) + const recipe = recipes.find(r => r.id === recipeId) + + if (recipe.isFavorite === false) { + recipe.isFavorite = true + button.classList.add("active") + } else { + recipe.isFavorite = false + button.classList.remove("active") + } + if (showFavoritesOnly === true) { + updateRecipes() + } }) + }) +} + +const updateRecipes = () => { + let filteredRecipes = recipes + + if (selectedFilters.length > 0) { + filteredRecipes = filteredRecipes.filter(recipe => + selectedFilters.includes(recipe.cuisine.toLowerCase()) + ) + console.log("Filtered recipes:", filteredRecipes) } - // Add message for chosen sort - if (selectedSort) { - messages.push(sortMessages[selectedSort]) + if (showFavoritesOnly === true) { + filteredRecipes = filteredRecipes.filter(recipe => recipe.isFavorite === true) } - // Show message or empty the text if nothing is chosen - if (messages.length === 0) { - userSelection.textContent = "" + filteredRecipes = sortRecipes(filteredRecipes) + + showRecipes(filteredRecipes) +} - } else { - userSelection.innerHTML = messages.join("
") - } - console.log("Current messages:", messages) - console.log("Selected filters:", selectedFilters) - console.log("Selected sort:", selectedSort) +const sortRecipes = (recipesArray) => { + if (selectedSort === "ascending") { + return recipesArray.sort((a, b) => a.readyInMinutes - b.readyInMinutes) + } + if (selectedSort === "descending") { + return recipesArray.sort((a, b) => b.readyInMinutes - a.readyInMinutes) + } + return recipesArray } + //Eventlistener +favBtn.addEventListener("click", () => { + if (showFavoritesOnly === false) { + showFavoritesOnly = true + favBtn.classList.add("active") + } else { + showFavoritesOnly = false + favBtn.classList.remove("active") + } + updateRecipes() +}) + + + filterButtons.forEach(button => { button.addEventListener("click", () => { const value = button.dataset.value //If "All" is chosen -> clear everything else if (value === "all") { - console.log("All button clicked = clear filters") - + selectedFilters = [] filterButtons.forEach(btn => btn.classList.remove("selected")) button.classList.add("selected") - selectedFilters = [] randomButtons.classList.remove("selected") - userSelection.textContent = "You seem to like everything — why not try the 'Surprise Me' button?" + updateRecipes() + console.log("Selected filters cleared") console.log("Random button deselected") @@ -95,7 +228,7 @@ filterButtons.forEach(button => { // Maintain order of clicks if (button.classList.contains("selected")) { - // Add filter if not chosen + // Add filter if it's not already chosen if (selectedFilters.includes(value) === false) { selectedFilters.push(value); } @@ -107,7 +240,7 @@ filterButtons.forEach(button => { console.log("Updated selected filters:", selectedFilters) } - updateUserSelection() + updateRecipes() }) }) @@ -125,7 +258,12 @@ sortButtons.forEach(button => { sortButtons.forEach(btn => btn.classList.remove("selected")) button.classList.add("selected") } - updateUserSelection() + + let filteredRecipes = recipes + if (selectedFilters.length > 0) { + filteredRecipes = recipes.filter(recipe => selectedFilters.includes(recipe.cuisine.toLocaleLowerCase())) + } + updateRecipes() }) }) @@ -134,18 +272,20 @@ randomButtons.addEventListener("click", () => { // Deselect all kitchen filters filterButtons.forEach(btn => btn.classList.remove("selected")) + sortButtons.forEach(btn => btn.classList.remove("selected")) //Clear internal filter selection selectedFilters = [] + selectedSort = null - const randomIndex = Math.floor(Math.random() * surpriseMessages.length) - const randomMessage = surpriseMessages[randomIndex] - - console.log("Random selection:", randomMessage) + const randomRecipe = recipes[Math.floor(Math.random() * recipes.length)] - userSelection.innerHTML = `🎉 Surprise! How about: ${randomMessage}?` + showRecipes([randomRecipe]) }) +updateRecipes() + + diff --git a/style.css b/style.css index 6cd70ff6c..cfbb11a5a 100644 --- a/style.css +++ b/style.css @@ -7,6 +7,49 @@ body { padding: 0 14px; } +header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + background-color: #fff; +} + +.favorites { + display: flex; + align-items: center; +} + +.btn-favorites { + display: flex; + align-items: center; + gap: 6px; + background: none; + border: none; + cursor: pointer; + font-size: 1rem; +} + +.btn-favorites svg { + vertical-align: middle; +} + +.heart-icon path { + fill: none; + stroke: black; + stroke-width: 2; + transition: fill 0.3s ease; +} + +.btn-favorites.active .heart-icon path { + fill: red; + stroke: red; +} + +.btn-favorites span { + display: none; +} + header, .options-container, .recipes-container { @@ -125,9 +168,9 @@ h2 { /* =========================================== Recipe container =========================================== */ -.recipes-container { +#recipeContainer { display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); justify-content: center; gap: 20px; margin-top: 40px; @@ -141,6 +184,7 @@ h2 { flex-direction: column; justify-content: flex-start; align-items: flex-start; + position: relative; padding: 12px; border-radius: 16px; transition: all 0.2s ease; @@ -151,6 +195,33 @@ h2 { box-shadow: 0px 0px 30px 0px rgba(0, 24, 164, 0.2); } +.btn-fav { + background: none; + border: none; + cursor: pointer; + padding: 0; + margin: 0; + position: absolute; + bottom: 12px; + right: 12px; +} + +.btn-fav svg path { + fill: none; + stroke: black; + stroke-width: 2; + transition: fill 0.3s ease; +} + +.btn-fav:hover path { + fill: rgba(255, 0, 0, 0.3); +} + +.btn-fav.active svg path { + fill: red; + stroke: red; +} + hr.solid { border: none; border-top: 2px solid #E9E9E9; @@ -180,8 +251,13 @@ hr.solid { font-size: 64px; } + .btn-favorites span { + display: inline; + line-height: 1; + } + h2 { - font-size: 28px; + font-size: 26px; } header, From c10fede943b911b2c6afe04e8ffb5fb8b5ac820a Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Thu, 2 Oct 2025 14:23:55 +0200 Subject: [PATCH 07/17] add url and images to recipes --- index.html | 6 +++++- script.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++------- style.css | 29 ++++++++++++++++++++----- 3 files changed, 84 insertions(+), 14 deletions(-) diff --git a/index.html b/index.html index d7418d796..247a07f21 100644 --- a/index.html +++ b/index.html @@ -21,7 +21,7 @@

Recipe Library

- +
+
diff --git a/script.js b/script.js index 9fd8aef5a..43d4121d2 100644 --- a/script.js +++ b/script.js @@ -15,7 +15,7 @@ const recipes = [ { id: 1, title: "Pesto Pasta", - image: "./pasta.jpg", + image: "images/pizza.jpg", readyInMinutes: 25, sourceUrl: "https://example.com/pesto-pasta", cuisine: "Italian", @@ -34,7 +34,7 @@ const recipes = [ { id: 2, title: "Green Curry with Tofu", - image: "./thai-curry.jpg", + image: "images/pizza.jpg", readyInMinutes: 40, sourceUrl: "https://example.com/green-curry-tofu", cuisine: "Thai", @@ -54,7 +54,7 @@ const recipes = [ { id: 3, title: "Chicken Tacos", - image: "./mexican-tacos.jpg", + image: "images/pizza.jpg", readyInMinutes: 20, sourceUrl: "https://example.com/chicken-tacos", cuisine: "Mexican", @@ -72,7 +72,7 @@ const recipes = [ { id: 4, title: "Bibimbap", - image: "./korean-bibimbap.jpg", + image: "images/pizza.jpg", readyInMinutes: 35, sourceUrl: "https://example.com/bibimbap", cuisine: "Korean", @@ -87,6 +87,42 @@ const recipes = [ "sesame oil" ], isFavorite: false + }, + { + id: 5, + title: "Spaghetti Carbonara", + image: "images/pizza.jpg", + readyInMinutes: 25, + sourceUrl: "https://example.com/spaghetti-carbonara", + cuisine: "Italian", + ingredients: [ + "spaghetti", + "eggs", + "parmesan cheese", + "pancetta", + "black pepper", + "garlic" + ], + isFavorite: false + }, + { + id: 6, + title: "Chicken Tikka Masala", + image: "images/pizza.jpg", + readyInMinutes: 50, + sourceUrl: "https://example.com/chicken-tikka-masala", + cuisine: "Indian", + ingredients: [ + "chicken", + "yogurt", + "tomato puree", + "cream", + "garam masala", + "ginger", + "garlic", + "onion" + ], + isFavorite: false } ] @@ -100,9 +136,9 @@ const showRecipes = (recipesArray) => { recipesArray.forEach(recipe => { recipesContainer.innerHTML += ` -
+
+ ${recipe.title}

${recipe.title}

- ${recipe.title}

Cuisine: ${recipe.cuisine}
Time: ${recipe.readyInMinutes} minutes
@@ -123,9 +159,18 @@ const showRecipes = (recipesArray) => {

` + }) addFavoriteListeners() + + document.querySelectorAll(".recipe-card").forEach(card => { + card.addEventListener("click", (event) => { + if (event.target.closest(".btn-fav")) return + const url = card.dataset.url + if (url) window.open(url, "_blank") + }) + }) } @@ -136,7 +181,6 @@ const addFavoriteListeners = () => { button.addEventListener("click", (event) => { event.stopPropagation() - const recipeId = parseInt(button.dataset.id) const recipe = recipes.find(r => r.id === recipeId) @@ -169,7 +213,7 @@ const updateRecipes = () => { } filteredRecipes = sortRecipes(filteredRecipes) - + console.log("Sorted recipes:", filteredRecipes) showRecipes(filteredRecipes) } @@ -207,6 +251,9 @@ filterButtons.forEach(button => { //If "All" is chosen -> clear everything else if (value === "all") { selectedFilters = [] + selectedSort = null + showFavoritesOnly = false + favBtn.classList.remove("active") filterButtons.forEach(btn => btn.classList.remove("selected")) button.classList.add("selected") randomButtons.classList.remove("selected") diff --git a/style.css b/style.css index cfbb11a5a..cfb158e86 100644 --- a/style.css +++ b/style.css @@ -51,8 +51,7 @@ header { } header, -.options-container, -.recipes-container { +.options-container { margin: 0 auto; max-width: 1200px; } @@ -262,7 +261,7 @@ hr.solid { header, .options-container, - .recipes-container { + #recipeContainer { margin: 0; padding-left: 14px; } @@ -270,12 +269,14 @@ hr.solid { .options-container { flex-direction: row; align-items: flex-start; + justify-content: space-between; gap: 100px; margin-bottom: 40px; } - .recipes-container { - max-width: 800px; + #recipeContainer { + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 24px; } .placeholder-output { @@ -298,4 +299,22 @@ hr.solid { hr.solid { margin: 0; } +} + +@media (min-width: 1024px) { + /* #recipeContainer { + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + gap: 28px; + justify-content: center; + } */ + + @media (min-width: 1600px) { + header { + max-width: 1300px; + } + + .options-container { + max-width: 1400px; + } + } } \ No newline at end of file From 1fb668bbe2adf3cbef5b6ea14313b8ec9ae55b55 Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Fri, 3 Oct 2025 14:09:43 +0200 Subject: [PATCH 08/17] add headings and comments in the javascript --- index.html | 8 ++++++-- script.js | 6 +++--- style.css | 35 ++++++++++++----------------------- 3 files changed, 21 insertions(+), 28 deletions(-) diff --git a/index.html b/index.html index 247a07f21..98b9545e5 100644 --- a/index.html +++ b/index.html @@ -11,6 +11,10 @@ href="https://fonts.googleapis.com/css2?family=Jost:wght@400;500;700&display=swap" rel="stylesheet" > + Recipe Library Sort on time + >🚀 Shortest first + >⏳ Longest first
diff --git a/script.js b/script.js index 43d4121d2..630e57a24 100644 --- a/script.js +++ b/script.js @@ -5,7 +5,7 @@ const randomButtons = document.getElementById("randomBtn") const recipesContainer = document.getElementById("recipeContainer") const favBtn = document.getElementById("favBtn") -// global state variabler +// Global state variables let selectedFilters = [] // All chosen filter-btns let selectedSort = null // Chosen sort-method let showFavoritesOnly = false @@ -126,6 +126,7 @@ const recipes = [ } ] +// Functions const showRecipes = (recipesArray) => { recipesContainer.innerHTML = "" @@ -159,7 +160,6 @@ const showRecipes = (recipesArray) => { ` - }) addFavoriteListeners() @@ -243,7 +243,6 @@ favBtn.addEventListener("click", () => { }) - filterButtons.forEach(button => { button.addEventListener("click", () => { const value = button.dataset.value @@ -291,6 +290,7 @@ filterButtons.forEach(button => { }) }) + sortButtons.forEach(button => { button.addEventListener("click", () => { console.log("Sort button clicked:", button.dataset.value) diff --git a/style.css b/style.css index cfb158e86..3c3c81e85 100644 --- a/style.css +++ b/style.css @@ -4,14 +4,15 @@ body { font-family: "Jost", sans-serif; - padding: 0 14px; + padding: 0; + margin: 0; } header { display: flex; justify-content: space-between; align-items: center; - padding: 16px; + padding: 16px 14px; background-color: #fff; } @@ -53,7 +54,8 @@ header { header, .options-container { margin: 0 auto; - max-width: 1200px; + padding-left: 14px; + max-width: 100%; } h1 { @@ -74,6 +76,7 @@ h2 { .options-container { display: flex; flex-direction: column; + padding: 0 14px; gap: 20px; margin: 0 auto; } @@ -169,8 +172,9 @@ h2 { =========================================== */ #recipeContainer { display: grid; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); justify-content: center; + padding: 0 14px; gap: 20px; margin-top: 40px; } @@ -274,11 +278,6 @@ hr.solid { margin-bottom: 40px; } - #recipeContainer { - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 24px; - } - .placeholder-output { font-size: 20px; } @@ -301,20 +300,10 @@ hr.solid { } } -@media (min-width: 1024px) { - /* #recipeContainer { - grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); - gap: 28px; - justify-content: center; - } */ - - @media (min-width: 1600px) { - header { - max-width: 1300px; - } +@media (min-width: 1600px) { - .options-container { - max-width: 1400px; - } + header, + .options-container { + max-width: 1400px; } } \ No newline at end of file From 015574b9d7c5b4f7bddb6c6bafe974785247ea90 Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Wed, 8 Oct 2025 17:05:47 +0200 Subject: [PATCH 09/17] add API for recipes --- index.html | 21 ++-- script.js | 284 ++++++++++++++++++++++------------------------------- style.css | 28 ++++++ 3 files changed, 162 insertions(+), 171 deletions(-) diff --git a/index.html b/index.html index 98b9545e5..90176b4de 100644 --- a/index.html +++ b/index.html @@ -60,10 +60,6 @@

Filter on kitchen

class="btn-filter" data-value="all" >All - + data-value="mediterranean" + >Mediterranean + + Favorites + + + +
+ + + +
diff --git a/script.js b/script.js index 4ac5efe3a..4f2a5f5fd 100644 --- a/script.js +++ b/script.js @@ -1,17 +1,20 @@ // Get elements from HTML +const favBtn = document.getElementById("favBtn") +const searchInput = document.getElementById("searchInput") +const searchBtn = document.getElementById("searchBtn") +const clearSearchBtn = document.getElementById("clearSearchBtn") const filterButtons = document.querySelectorAll(".btn-filter") const sortButtons = document.querySelectorAll(".btn-sort") const randomButton = document.getElementById("randomBtn") const recipesContainer = document.getElementById("recipeContainer") -const favBtn = document.getElementById("favBtn") const API_KEY = "dd6e45be84ea4b5ca75f926ee451806c" -const URL = `https://api.spoonacular.com/recipes/complexSearch?number=30&apiKey=${API_KEY}&cuisine=Thai,Mexican,Mediterranean,Indian&addRecipeInformation=true&addRecipeInstructions=true&fillIngredients=true` +const URL = `https://api.spoonacular.com/recipes/complexSearch?number=25&apiKey=${API_KEY}&cuisine=Thai,Mexican,Mediterranean,Indian&addRecipeInformation=true&addRecipeInstructions=true&fillIngredients=true` const loadingIndicator = document.getElementById("loading") // Global state variables -let selectedFilters = [] // All chosen filter-btns -let selectedSort = null // Chosen sort-method +let selectedFilters = [] +let selectedSort = null let showFavoritesOnly = false let allRecipes = [] @@ -27,12 +30,10 @@ const fetchData = async () => { if (cachedRecipes) { try { allRecipes = JSON.parse(cachedRecipes) - console.log("Loaded recipes from cache") updateRecipes() loadingIndicator.style.display = "none" return } catch (e) { - console.warn("Could not parse cached recipes", e) localStorage.removeItem("allRecipes") localStorage.removeItem("lastFetch") } @@ -40,15 +41,11 @@ const fetchData = async () => { } loadingIndicator.style.display = "block" try { - console.log("Fetching recipes from API...") const response = await fetch(URL) if (!response.ok) throw new Error(`HTTP ${response.status}`) const data = await response.json() - console.log("API response data:", data) allRecipes = data.results.map(recipe => { - console.log("Processing recipe:", recipe) - let ingredients = [] if (recipe.extendedIngredients && recipe.extendedIngredients.length > 0) { @@ -64,9 +61,6 @@ const fetchData = async () => { ) || [] ) } - - console.log("Extracted ingredients:", ingredients) - return { id: recipe.id, title: recipe.title, @@ -90,10 +84,8 @@ const fetchData = async () => { if (cachedRecipes) { try { allRecipes = JSON.parse(cachedRecipes) - console.log("Loaded recipes from cache due to error") updateRecipes() } catch (e) { - console.error("Could not parse cached recipes", e) recipesContainer.innerHTML = "Could not load recipes 😢" localStorage.removeItem("allRecipes") localStorage.removeItem("lastFetch") @@ -101,7 +93,6 @@ const fetchData = async () => { } else { recipesContainer.innerHTML = "Could not load recipes 😢" } - console.error(err) } finally { loadingIndicator.style.display = "none" } @@ -109,41 +100,49 @@ const fetchData = async () => { // Functions const showRecipes = (recipesArray) => { - recipesContainer.innerHTML = "" + const recipesContainer = document.getElementById("recipeContainer") + recipesContainer.style.opacity = "0" + recipesContainer.style.transform = "scale(0.95)" - if (recipesArray.length === 0) { - recipesContainer.innerHTML = `

😢 No recipes match your filter, try choosing another one

` - return - } + setTimeout(() => { + recipesContainer.innerHTML = "" - recipesArray.forEach(recipe => { - recipesContainer.innerHTML += ` -
- ${recipe.title} -

${recipe.title}

-
-

Cuisine: ${recipe.cuisine}
- Time: ${recipe.readyInMinutes} minutes
-

-
-

Ingredients
- ${recipe.ingredients.join("
")} -

- -
-` - }) + if (recipesArray.length === 0) { + recipesContainer.innerHTML = `

😢 No recipes match your filter, try choosing another one

` + } else { + recipesArray.forEach(recipe => { + recipesContainer.innerHTML += ` +
+ ${recipe.title} +

${recipe.title}

+
+

Cuisine: ${recipe.cuisine}
+ Time: ${recipe.readyInMinutes} minutes

+
+

Ingredients
+ ${recipe.ingredients.join("
")}

+ +
+ ` + }) + } + + // Fade in igen efter att nytt innehåll är inlagt + recipesContainer.style.opacity = "1" + recipesContainer.style.transform = "scale(1)" + }, 150) } + const updateRecipes = () => { let filteredRecipes = [...allRecipes] @@ -151,7 +150,6 @@ const updateRecipes = () => { filteredRecipes = filteredRecipes.filter(recipe => selectedFilters.includes(recipe.cuisine.toLowerCase()) ) - console.log("Filtered recipes:", filteredRecipes) } if (showFavoritesOnly === true) { @@ -159,7 +157,6 @@ const updateRecipes = () => { } filteredRecipes = sortRecipes(filteredRecipes) - console.log("Sorted recipes:", filteredRecipes) showRecipes(filteredRecipes) } @@ -188,6 +185,42 @@ favBtn.addEventListener("click", () => { updateRecipes() }) +searchBtn.addEventListener("click", () => { + searchInput.classList.toggle("active") + searchInput.value = "" + clearSearchBtn.classList.remove("active") + searchInput.focus() +}) + +clearSearchBtn.addEventListener("click", () => { + searchInput.value = "" + clearSearchBtn.classList.remove("active") + if (window.innerWidth < 668) { + searchInput.classList.remove("active") + } + updateRecipes() +}) + +searchInput.addEventListener("input", () => { + const query = searchInput.value.toLowerCase().trim() + + if (query !== "") { + clearSearchBtn.classList.add("active") + } else { + clearSearchBtn.classList.remove("active") + if (window.innerWidth < 668) { + searchInput.classList.remove("active") + } + updateRecipes() + return + } + + const searchedRecipes = allRecipes.filter(recipe => + recipe.title.toLowerCase().includes(query) || + recipe.ingredients.join(", ").toLowerCase().includes(query) + ) + showRecipes(searchedRecipes); +}) filterButtons.forEach(button => { button.addEventListener("click", () => { @@ -203,11 +236,6 @@ filterButtons.forEach(button => { button.classList.add("selected") randomButton.classList.remove("selected") updateRecipes() - - - console.log("Selected filters cleared") - console.log("Random button deselected") - return } else { @@ -229,8 +257,6 @@ filterButtons.forEach(button => { // Remove filter when unclicked selectedFilters = selectedFilters.filter(f => f !== value); } - - console.log("Updated selected filters:", selectedFilters) } updateRecipes() }) @@ -239,12 +265,9 @@ filterButtons.forEach(button => { sortButtons.forEach(button => { button.addEventListener("click", () => { - console.log("Sort button clicked:", button.dataset.value) - if (selectedSort === button.dataset.value) { selectedSort = null button.classList.remove("selected") - console.log("Sort cleared") } else { selectedSort = button.dataset.value diff --git a/style.css b/style.css index 176ba6084..c1d1dcd4d 100644 --- a/style.css +++ b/style.css @@ -8,12 +8,28 @@ body { margin: 0; } +h1 { + color: #0018A4; + font-weight: 700; + font-size: 34px; +} + header { display: flex; - justify-content: space-between; - align-items: center; + flex-direction: column; + align-items: flex-start; padding: 16px 14px; background-color: #fff; + gap: 10px; + width: 100%; +} + +.header-actions { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; + width: 100%; } .favorites { @@ -51,17 +67,57 @@ header { display: none; } -header, -.options-container { - margin: 0 auto; - padding-left: 14px; - max-width: 100%; +.search-container { + display: flex; + align-items: center; + border: 1px solid #ccc; + border-radius: 50px; + padding: 5px; + background: #fff; + transition: all 0.3s ease; + overflow: hidden; + flex-shrink: 1; + min-width: 0; + max-width: 50%; } -h1 { - color: #0018A4; - font-weight: 700; - font-size: 40px; +#searchInput { + border: none; + outline: none; + padding: 5px; + font-size: 12px; + flex: 1; + display: none; + width: 0; + opacity: 0; + transition: width 0.3s ease, opacity 0.3s ease; +} + +#searchInput.active { + display: block; + width: 100%; + opacity: 1; + max-width: 300px; +} + +.btn-search { + background: none; + border: none; + cursor: pointer; + font-size: 18px; +} + +.btn-clear-search { + display: none; + background: none; + border: none; + cursor: pointer; + font-size: 18px; + margin-left: 4px; +} + +.btn-clear-search.active { + display: block; } h2 { @@ -76,9 +132,10 @@ h2 { .options-container { display: flex; flex-direction: column; - padding: 0 14px; + padding: 10px 14px; gap: 20px; margin: 0 auto; + max-width: 100%; } .filter-buttons, @@ -174,9 +231,10 @@ h2 { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); justify-content: center; - padding: 0 14px; + padding: 12px 14px; gap: 20px; margin-top: 40px; + transition: grid-template-columns 0.3s ease, opacity 0.3s ease, transform 0.3s ease; } /* Recipe cards */ @@ -190,7 +248,7 @@ h2 { position: relative; padding: 12px; border-radius: 16px; - transition: all 0.2s ease; + transition: all 0.3s ease-in-out; } .recipe-card:hover { @@ -278,8 +336,25 @@ hr.solid { Media queries =========================================== */ @media (min-width: 668px) { + header { + flex-direction: row; + justify-content: space-between; + align-items: center; + } + h1 { - font-size: 64px; + font-size: 40px; + } + + .header-actions { + flex-wrap: nowrap; + flex: 1; + justify-content: flex-end; + gap: 20px; + } + + .search-container { + width: auto; } .btn-favorites span { @@ -287,6 +362,17 @@ hr.solid { line-height: 1; } + #searchInput { + display: block; + font-size: 16px; + width: 200px; + opacity: 1; + } + + #searchBtn { + display: none; + } + h2 { font-size: 26px; } @@ -295,7 +381,6 @@ hr.solid { .options-container, #recipeContainer { margin: 0; - padding-left: 14px; } .options-container { @@ -306,6 +391,10 @@ hr.solid { margin-bottom: 40px; } + #recipeContainer { + padding: 18px; + } + .placeholder-output { font-size: 20px; } @@ -315,6 +404,7 @@ hr.solid { } .recipe-card h2 { + font-size: 24px; margin: 2px 0; } @@ -328,10 +418,42 @@ hr.solid { } } + +@media (min-width: 1200px) { + h1 { + font-size: 64px; + } + + h2 { + font-size: 34px; + } + + #recipeContainer { + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + } +} + @media (min-width: 1600px) { header, .options-container { max-width: 1400px; + padding: 0 18px; + } + + #recipeContainer { + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + } +} + +@media (min-width: 2000px) { + + header, + .options-container { + max-width: 1800px; + } + + #recipeContainer { + grid-template-columns: repeat(auto-fill, minmax(460px, 1fr)); } } \ No newline at end of file From 76c6bc25769985d93f77b32793ae49f195befbf4 Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Thu, 9 Oct 2025 20:17:13 +0200 Subject: [PATCH 12/17] add description to the code --- index.html | 23 ++++++++++------------- script.js | 9 ++++++--- style.css | 17 +++++++++-------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/index.html b/index.html index 2242ff4dc..49deae7d9 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,7 @@ name="viewport" content="width=device-width, initial-scale=1.0" > + Recipe Library - Recipe Library Need inspiration?
-
- +
+
+

Loading...

+
-
+
-
-
+
diff --git a/script.js b/script.js index 4f2a5f5fd..f8c48773b 100644 --- a/script.js +++ b/script.js @@ -69,7 +69,7 @@ const fetchData = async () => { readyInMinutes: recipe.readyInMinutes, image: recipe.image, sourceUrl: recipe.sourceUrl, - ingredients: [...new Set(ingredients)], // ta bort dubbletter + ingredients: [...new Set(ingredients)], // removes duplicates isFavorite: false } }) @@ -136,7 +136,7 @@ const showRecipes = (recipesArray) => { }) } - // Fade in igen efter att nytt innehåll är inlagt + // Fade in again after new content is added recipesContainer.style.opacity = "1" recipesContainer.style.transform = "scale(1)" }, 150) @@ -185,6 +185,7 @@ favBtn.addEventListener("click", () => { updateRecipes() }) + searchBtn.addEventListener("click", () => { searchInput.classList.toggle("active") searchInput.value = "" @@ -192,6 +193,7 @@ searchBtn.addEventListener("click", () => { searchInput.focus() }) + clearSearchBtn.addEventListener("click", () => { searchInput.value = "" clearSearchBtn.classList.remove("active") @@ -201,6 +203,7 @@ clearSearchBtn.addEventListener("click", () => { updateRecipes() }) + searchInput.addEventListener("input", () => { const query = searchInput.value.toLowerCase().trim() @@ -222,6 +225,7 @@ searchInput.addEventListener("input", () => { showRecipes(searchedRecipes); }) + filterButtons.forEach(button => { button.addEventListener("click", () => { const value = button.dataset.value @@ -317,7 +321,6 @@ recipesContainer.addEventListener("click", (event) => { } }) -//Initial fetch fetchData() diff --git a/style.css b/style.css index c1d1dcd4d..d63f759cc 100644 --- a/style.css +++ b/style.css @@ -8,6 +8,9 @@ body { margin: 0; } +/* =========================================== + Header + =========================================== */ h1 { color: #0018A4; font-weight: 700; @@ -32,6 +35,7 @@ header { width: 100%; } +/* Favorite recipes and btn */ .favorites { display: flex; align-items: center; @@ -67,6 +71,7 @@ header { display: none; } +/* Search field */ .search-container { display: flex; align-items: center; @@ -120,15 +125,14 @@ header { display: block; } +/* =========================================== + Options container + =========================================== */ h2 { font-weight: 700; font-size: 20px; } -/* =========================================== - Options container - =========================================== */ - .options-container { display: flex; flex-direction: column; @@ -304,6 +308,7 @@ hr.solid { border-radius: 12px; } +/* Loading for recipes */ #loading { display: none; text-align: center; @@ -369,10 +374,6 @@ hr.solid { opacity: 1; } - #searchBtn { - display: none; - } - h2 { font-size: 26px; } From e86d135b25ca6d5f32f58a4c22c88b7b71286e1e Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Thu, 9 Oct 2025 20:26:43 +0200 Subject: [PATCH 13/17] add Netlify link to readme file --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 58f1a8a66..446329f50 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ # js-project-recipe-library + +A responsive recipe library built with HTML, CSS, and JavaScript. + +## 🌐 Live Demo +Check out the deployed version here: +👉 [Recipe Library on Netlify](https://recipelibrary-app.netlify.app) From ab1d718c9f046fa95b52a237d6f48add9402ad8b Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Thu, 9 Oct 2025 21:23:04 +0200 Subject: [PATCH 14/17] add padding in media query --- style.css | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/style.css b/style.css index d63f759cc..bb333be49 100644 --- a/style.css +++ b/style.css @@ -349,6 +349,7 @@ hr.solid { h1 { font-size: 40px; + padding-left: 20px; } .header-actions { @@ -390,10 +391,11 @@ hr.solid { justify-content: space-between; gap: 100px; margin-bottom: 40px; + padding-left: 40px; } #recipeContainer { - padding: 18px; + padding: 0 40px 40px 40px; } .placeholder-output { @@ -439,7 +441,6 @@ hr.solid { header, .options-container { max-width: 1400px; - padding: 0 18px; } #recipeContainer { From b8ae90a0c76f071e28dbfcc5a387c8c61a36c0fe Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Fri, 10 Oct 2025 12:01:52 +0200 Subject: [PATCH 15/17] add comments and headings --- script.js | 133 ++++++++++++++++++++++++++++++------------------------ style.css | 6 +-- 2 files changed, 77 insertions(+), 62 deletions(-) diff --git a/script.js b/script.js index f8c48773b..771cb7f15 100644 --- a/script.js +++ b/script.js @@ -1,4 +1,4 @@ -// Get elements from HTML +// ---------- GLOBAL ELEMENT REFERENCES ---------- const favBtn = document.getElementById("favBtn") const searchInput = document.getElementById("searchInput") const searchBtn = document.getElementById("searchBtn") @@ -7,18 +7,43 @@ const filterButtons = document.querySelectorAll(".btn-filter") const sortButtons = document.querySelectorAll(".btn-sort") const randomButton = document.getElementById("randomBtn") const recipesContainer = document.getElementById("recipeContainer") +const loadingIndicator = document.getElementById("loading") + const API_KEY = "dd6e45be84ea4b5ca75f926ee451806c" const URL = `https://api.spoonacular.com/recipes/complexSearch?number=25&apiKey=${API_KEY}&cuisine=Thai,Mexican,Mediterranean,Indian&addRecipeInformation=true&addRecipeInstructions=true&fillIngredients=true` -const loadingIndicator = document.getElementById("loading") -// Global state variables +// ---------- GLOBAL STATE ---------- let selectedFilters = [] let selectedSort = null let showFavoritesOnly = false let allRecipes = [] +// ---------- UTILITY FUNCTIONS ---------- +const resetFilters = () => { + selectedFilters = [] + selectedSort = null + showFavoritesOnly = false + favBtn.classList.remove("active") + filterButtons.forEach(btn => btn.classList.remove("selected")) + sortButtons.forEach(btn => btn.classList.remove("selected")) + randomButton.classList.remove("selected") +} + +// Helper to extract and clean ingredients +const extractIngredients = (recipe) => { + const fromExtended = recipe.extendedIngredients?.map(i => i.original.toLowerCase().trim()) || [] + const fromInstructions = recipe.analyzedInstructions?.flatMap(instr => + instr.steps?.flatMap(step => + step.ingredients?.map(i => i.name.toLowerCase().trim()) + ) + ) || [] + return [...new Set([...fromExtended, ...fromInstructions])] +} + + +// ---------- DATA FETCHING ---------- // Fetch data from API or localStorage const fetchData = async () => { const lastFetch = localStorage.getItem("lastFetch") @@ -30,67 +55,62 @@ const fetchData = async () => { if (cachedRecipes) { try { allRecipes = JSON.parse(cachedRecipes) + // Update the UI with cached recipes updateRecipes() loadingIndicator.style.display = "none" return } catch (e) { + // If cached data is corrupted → remove it to force fresh fetch localStorage.removeItem("allRecipes") localStorage.removeItem("lastFetch") } } } + // Show loading spinner loadingIndicator.style.display = "block" try { const response = await fetch(URL) + + //Check if API quota is reached + if (response.status === 402) { + recipesContainer.innerHTML = "

🚫 API daily quota reached. We've hit our daily request limit for recipe data. Please try again tomorrow or reload later

" + return + } if (!response.ok) throw new Error(`HTTP ${response.status}`) - const data = await response.json() - allRecipes = data.results.map(recipe => { - let ingredients = [] - - if (recipe.extendedIngredients && recipe.extendedIngredients.length > 0) { - ingredients = recipe.extendedIngredients.map(ing => - ing.original.toLowerCase().trim() // ← Makes all ingredients lowercase and trims whitespace - ) - } else if (recipe.analyzedInstructions && recipe.analyzedInstructions.length > 0) { - ingredients = recipe.analyzedInstructions.flatMap(instr => - instr.steps?.flatMap(step => - step.ingredients?.map(ing => - ing.name.toLowerCase().trim() // ← Makes all ingredient names lowercase and trims whitespace - ) || [] - ) || [] - ) - } - return { - id: recipe.id, - title: recipe.title, - cuisine: ((recipe.cuisines?.[0] || "Unknown").charAt(0).toUpperCase() + - (recipe.cuisines?.[0] || "Unknown").slice(1).toLowerCase()), - readyInMinutes: recipe.readyInMinutes, - image: recipe.image, - sourceUrl: recipe.sourceUrl, - ingredients: [...new Set(ingredients)], // removes duplicates - isFavorite: false - } - }) + const data = await response.json() + allRecipes = data.results.map(recipe => ({ + id: recipe.id, + title: recipe.title, + cuisine: ((recipe.cuisines?.[0] || "Unknown").charAt(0).toUpperCase() + + (recipe.cuisines?.[0] || "Unknown").slice(1).toLowerCase()), + readyInMinutes: recipe.readyInMinutes, + image: recipe.image, + sourceUrl: recipe.sourceUrl, + ingredients: extractIngredients(recipe), // Extract ingredients via helper function + isFavorite: false + })) + // Save recipes to localStorage localStorage.setItem("allRecipes", JSON.stringify(allRecipes)) localStorage.setItem("lastFetch", now.toString()) - updateRecipes() } catch (err) { + // Error handling: use cached data if API fetch fails const cachedRecipes = localStorage.getItem("allRecipes") if (cachedRecipes) { try { allRecipes = JSON.parse(cachedRecipes) updateRecipes() } catch (e) { + // If cached data is corrupted → clear it and show error message recipesContainer.innerHTML = "Could not load recipes 😢" localStorage.removeItem("allRecipes") localStorage.removeItem("lastFetch") } } else { + // No cached data available → show error message recipesContainer.innerHTML = "Could not load recipes 😢" } } finally { @@ -98,14 +118,16 @@ const fetchData = async () => { } } -// Functions +// ---------- CORE RENDERING FUNCTIONS ---------- const showRecipes = (recipesArray) => { const recipesContainer = document.getElementById("recipeContainer") + + // Add animation: fade out and scale down before updating content recipesContainer.style.opacity = "0" recipesContainer.style.transform = "scale(0.95)" setTimeout(() => { - recipesContainer.innerHTML = "" + recipesContainer.innerHTML = "" // Clear existing recipes if (recipesArray.length === 0) { recipesContainer.innerHTML = `

😢 No recipes match your filter, try choosing another one

` @@ -146,6 +168,7 @@ const showRecipes = (recipesArray) => { const updateRecipes = () => { let filteredRecipes = [...allRecipes] + // Apply cuisine filters if selected if (selectedFilters.length > 0) { filteredRecipes = filteredRecipes.filter(recipe => selectedFilters.includes(recipe.cuisine.toLowerCase()) @@ -173,7 +196,7 @@ const sortRecipes = (recipesArray) => { } -//Eventlistener +// ---------- EVENT LISTENERS ---------- favBtn.addEventListener("click", () => { if (showFavoritesOnly === false) { showFavoritesOnly = true @@ -229,16 +252,10 @@ searchInput.addEventListener("input", () => { filterButtons.forEach(button => { button.addEventListener("click", () => { const value = button.dataset.value - - //If "All" is chosen -> clear everything else + randomButton.classList.remove("selected") if (value === "all") { - selectedFilters = [] - selectedSort = null - showFavoritesOnly = false - favBtn.classList.remove("active") - filterButtons.forEach(btn => btn.classList.remove("selected")) + resetFilters() button.classList.add("selected") - randomButton.classList.remove("selected") updateRecipes() return @@ -269,6 +286,8 @@ filterButtons.forEach(button => { sortButtons.forEach(button => { button.addEventListener("click", () => { + randomButton.classList.remove("selected") + if (selectedSort === button.dataset.value) { selectedSort = null button.classList.remove("selected") @@ -282,24 +301,22 @@ sortButtons.forEach(button => { }) }) -randomButton.addEventListener("click", () => { - randomButton.classList.toggle("selected") - - // Deselect all kitchen filters - filterButtons.forEach(btn => btn.classList.remove("selected")) - sortButtons.forEach(btn => btn.classList.remove("selected")) - - //Clear internal filter selection - selectedFilters = [] - selectedSort = null - showFavoritesOnly = false - favBtn.classList.remove("active") - const randomRecipe = allRecipes[Math.floor(Math.random() * allRecipes.length)] +randomButton.addEventListener("click", () => { + if (randomButton.classList.contains("selected")) { - showRecipes([randomRecipe]) + randomButton.classList.remove("selected") + resetFilters() + updateRecipes() + } else { + resetFilters() + randomButton.classList.add("selected") + const randomRecipe = allRecipes[Math.floor(Math.random() * allRecipes.length)] + showRecipes([randomRecipe]) + } }) + recipesContainer.addEventListener("click", (event) => { const favButton = event.target.closest(".btn-fav") if (favButton) { diff --git a/style.css b/style.css index bb333be49..29c2f15c5 100644 --- a/style.css +++ b/style.css @@ -130,7 +130,7 @@ header { =========================================== */ h2 { font-weight: 700; - font-size: 20px; + font-size: 24px; } .options-container { @@ -139,7 +139,6 @@ h2 { padding: 10px 14px; gap: 20px; margin: 0 auto; - max-width: 100%; } .filter-buttons, @@ -395,7 +394,7 @@ hr.solid { } #recipeContainer { - padding: 0 40px 40px 40px; + padding: 0 40px 40px; } .placeholder-output { @@ -421,7 +420,6 @@ hr.solid { } } - @media (min-width: 1200px) { h1 { font-size: 64px; From 883c62b406014b699522337c9a31b0243dba106f Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Fri, 10 Oct 2025 12:09:06 +0200 Subject: [PATCH 16/17] cleaner code --- script.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/script.js b/script.js index 771cb7f15..c3b5da214 100644 --- a/script.js +++ b/script.js @@ -31,6 +31,7 @@ const resetFilters = () => { randomButton.classList.remove("selected") } + // Helper to extract and clean ingredients const extractIngredients = (recipe) => { const fromExtended = recipe.extendedIngredients?.map(i => i.original.toLowerCase().trim()) || [] @@ -49,7 +50,7 @@ const fetchData = async () => { const lastFetch = localStorage.getItem("lastFetch") const now = Date.now() - // If it's been less than 24 hours since the last fetch → use cache + // Use cached recipes if last fetch was under 24 hours ago if (lastFetch && (now - lastFetch < 24 * 60 * 60 * 1000)) { const cachedRecipes = localStorage.getItem("allRecipes") if (cachedRecipes) { @@ -118,10 +119,9 @@ const fetchData = async () => { } } + // ---------- CORE RENDERING FUNCTIONS ---------- const showRecipes = (recipesArray) => { - const recipesContainer = document.getElementById("recipeContainer") - // Add animation: fade out and scale down before updating content recipesContainer.style.opacity = "0" recipesContainer.style.transform = "scale(0.95)" @@ -167,7 +167,6 @@ const showRecipes = (recipesArray) => { const updateRecipes = () => { let filteredRecipes = [...allRecipes] - // Apply cuisine filters if selected if (selectedFilters.length > 0) { filteredRecipes = filteredRecipes.filter(recipe => @@ -240,7 +239,6 @@ searchInput.addEventListener("input", () => { updateRecipes() return } - const searchedRecipes = allRecipes.filter(recipe => recipe.title.toLowerCase().includes(query) || recipe.ingredients.join(", ").toLowerCase().includes(query) @@ -304,10 +302,10 @@ sortButtons.forEach(button => { randomButton.addEventListener("click", () => { if (randomButton.classList.contains("selected")) { - randomButton.classList.remove("selected") resetFilters() updateRecipes() + } else { resetFilters() randomButton.classList.add("selected") From 14a76f512c97944d5111f374f34e21be721e04aa Mon Sep 17 00:00:00 2001 From: Sandra Hagevall Date: Fri, 10 Oct 2025 12:36:36 +0200 Subject: [PATCH 17/17] add fixes to media querys --- style.css | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/style.css b/style.css index 29c2f15c5..43c79cd30 100644 --- a/style.css +++ b/style.css @@ -385,10 +385,6 @@ hr.solid { } .options-container { - flex-direction: row; - align-items: flex-start; - justify-content: space-between; - gap: 100px; margin-bottom: 40px; padding-left: 40px; } @@ -420,6 +416,15 @@ hr.solid { } } +@media (min-width: 900px) { + .options-container { + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + gap: 100px; + } +} + @media (min-width: 1200px) { h1 { font-size: 64px;