diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..25bf8f8fa Binary files /dev/null and b/.DS_Store differ diff --git a/Assets/.DS_Store b/Assets/.DS_Store new file mode 100644 index 000000000..5008ddfcf Binary files /dev/null and b/Assets/.DS_Store differ diff --git a/Assets/404error.jpg b/Assets/404error.jpg new file mode 100644 index 000000000..80914e924 Binary files /dev/null and b/Assets/404error.jpg differ diff --git a/Assets/bakedchicken.png b/Assets/bakedchicken.png new file mode 100644 index 000000000..f9c514470 Binary files /dev/null and b/Assets/bakedchicken.png differ diff --git a/Assets/focaccia.png b/Assets/focaccia.png new file mode 100644 index 000000000..d95d90db7 Binary files /dev/null and b/Assets/focaccia.png differ diff --git a/Assets/food_default.jpg b/Assets/food_default.jpg new file mode 100644 index 000000000..e90ddeae3 Binary files /dev/null and b/Assets/food_default.jpg differ diff --git a/Assets/scallion.png b/Assets/scallion.png new file mode 100644 index 000000000..d21e1d3f1 Binary files /dev/null and b/Assets/scallion.png differ diff --git a/README.md b/README.md index 58f1a8a66..6c25f077d 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ # js-project-recipe-library + +### Netlify link: + +https://juls-recipe-library.netlify.app/ + +### Description: + +- Recipe Library app +- Fetches random recepies from Spoonacular API (https://spoonacular.com/food-api) diff --git a/index.html b/index.html new file mode 100644 index 000000000..32358190f --- /dev/null +++ b/index.html @@ -0,0 +1,118 @@ + + +
+ + +
+ Try another filter, or go back to all recipes.
+Please try again later or use cached recipes.
+${err.message}
`; + } +} + +// Random button +function getRandomRecipe() { + const all = showingFavourites ? favourites : recipes; + if (!all.length) return; + const random = all[Math.floor(Math.random() * all.length)]; + displayRecipes([random]); +} + +// Favs storage +function attachFavouriteEvents() { + const favBtns = document.querySelectorAll(".favourite-btn"); + favBtns.forEach(btn => { + btn.addEventListener("click", e => { + const id = parseInt(e.target.dataset.id); + toggleFavourite(id); + }); + }); +} + +function toggleFavourite(id) { + const recipe = recipes.find(r => r.id === id) || favourites.find(f => f.id === id); + if (!recipe) return; + + const exists = favourites.some(f => f.id === id); + if (exists) { + favourites = favourites.filter(f => f.id !== id); + } else { + favourites.push(recipe); + } + + localStorage.setItem("favourites", JSON.stringify(favourites)); + + if (showingFavourites) displayRecipes(favourites); + else applyFiltersAndSorting(); +} + +//Main functions for filters and sortings + +function initCuisineFilters() { + const cuisineButtons = [btnAll, btnAsian, btnFrench, btnMexican, btnItalian]; + + cuisineButtons.forEach(btn => { + btn.addEventListener("click", () => { + const clickedCuisine = btn.textContent.trim(); + + if (clickedCuisine === "All") { + activeCuisines = []; + cuisineButtons.forEach(b => b.classList.remove("active")); + btnAll.classList.add("active"); + } else { + btnAll.classList.remove("active"); + + if (activeCuisines.includes(clickedCuisine)) { + activeCuisines = activeCuisines.filter(c => c !== clickedCuisine); + btn.classList.remove("active"); + } else { + activeCuisines.push(clickedCuisine); + btn.classList.add("active"); + } + + if (activeCuisines.length === 0) btnAll.classList.add("active"); + } + + applyFiltersAndSorting(); + }); + }); +} + +function initTimeFilters() { + timeBtns.forEach(btn => { + btn.addEventListener("click", () => { + const text = btn.textContent; + let newTime = null; + + if (text.includes("30")) newTime = "30"; + else if (text.includes("45")) newTime = "45"; + else if (text.includes("1,5")) newTime = "90"; + else if (text.includes("3")) newTime = "180"; + + if (activeTime === newTime) { + activeTime = null; + timeBtns.forEach(b => b.classList.remove("active")); + } else { + activeTime = newTime; + timeBtns.forEach(b => b.classList.remove("active")); + btn.classList.add("active"); + } + + applyFiltersAndSorting(); + }); + }); +} + +function initSortingButtons() { + sortShortest.addEventListener("click", () => { + activeSort = activeSort === "asc" ? null : "asc"; + sortShortest.classList.toggle("active", activeSort === "asc"); + sortLongest.classList.remove("active"); + applyFiltersAndSorting(); + }); + + sortLongest.addEventListener("click", () => { + activeSort = activeSort === "desc" ? null : "desc"; + sortLongest.classList.toggle("active", activeSort === "desc"); + sortShortest.classList.remove("active"); + applyFiltersAndSorting(); + }); +} + +function initRandomButton() { + btnRandom.addEventListener("click", () => { + btnRandom.classList.add("active"); + getRandomRecipe(); + }); + + const otherButtons = [ + btnAll, btnAsian, btnFrench, btnMexican, btnItalian, + ...timeBtns, + sortShortest, sortLongest, + btnFavourites + ]; + + otherButtons.forEach(btn => { + btn.addEventListener("click", () => { + btnRandom.classList.remove("active"); + }) + }) +} + +function initFavouritesButton() { + const originalFavText = btnFavourites.innerHTML; + + btnFavourites.addEventListener("click", e => { + e.preventDefault(); + showingFavourites = !showingFavourites; + btnFavourites.innerHTML = showingFavourites + ? "Back to All Recipes" + : originalFavText; + + btnFavourites.classList.toggle("active", showingFavourites); + + if (showingFavourites) displayRecipes(favourites); + else applyFiltersAndSorting(); + }); +} + +function attachUiEvents() { + initCuisineFilters(); + initTimeFilters(); + initSortingButtons(); + initRandomButton(); + initFavouritesButton(); +} + + +// Initial load +function init() { + attachUiEvents(); + btnAll.classList.add("active"); + fetchRecipes(); +} + +init(); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 000000000..07b6423ff --- /dev/null +++ b/style.css @@ -0,0 +1,534 @@ +* { + margin: 0; + padding: 0; +} + +html { + font-family: Futura, Trebuchet-MS, Arial, sans-serif; + font-size: 16px; + font-weight: 500; + line-height: normal; + text-decoration: none; + scroll-behavior: smooth; + overflow-x: hidden; + box-sizing: border-box; +} + +body { + background-color: #FAFBFF; +} + +header { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +h1 { + color: #0018A4; + font-size: 64px; + font-weight: bold; + line-height: 100%; + text-align: left; + margin: 64px 0 0 64px; + display: inline-block; +} + +.fav-container { + margin: 64px 64px 0 0; + padding: 0; +} + +#btnFavourites { + background-color: #fbb0c2; + align-self: flex-end; + border: 2px solid transparent; +} + +#btnFavourites:hover { + border: 2px solid #0018A4; + transition: 0.3s; +} + +#btnFavourites.active { + color: #fff; + background: #e35072; + border: 2px solid transparent; + transition: 0.3s; +} + +/* Media queries for H1 */ +/* Wide screen >1440px */ +@media (min-width: 1440px) { + h1 { + font-size: 90px; + margin: 64px 0 0 120px; + } +} + +/* Tablet screen between 576px-768px */ +@media (min-width: 576px) and (max-width: 1024px) { + header { + display: flex; + flex-direction: column; + } + + h1 { + font-size: 10vw; + text-wrap: nowrap; + margin: 40px 0 0 40px; + } + + .fav-container { + margin: 24px 0 24px 40px; + padding: 0; + } +} + +/* Mobile screen <=576px */ +@media (max-width: 576px) { + header { + display: flex; + flex-direction: column; + } + + h1 { + font-size: 12vw; + text-wrap: nowrap; + margin: 20px 0 0 20px; + } + + .fav-container { + margin: 20px 20px 0; + padding: 0; + } +} + +/* / Media queries for H1 */ + + +/* main { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(2, auto); + gap: 2rem; +} */ + +.filter-container { + display: flex; + gap: 2rem; + margin: 64px 64px 48px; + width: 100%; + max-width: 100%; +} + +.filter-wrapper { + display: flex; + flex-direction: column; + gap: 12px; +} + +.filter { + display: flex; + flex-wrap: wrap; + gap: 12px; + align-items: flex-start; + width: 100%; + max-width: 100%; +} + +.filter-wrapper>h3 { + grid-column: span 4; + font-size: 22px; + font-weight: bold; + padding-bottom: 4px; +} + +/* Media queries for filter block */ +/* Wide screen >1440px */ +@media (min-width: 1440px) { + .filter-container { + display: flex; + gap: 2rem; + margin: 64px 120px 48px; + } + + .filter>h3 { + font-size: 24px; + text-wrap: nowrap; + } +} + +/* Tablet screen between 576px-768px */ +@media (min-width: 576px) and (max-width: 768px) { + .filter-container { + display: flex; + flex-direction: column; + gap: 2rem; + margin: 24px 40px; + } + + .filter>h3 { + font-size: 18px; + text-wrap: nowrap; + } +} + +/* Mobile screen <=576px */ +@media (max-width: 576px) { + .filter-container { + display: flex; + flex-direction: column; + width: fit-content; + gap: 16px; + margin: 24px 20px; + } + + .filter>h3 { + font-size: 18px; + } +} + +/* / Media queries for filter block */ + + +.filter .btn { + font-size: 18px; + color: #0018A4; + font-weight: medium; + border: 2px solid transparent; + border-radius: 50px; + height: 44px; + width: fit-content; + padding: 8px 16px; + box-sizing: border-box; + cursor: pointer; + text-wrap: nowrap; +} + +#btnRandom { + background-color: #cdc0fa; + box-sizing: border-box; +} + +#btnRandom:hover { + border: 2px solid #0018A4; + transition: 0.3s; +} + +#btnRandom.active { + color: #fff; + background: #2c1493; + border: 2px solid transparent; + transition: 0.3s; +} + +.btn-filter-kitch { + background-color: #CCFFE2; + box-sizing: border-box; +} + +.btn-filter-kitch:hover { + border: 2px solid #0018A4; + transition: 0.3s; +} + +.btn-filter-kitch.active { + color: #fff; + background: #0018A4; + border: 2px solid transparent; + transition: 0.3s; +} + +.btn-filter-min { + background-color: #FFECEA; + box-sizing: border-box; +} + +.btn-filter-min:hover { + border: 2px solid #0018A4; + transition: 0.3s; +} + +.btn-filter-min.active { + color: #fff; + background: #FF6589; + border: 2px solid transparent; + transition: 0.3s; +} + +.btn-sort { + background-color: #f7c0fa; + box-sizing: border-box; +} + +.btn-sort:hover { + border: 2px solid #0018A4; + transition: 0.3s; +} + +.btn-sort.active { + color: #fff; + background: #86048d; + border: 2px solid transparent; + transition: 0.3s; +} + +button.disabled { + opacity: 0.5; + pointer-events: none; + cursor: not-allowed; +} + +/* Media queries for filter buttons */ +/* Mobile screen <=480px */ +@media (max-width: 480px) { + .filter .btn { + font-size: 16px; + } +} + +/* / Media queries for filter buttons */ + + +.cards-container { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + grid-template-rows: auto; + grid-auto-rows: auto; + gap: 1rem; + margin: 0 auto 64px; + box-sizing: border-box; + max-width: 1600px; + width: 90%; +} + +/* Media queries for cards block */ +/* Tablet screen between 576px-1024px */ +@media (min-width: 576px) and (max-width: 1024px) { + .cards-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1rem; + margin: 0 40px; + } +} + +/* Mobile screen <=576px */ +@media (max-width: 576px) { + .cards-container { + display: flex; + flex-direction: column; + gap: 1rem; + margin: auto; + box-sizing: border-box; + align-items: stretch; + } +} + +/* / Media queries for cards block */ + +.card { + background-color: #fff; + border: 1px solid #E9E9E9; + border-radius: 16px; + display: flex; + position: relative; + overflow: hidden; + max-width: 400px; + min-width: 0; + padding: 16px 16px 24px 16px; + flex-direction: column; + align-items: flex-start; + gap: 16px; + box-shadow: 0 4px 8px rgba(23, 8, 8, 0.1); + transition: transform 0.2s; +} + +.card:hover { + border: 2px solid #0018A4; + box-shadow: 0 0 30px 0 rgba(0, 24, 164, 0.20); +} + +.card-link { + display: block; + pointer-events: auto; + text-decoration: none; + color: inherit; + height: 100%; + width: 100%; +} + +.card-img { + min-width: 180px; + max-width: 100%; + width: 100%; + object-fit: cover; + height: auto; +} + +.card-big-heading { + font-size: 22px; + font-weight: 700; + line-height: normal; +} + +.card-content { + display: flex; + flex-direction: column; + gap: 12px; +} + +.card a { + pointer-events: auto; +} + +.favourite-btn { + background: white; + border: none; + border-radius: 4px; + outline: none; + cursor: pointer; + pointer-events: auto; + position: absolute; + top: 12px; + right: 12px; + z-index: 5; + font-size: 1.5rem; + padding: 4px 8px; + transition: transform 0.2s ease; +} + +.favourite-btn:hover { + transform: scale(1.2); +} + +.favourite-btn:focus { + outline: none; +} + +.favourite-btn:active { + transform: scale(1.1); +} + +.card-line { + width: 100%; + border-top: 1px solid #E9E9E9; +} + +.details-card { + display: flex; + flex-direction: column; + gap: 8px; +} + +.card-detail { + display: flex; + gap: 4px; + align-items: baseline; +} + +.card-detail dt { + font-size: 18px; + font-weight: 700; +} + +.card h3 { + font-size: 18px; + font-weight: 700; +} + +.ingredient-list { + list-style-type: none; + padding-left: 0; +} + +/* No recipes found card */ +.empty-state { + background: none; + border: none; + box-shadow: none; + text-align: center; + padding: 3rem 1rem; + width: 100%; + max-width: 800px; + margin: 4rem auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.empty-state h2 { + font-size: 1.8rem; + color: #0018A4; + margin-bottom: 0.5rem; +} + +.empty-state p { + font-size: 1rem; + color: #333; +} + +.empty-state-img { + width: 100%; + max-width: 500px; + height: auto; + margin-bottom: 1rem; +} + +/* Media queries for the cards*/ + +/* Tablet screen between 576px-1024px */ +@media (min-width: 576px) and (max-width: 1024px) { + .card { + padding: 16px 16px 24px 16px; + flex-direction: column; + align-items: center; + gap: 16px; + width: auto; + } +} + +/* Mobile screen <=576px */ +@media (max-width: 576px) { + .card { + width: 100%; + padding: 16px 12px 20px 12px; + flex-direction: column; + align-items: start; + gap: 16px; + } + + .card-img { + width: 100%; + max-width: 100%; + height: auto; + } + + .card-big-heading { + font-size: 20px; + } + + .card-detail dt { + font-size: 16px; + } + + .card h3 { + font-size: 16px; + } + + .empty-state-img { + max-width: 280px; + } + + .empty-state h2 { + font-size: 1.4rem; + } + + .empty-state p { + font-size: 0.9rem; + } +} + +/* / Media queries for the cards*/ \ No newline at end of file