diff --git a/README.md b/README.md index 58f1a8a66..02a65ac01 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ -# js-project-recipe-library +Welcome to my Recipe Project — a clean, interactive web app that helps you explore and organize recipes by cuisine and cooking time. + +Features: +Filter by cuisine: Italian, Mexican, Mediterranean, Asian, Middle Eastern, European +Sort by time: Quickly find recipes based on how long they take +Responsive layout and easy to navigate + +Tech Stack: +Built with modern web tools (HTML, CSS, JavaScript) and deployed via Netlify. + +LINK: https://leonekelund-recipe-project.netlify.app/ diff --git a/index.html b/index.html new file mode 100644 index 000000000..233972356 --- /dev/null +++ b/index.html @@ -0,0 +1,61 @@ + + + + + + + Recipe project + + + + + + + +

Recipe Library

+ + + +
+ +
+

Filter on kitchen

+ +
+ + + + + + + +
+ +
+ + + +
+

Sort on time

+
+ + + +
+
+ + +
+
+ + +
+ + + +
+ + + + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 000000000..2aed88e12 --- /dev/null +++ b/script.js @@ -0,0 +1,154 @@ +//API VARIABLES +const apiKey = "bd0cc269e37b44dc9363d356be6f251a"; +const url2 = `https://api.spoonacular.com/recipes/complexSearch?number=5&sort=random&addRecipeInformation=true&apiKey=${apiKey}&cuisine=Asian,Italian,Mexican,Mediterranean,Middle Eastern,European`; + + + +//DOM ELEMENTS +const recipesContainer = document.getElementById('recipesContainer'); +const filterBtns = document.querySelectorAll('.filter-btn'); +const sortBtns = document.querySelectorAll('.sort-btn'); +const randomBtn = document.getElementById('randomBtn'); + +const showMessage = (message) => { + recipesContainer.innerHTML = ` +
+

${message}

+
+ `; +}; + +let recipesData = []; +let currentRecipes = []; + +// NORMALIZE THE CUISINES +const normalizeCuisine = (cuisine) => { + if (!cuisine) return "Unknown"; + + const asianCuisines = ["Chinese", "Japanese", "Thai", "Korean", "Vietnamese", "Indian"]; + const middleEasternCuisines = ["Turkish", "Lebanese", "Persian", "Israeli", "Egyptian"]; + const europeanCuisines = ["French", "Spanish", "German", "Greek", "British"]; + + if (asianCuisines.includes(cuisine)) return "Asian"; + if (middleEasternCuisines.includes(cuisine)) return "Middle Eastern"; + if (europeanCuisines.includes(cuisine)) return "European"; + + return cuisine; +}; + +// FETCHING API +const fetchData = () => { + fetch(url2) + .then(res => res.json()) + .then(data => { + if (!data.results) { + showMessage("Daily API quota reached or an error occurred. Please try again later."); + return; + } + + recipesData = data.results.map(recipe => ({ + id: recipe.id, + title: recipe.title, + image: recipe.image, + readyInMinutes: recipe.readyInMinutes, + cuisine: normalizeCuisine(recipe.cuisines?.[0]), + summary: recipe.summary + })); + + currentRecipes = recipesData; + + if (recipesData.length === 0) { + showMessage("No recipes found."); + } else { + showRecipes(recipesData); + } + }) + .catch(() => { + showMessage("Unable to load recipes. Please check your connection or try again later."); + }); +}; + + +fetchData(); + + + +// FUNCTION TO SHOW RECIPES +const showRecipes = (recipesToShow = recipesData) => { + + currentRecipes = recipesToShow; + recipesContainer.innerHTML = ''; + + recipesToShow.forEach(recipe => { + recipesContainer.innerHTML += ` +
+
+

${recipe.title}

+
+

Cuisine: ${recipe.cuisine}

+
+ ${recipe.title} +
+

Cooking time: ${recipe.readyInMinutes} minutes!

+

${recipe.summary}

+
+
+ + `; + }); +}; + +// FUNCTION FOR BUTTONS + +//FILTER BUTTONS +filterBtns.forEach(btn => { + btn.addEventListener("click", () => { + const selectedCuisine = btn.id.toLowerCase(); + + const filtered = + selectedCuisine === "all" + ? recipesData + : recipesData.filter(r => r.cuisine.toLowerCase() === selectedCuisine); + + filterBtns.forEach(b => b.classList.remove("active")); + btn.classList.add("active"); + + if (filtered.length === 0) { + showMessage(`No ${selectedCuisine} recipes found.`); + } else { + showRecipes(filtered); + } + }); +}); + + + +// SORT BUTTONS +sortBtns.forEach(btn => { + btn.addEventListener("click", () => { + const order = btn.id; // "ascending" or "descending" + + const sorted = [...currentRecipes].sort((a, b) => + order === "ascending" + ? a.readyInMinutes - b.readyInMinutes + : b.readyInMinutes - a.readyInMinutes + ); + + sortBtns.forEach(b => b.classList.remove("active")); + btn.classList.add("active"); + + showRecipes(sorted); + }); +}); + + + +// RANDOMBUTTON +randomBtn.addEventListener('click', () => { + //RANDOMNUMBER + const randomIndex = Math.floor(Math.random() * recipesData.length); + + const randomRecipe = recipesData[randomIndex]; + + showRecipes([randomRecipe]); +}); diff --git a/style.css b/style.css new file mode 100644 index 000000000..3fc9d2347 --- /dev/null +++ b/style.css @@ -0,0 +1,237 @@ +:root { + --primary-blue: #0018A4; + ; + --filter-default-background: #ccffe2; + --filter-hover-selected-color: #0018A4; + + + --sort-default-background: #FFECEA; + --sort-hover-selected-color: #FF6589; + + --space-1: 1rem; +} + +html, +body { + background-color: #FAFBFF; + font-family: "Futura", "Trebuchet MS", Arial, sans-serif; + overflow-x: hidden; +} + + + + + +h1 { + color: #0018A4; + font-family: Futura; + font-size: 2em; + font-style: normal; + font-weight: 700; + line-height: normal; + text-align: center; +} + +h2 { + color: #000; + font-family: Futura; + font-size: 22px; + font-style: normal; + font-weight: 700; + line-height: normal; + text-align: center; +} + + + + +.filter-sort-container { + display: flex; + flex-direction: column; + gap: 88px; + width: 100%; +} + + +.filter-section, +.fort-section { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; +} + + +.button-section { + display: flex; + flex-direction: column; + gap: 12px; + align-items: center; + +} + +.filter-btn { + background-color: var(--filter-default-background); + color: var(--filter-hover-selected-color); + border: none; + display: flex; + padding: 8px 16px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 50px; + border: 2px solid transparent; + transition: border-color 0.2s ease; + width: fit-content; +} + +.filter-btn:hover { + border: 2px solid var(--primary-blue); +} + +.filter-btn.active { + background-color: var(--primary-blue); + color: white; +} + +.sort-btn { + border: none; + background-color: var(--sort-default-background); + color: var(--filter-hover-selected-color); + display: flex; + padding: 8px 16px; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 50px; + border: 2px solid transparent; + transition: border-color 0.2s ease; + width: fit-content; +} + +.sort-btn:hover { + color: white; + border: 2px solid var(--primary-blue); + background-color: var(--sort-hover-selected-color); +} + +.sort-btn.active { + background-color: var(--sort-hover-selected-color); + color: white; +} + + +.recipe-card { + display: flex; + width: 250px; + padding: 16px 16px 24px 16px; + flex-direction: column; + align-items: flex-start; + gap: 16px; + border: 2px solid #E9E9E9; + background: #FFF; + border-radius: 16px; + margin: 0 auto; + margin-bottom: 50px; + +} + +.recipe-card img { + width: 80%; + border-radius: 8px; + margin: 0 auto; +} + +.recipe-card:hover { + box-shadow: 0 0 30px 0 rgba(0, 24, 164, 0.20); + border: 2px solid #0018A4; +} + +.random-recipe-btn { + background-color: var(--primary-blue); + color: white; + display: flex; + padding: 8px 16px; + justify-content: center; + align-items: center; + gap: 10px; + border-radius: 50px; + border: 2px solid transparent; + transition: border-color 0.2s ease; + margin-top: var(--space-1); + margin: 0 auto; + margin-bottom: 100px; + +} + +.random-recipe-btn:hover { + cursor: pointer; +} + +.recipe-title { + font-weight: bold; +} + +.divider { + width: 95%; +} + + + + +/* MEDIA QUERIES FOR DESKTOP */ + +@media (min-width: 768px) { + + h1 { + font-size: 64px; + } + + h2 { + text-align: start; + + } + + .filter-sort-container { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 88px; + } + + .recipe-card { + width: 300px; + } + + .button-section { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 12px; + justify-content: flex-start; + + } + + .button-section button { + width: auto; + display: inline-flex; + } + + .recipes-container { + display: flex; + gap: 32px; + flex-wrap: wrap; + + } + + .random-recipe-btn { + margin-bottom: 100px; + } + + .recipe-card-wrapper { + display: flex; + justify-content: start; + } + +} \ No newline at end of file