diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..fd3a2d8bd Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index 58f1a8a66..5dd21d718 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ # js-project-recipe-library + +https://marina-recipelibrary.netlify.app/ diff --git a/images/padthai.jpg b/images/padthai.jpg new file mode 100644 index 000000000..37996926b Binary files /dev/null and b/images/padthai.jpg differ diff --git a/images/pizza.jpg b/images/pizza.jpg new file mode 100644 index 000000000..ad1df6635 Binary files /dev/null and b/images/pizza.jpg differ diff --git a/images/risotto.jpg b/images/risotto.jpg new file mode 100644 index 000000000..cc6c5a934 Binary files /dev/null and b/images/risotto.jpg differ diff --git a/images/tacos.jpg b/images/tacos.jpg new file mode 100644 index 000000000..9316add33 Binary files /dev/null and b/images/tacos.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..78b524055 --- /dev/null +++ b/index.html @@ -0,0 +1,46 @@ + + + + + + + + Project Recipe Library + + + +
+
+

Recipe Library

+
+ +
+
+

Filter on kitchen

+ + + + + + + +
+ +
+

Sort on time

+ + +
+
+

Random

+ +
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 000000000..c0254c900 --- /dev/null +++ b/script.js @@ -0,0 +1,137 @@ +//=======GLOBAL VARIABLES=======// + +const apiKey = "f7aa1eb0bfab45cdae5e9f2f21292092" +const container = document.getElementById("container") +const buttons = document.querySelectorAll(".btn-kitchen") +const sortButtons = document.querySelectorAll(".btn-sorting") +const randomButton = document.querySelector(".btn-random") + +let selectedCuisine = "All" +let currentRecipes = [] + +//======= FUNCTIONS for UI =======// + +//Buttons, changes color when active// +function makesButtonInteractive(groupClass) { + const buttons = document.querySelectorAll(groupClass) + + buttons.forEach((btn) => { + btn.addEventListener("click", () => { + buttons.forEach(b => b.classList.remove("active")) + btn.classList.add("active") + }) + }) +} + +//Shows recipes in container// +const showRecipe = (recipeArray) => { + container.innerHTML = "" //Resetting the container before filling it// + + recipeArray.forEach(recipe => { + const cuisineText = recipe.cuisines.length ? recipe.cuisines.join(", ") : "N/A" + + const timeText = recipe.readyInMinutes ? `${recipe.readyInMinutes} min` : "N/A" + + container.innerHTML += ` +
+ ${recipe.title} +

${recipe.title}

+
+ +
+ Cuisine: ${cuisineText} + Time: ${timeText} +
+ +
+

Ingredients:

+ +
+ `; + }); +}; + +//======= EVENTLISTENERS =======// + +//Filterbuttons// +buttons.forEach(button => { + button.addEventListener("click", () => { + selectedCuisine = button.textContent.trim() //trim removes invisible spaces at the beginning or end of a text + fetchRecipes() + }) +}) + +//Sortingbuttons// +sortButtons.forEach(button => { + button.addEventListener("click", () => { + const chosenSort = button.textContent.trim() //Ascending/descending// + if (!currentRecipes.length) return + + const sortedRecipes = [...currentRecipes] //coping the latest list from the API + + sortedRecipes.sort((a, b) => { + const timeA = a.readyInMinutes || 0 + const timeB = b.readyInMinutes || 0 + return chosenSort === "Ascending" ? timeA - timeB : timeB - timeA + }) + + showRecipe(sortedRecipes) + }) +}) + +//Randombutton// +randomButton.addEventListener("click", () => { + if (!currentRecipes.length) { + container.innerHTML = + "

No recipes to show for this cuisine right now...

" + return + } + + const randomIndex = Math.floor(Math.random() * currentRecipes.length) + const randomRecipe = currentRecipes[randomIndex] + + showRecipe([randomRecipe]) +}) + +//======= FETCH from API =======// + +async function fetchRecipes() { + container.innerHTML = "

⏳ Loading recipes...

" + + try { + let url = `https://api.spoonacular.com/recipes/random?number=30&apiKey=${apiKey}&addRecipeInformation=true&addRecipeInstructions=true` + + if (selectedCuisine !== "All") { + url += `&cuisine=${selectedCuisine}` //Use cuisines here instead of tags, to get sorted by cuisines// + } + + const response = await fetch(url) + if (!response.ok) throw new Error("API-limit reached or network error") + + const data = await response.json() + + currentRecipes = data.recipes + .filter(recipe => recipe.cuisines && recipe.cuisines.length > 0) //Takes away recipes without cuisine + .filter(recipe => selectedCuisine === "All" || recipe.cuisines.includes(selectedCuisine)) //Filter on chosen cuisine + + if (currentRecipes.length === 0) { + container.innerHTML = `

No recipes were found for this "${selectedCuisine}" 😕

` + } else { + showRecipe(currentRecipes) + } + + } catch (err) { + container.innerHTML = `

⚠️ ${err.message}

` + } +} + +//======= SETUP =======// + +makesButtonInteractive(".btn-kitchen") //The function runs on both groups// +makesButtonInteractive(".btn-sorting") +fetchRecipes() //fetches all recipes when page loads diff --git a/style.css b/style.css new file mode 100644 index 000000000..642aee5b2 --- /dev/null +++ b/style.css @@ -0,0 +1,198 @@ +*, +/*html important to avoid side-scroll*/ +body, +html { + margin: 0; + padding: 0; + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + box-sizing: border-box; + overflow-x: hidden; +} + +.outer-container { + display: grid; +} + +h1 { + width: 350px; + height: 70px; + margin: 50px 0 0 10px; + color: #0018A4; + font-size: 45px; + font-weight: bold; + line-height: 100%; +} + + +.upper-box { + display: flex; + flex-direction: column; + gap: 20px; + margin: 10px; + padding: 10px; + font-size: 20px; + font-weight: bold; +} + +.btn-kitchen { + padding: 8px 16px 8px 16px; + margin-top: 10px; + background-color: #CCFFE2; + border-radius: 50px; + font-size: 16px; + border: none; + color: #0018A4; +} + +.btn-kitchen.active { + background-color: #0018A4; + color: white; +} + +.btn-kitchen:hover { + border: 2px solid #0018A4; +} + +.btn-sorting { + padding: 8px 16px 8px 16px; + margin-top: 10px; + background-color: #FFECEA; + border-radius: 50px; + font-size: 16px; + border: none; + color: #0018A4; +} + +.btn-sorting.active { + background-color: #FF6589; + color: white; +} + +.btn-sorting:hover { + border: 2px solid #2d46d5; +} + +.btn-random { + width: 150px; + padding: 8px 16px 8px 16px; + margin-top: 10px; + background-color: rgb(212, 232, 240); + border-radius: 50px; + font-size: 14px; + border: none; + color: #0018A4; +} + +.btn-random.active { + background-color: #0018A4; + color: white; +} + +.btn-random:hover { + border: 2px solid #2d46d5; +} + +.container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, max-content)); + width: 100%; + max-width: 320px; + justify-content: center; + gap: 20px; + padding: 10px; + margin: 0 auto; +} + +.card { + font-size: 22px; + display: flex; + max-width: 300px; + flex-direction: column; + border: 1px solid #e9e9e9; + border-radius: 16px; + padding: 16px 16px 24px 16px; +} + +.card:hover { + transform: scale(1.03); + box-shadow: 0px 0px 30px 0px #0018A433; + border: 2px solid #0018A4; +} + +.card img { + object-fit: cover; + width: 100%; + height: 200px; + border-radius: 12px; + margin-bottom: 15px; +} + +.card ul { + list-style-type: none; + font-size: 16px; + margin-top: 10px; +} + +p { + font-size: 18px; +} + +.info-row { + font-size: 16px; +} + +.info-row span { + display: block; + padding: 5px; +} + +ul li { + padding: 5px; +} + +.divider { + width: 95%; + height: 0; + border: 1px solid #e9e9e9; + margin: 10px 0 10px 0; +} + +/* //==== TABLET ====// */ +@media (min-width:600px) { + h1 { + font-size: 50px; + margin: 50px 0 0 20px; + } + + .container { + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + justify-content: center; + width: 100%; + max-width: 900px; + margin: 0 auto; + gap: 50px; + } +} + +/* //==== DESKTOP ====// */ +@media (min-width: 1024px) { + .outer-container { + width: 100%; + padding-left: 40px; + box-sizing: border-box; + } + + h1, + .upper-box { + margin-left: 0; + text-align: left; + } + + .container { + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + justify-content: start; + margin-left: 0; + gap: 30px; + max-width: 1600px; + } +} \ No newline at end of file