diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..72f1563
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+._*
\ No newline at end of file
diff --git a/README.md b/README.md
index 93bb712..4873e72 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,6 @@ Tweat 🍴✨
Tweat is a modern recipe web app designed to make your cooking experience delightful and personal. Explore a collection of built-in recipes for inspiration, and easily add your own creations to build a customized recipe book. Whether you're a home chef or a foodie, Tweat helps you organize, discover, and share your culinary ideas with ease.
- Tweat, is not an homonym of the old "tweet", but is a fast way to say "To Eat!"
+ Tweat, is not an homonym of the old "tweet", but is a "tweak" and a fast way to say "To Eat!" ;)
The pitch is simple, and techy people should get it right away: Tweat, is the git of the kitchen. How? you may say. It's easy: You browse recipes, then you choose one or more, at first we aggregate ingredients, and pre-cooked elements (equivalent to staging), then once that is done, we move to the execution stage (the commit).
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..9df0a93
--- /dev/null
+++ b/app.js
@@ -0,0 +1,438 @@
+
+// getting the elements from the page (timers)
+const topSection = document.querySelector('.top');
+window.addEventListener('scroll', () => {
+ topSection.style.top = '0';
+
+});
+
+// getting the elements from the page
+const recipesContainer = document.querySelector('.recipesContainer');
+const recipesSearchResults = document.querySelector('.search-results');
+const recipeName = document.querySelector('.recipe_name');
+const recipeImg = document.querySelector('.recipeImg');
+const ingredientsList = document.querySelector('.ingredientsList');
+const cookingSteps = document.querySelector('.cookingSteps');
+const ingredientNumber = document.querySelector('.number-seach-input');
+const keywordInput = document.querySelector('.keyword-seach-input');
+const keywordSearchBtn = document.querySelector('.keyword-search-btn');
+const ingredientSearchBtn = document.querySelector('.ingr-nbr-find-btn');
+const timeCounterContainer = document.querySelector('.time-counter');
+const timeCount = document.querySelector('.time-count');
+const timerContainer = document.querySelector('.timer-container');
+const minutesInput = document.querySelector('.time-amount-input');
+const setTimerBtn = document.querySelector('.set-timer-btn');
+const timerState = document.querySelector('.timer-state')
+const addRecipeButton = document.querySelector('.addRecipeButton');
+const addRecipeForm = document.querySelector('.addRecipeForm');
+const addNewIngredientsBtn = document.querySelector('.addNewIngredientsBtn');
+// the elements that will hold the new recipe
+const newRecipeName = document.querySelector('.newRecipeName');
+const newRecipeImg = document.querySelector('.newRecipeImage');
+const newRecipeType = document.querySelector('.newRecipeType');
+const newRecipePitch = document.querySelector('.newRecipePitch');
+const newIngredientName = document.querySelector('.newRecipeIngredientName');
+const newIngredientAmount = document.querySelector('.newIngredientAmount');
+const newIngredientUnit = document.querySelector('.newIngredientUnit');
+const newRecipeDescription = document.querySelector('.newRecipeDescription');
+const moreIngredientsContainer = document.querySelector('.moreIngredients');
+
+// the list of all recipes, will be an array of objects, to facilitate iteration and ordering
+//the list of units that are used in the recipes
+const unitTypes = {
+ weight: ["g", "kg"],
+ volume: ["ml", "dl", "l", "cup", "tbsp", "tsp"],
+ counts: ["piece", "dozen", "bottle", "pack", "clove"]
+
+ // shortcuts
+ // g = unitTypes.weight[0] kg = unitTypes.weight[1]
+ // ml = unitTypes.volume[0] ld =ml = unitTypes.volume[1] l = unitTypes.volume[2] cup = unitTypes.volume[3] tbsp = unitTypes.volume[4] tsp = unitTypes.volume[5]
+ // pieces = unitTypes.counts[0] dozen = unitTypes.counts[1] bottle = unitTypes.counts[2] pack = unitTypes.counts[3] clove = unitTypes.counts[4]
+};
+//fetching the recipes
+let builtInRecipes = []
+async function getRecipes() {
+ const source = "https://raw.githubusercontent.com/siderdk/siderdk.github.io/refs/heads/main/api/tweatData.json";
+ const response = await fetch(source);
+ builtInRecipes = await response.json();
+ return builtInRecipes;
+}
+
+// load user-created recipes from local storage
+const userRecipesKey = "userRecipes";
+const loadUserRecipes = () => {
+ const storedRecipes = localStorage.getItem(userRecipesKey);
+ return storedRecipes ? JSON.parse(storedRecipes) : [];
+};
+
+//consolidating all recipes in one list
+const getAllRecipes = async () => {
+ await getRecipes();
+ const userRecipes = loadUserRecipes();
+ return [...builtInRecipes, ...userRecipes];
+};
+
+getAllRecipes()
+ .then((allRecipes) => {
+ console.log("recipes fetched", allRecipes);
+ })
+ .catch((error) => {
+ console.error("An error occurred while fetching recipes:", error);
+ });
+
+const allRecipes = await getAllRecipes();
+
+
+//a function to render a preview card for each recipe inside a given container "parent"
+async function renderRecipeCard (parent, recipe) {
+ parent.innerHTML = ""
+ recipe.forEach((obj)=>{
+
+ const recipeCard = document.createElement('div');
+ recipeCard.classList.add('recipe-preview');
+
+ const recipeTitle = document.createElement('h3');
+ recipeTitle.innerText = obj.title;
+ recipeCard.appendChild(recipeTitle);
+
+ const recipeImage = document.createElement('img');
+ recipeImage.src = obj.pictureLink;
+ recipeImage.alt = obj.title;
+ recipeCard.appendChild(recipeImage)
+
+ const recipePitch = document.createElement('p');
+ recipePitch.innerText = obj.pitch;
+ recipePitch.classList.add('pitch')
+ recipeCard.appendChild(recipePitch);
+
+
+ parent.appendChild(recipeCard);
+
+ //making cards expand on click
+
+ expandCollapseCard(recipeCard, obj);
+
+ });
+};
+
+renderRecipeCard(recipesContainer, allRecipes);
+
+// expand and collapse the recipe card
+async function expandCollapseCard (recipeCard, obj) {
+ recipeCard.addEventListener("click", ()=>{
+ if (!recipeCard.querySelector('.ingredients') && !recipeCard.querySelector('.description')) {
+ const recipePitch = recipeCard.querySelector('p');
+ recipePitch.style.display = "none";
+ recipeCard.style.height = "auto";
+ recipeCard.style.width = "25rem";
+ recipeCard.style.fontsize = "2rem";
+ const ingredientsList = document.createElement('ul');
+ ingredientsList.classList.add('ingredients');
+ obj.ingredients.forEach((ingredient) => {
+ const listItem = document.createElement('li');
+ if (ingredient.amount > 0) {listItem.innerText = `${ingredient.amount || ""} ${ingredient.unit || ""} of ${ingredient.name}`
+ } else {listItem.innerText = `${ingredient.name}: to taste`};
+
+ ingredientsList.appendChild(listItem);
+ listItem.style.margin = "0.2rem";
+
+ });
+
+ recipeCard.appendChild(ingredientsList);
+ ingredientsList.style.margin = "0.5rem";
+
+ const recipeDescription = document.createElement('fieldset');
+ recipeDescription.innerHTML = `
+
${obj.description}
`;
+ recipeDescription.classList.add('description');
+ recipeCard.appendChild(recipeDescription);
+
+ const addToListBtn = document.createElement('button');
+ addToListBtn.innerText = 'Add to shopping list';
+ addToListBtn.classList.add('add-to-list-button');
+ recipeCard.appendChild(addToListBtn);
+
+ const collapseButton = document.createElement('button');
+ collapseButton.innerText = 'Collapse';
+ collapseButton.classList.add('collapse-button');
+ recipeCard.appendChild(collapseButton);
+
+ recipeCard.style.position = "fixed";
+ recipeCard.style.top = "50%";
+ recipeCard.style.left = "50%";
+ recipeCard.style.transform = "translate(-50%, -50%)";
+ recipeCard.style.zIndex = "3";
+ // Create and style the overlay
+ const overlay = document.createElement('div');
+ overlay.style.position = "fixed";
+ overlay.style.top = "0";
+ overlay.style.left = "0";
+ overlay.style.width = "100vw";
+ overlay.style.height = "100vh";
+ overlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
+ overlay.style.zIndex = "1"; // Behind the card
+ document.body.appendChild(overlay);
+
+ //if the recipe is editable, add an edit button and delete button
+ if (obj.edit) {
+ const editButton = document.createElement('a');
+ editButton.innerHTML = ``;
+ editButton.classList.add('edit-recipe-btn');
+ recipeCard.appendChild(editButton);
+ const deleteButton = document.createElement('a');
+ deleteButton.innerHTML = ``;
+ deleteButton.classList.add('delete-recipe-btn');
+ recipeCard.appendChild(deleteButton);
+ }
+
+
+ // Make a card collapse
+ collapseButton.addEventListener("click", (e) => {
+ e.stopPropagation(); // Prevent triggering the card click event
+ ingredientsList.remove();
+ recipeDescription.remove();
+ collapseButton.remove();
+ addToListBtn.remove();
+ recipeCard.style.width = "15rem";
+ recipePitch.style.display = "unset";
+ recipeCard.style.transform = "";
+ recipeCard.style.position = "";
+ recipeCard.style.top = "";
+ recipeCard.style.left = "";
+ recipeCard.style.zIndex = "";
+ document.body.removeChild(overlay);
+
+ // Remove the edit and delete buttons if they exist (for some reason I have to redifine them because the console says they are out of scope)
+ if (obj.edit) {
+ const editButton = recipeCard.querySelector('.edit-recipe-btn');
+ editButton.remove();
+ const deleteButton = recipeCard.querySelector('.delete-recipe-btn');
+ deleteButton.remove();
+ }
+
+ });
+}
+ })
+};
+
+//find recipes by number of ingredients and render them
+const getRecipeByIngredientNbr = (recipeslist, num)=>{
+ const foundRecipes = [];
+ recipeslist.forEach((recipe)=>{
+ if(recipe.ingredients.length===num) {
+ foundRecipes.push(recipe);
+ }
+ })
+ return foundRecipes;
+}
+
+ingredientSearchBtn.addEventListener("click", (e)=> {
+ const regex = /^(|[^0-9]|[0-9]*[^0-9]|)$/; // regex to check if the input is a positive number
+ const str = ingredientNumber.value
+ const num = parseInt(str.replace(regex, "")); // parsing the number from the input
+ const result = getRecipeByIngredientNbr(allRecipes, num);
+ if (num===0) {renderRecipeCard(recipesContainer, allRecipes)} else {renderRecipeCard(recipesContainer, result)}
+ ;
+})
+ingredientNumber.addEventListener("keydown", (e) => {
+ if (e.key === "Enter") {
+ const regex = /^(|[^0-9]|[0-9]*[^0-9]|)$/; // regex to check if the input is a positive number
+ const str = ingredientNumber.value;
+ const num = parseInt(str.replace(regex, ""), 10); // parsing the number from the input
+ const result = getRecipeByIngredientNbr(allRecipes, num);
+
+ if (num === 0) {
+ renderRecipeCard(recipesContainer, allRecipes);
+ } else {
+ renderRecipeCard(recipesContainer, result);
+ }
+ }
+});
+
+
+
+// finding recipes by keywords
+const getRecipeByKeyword = (recipesList, str) =>{
+ const foundRecipes = [];
+ //If I understand regex correctly, this is supposed to catch anything that is not a space or a letter
+ const regex = /[^a-zA-Z\s]/g;
+ const cleanStr = str.replace(regex, "").toLowerCase();
+ //handling empty inputs
+ if (!cleanStr.trim()) {
+ return allRecipes;
+ } else {
+ recipesList.forEach((recipe)=>{
+ // cheking only in the relevant parts of the recipe
+ let found = false;
+ if (recipe.title.toLowerCase().includes(cleanStr)) {
+ found = true
+ } else if (recipe.pitch.toLowerCase().includes(cleanStr)) {
+ found = true
+ } else if (recipe.description.toLowerCase().includes(cleanStr)) {
+ found = true
+ } else if (recipe.ingredients.some((ingredient)=> ingredient.name.toLowerCase().includes(cleanStr))) {
+ found = true;
+ };
+ if (found) {
+ foundRecipes.push(recipe);
+ }
+ });
+ return foundRecipes;
+ }
+};
+
+
+keywordSearchBtn.addEventListener("click", async (e)=>{
+ const result = getRecipeByKeyword(allRecipes, keywordInput.value);
+ renderRecipeCard(recipesContainer, result);
+} );
+keywordInput.addEventListener("keydown", async (e)=>{
+ const result = getRecipeByKeyword(allRecipes, keywordInput.value);
+ renderRecipeCard(recipesContainer, result);
+ } )
+
+//user timer
+const TimeIsUp = ()=> {
+ alert('The timer is Up!!!')
+}
+
+setTimerBtn.addEventListener("click", ()=>{
+ const timeAmount = Math.sqrt((parseInt(minutesInput.value) ** 2))
+ const timeInMlSec = timeAmount * 60000 // converting the amount of time to milliseconds
+ timerState.textContent = `A timer has been set for ${timeAmount} minutes`;
+ setTimeout(()=>{
+ alert("The time is up!");
+ timerState.textContent = "";
+ minutesInput.value = ""
+ }, timeInMlSec);
+
+});
+
+// total time on the page
+const timeCounter = () => {
+ const startTime = new Date(); // Record the time the user starts visiting the page
+
+ setInterval(() => {
+ const currentTime = new Date(); // Get the current time
+ const timeElapsed = Math.floor((currentTime - startTime) / 1000); // Time difference in seconds
+
+ // Convert the elapsed time to minutes and seconds
+ const seconds = timeElapsed % 60;
+ const minutes = Math.floor(timeElapsed / 60);
+
+ // Update the display
+
+ if (timeElapsed <59) {
+ timeCount.innerText = `${seconds} seconds`;
+ } else if (timeElapsed > 59 && timeElapsed < 119) {
+ timeCount.innerText = `${minutes} minute and ${seconds} seconds`
+ } else if (timeElapsed >= 120){timeCount.innerText = `${minutes} minutes and ${seconds} seconds`}
+ // Alert if user has been on the page for more than an hour
+ if (minutes > 59) {
+ alert("There is no way you are on this page for this long! Thank you for your attention");
+ }
+ }, 1000); // Run every second
+};
+
+// Start the count
+timeCounter();
+
+
+//adding a recipe functionality
+
+//the object that will hold the new recipe
+let newRecipe = {
+ title: "",
+ id : `${Date.now()}`,
+ pictureLink: "",
+ type: "",
+ pitch: "",
+ ingredients: [
+ { name: "",
+ amount: "",
+ unit: "" }
+ ],
+ description: "",
+ edit: true
+};
+// a function to save recipes to local storage
+const saveUserRecipes = (recipes) => {
+ localStorage.setItem(userRecipesKey, JSON.stringify(recipes));
+};
+
+//a function to add a new recipe to the list of recipes
+const addNewRecipe = (newRecipe) => {
+ const userRecipes = loadUserRecipes();
+ userRecipes.push(newRecipe);
+ saveUserRecipes(userRecipes);
+};
+
+
+// showing and resetting the form to add a new recipe
+addRecipeButton.addEventListener('click', ()=>{
+ if(addRecipeButton.innerText === "Add your recipe to the list") {
+ addRecipeButton.innerText = "cancel";
+ addRecipeButton.style.background = "rgba(0, 0, 0, 0.75)";
+ } else {
+ addRecipeButton.innerText = "Add your recipe to the list"
+ addRecipeButton.style.background = "linear-gradient(90deg, #036f11, #6fca3a)";
+ resetNewRecipeForm();
+ };
+ addRecipeForm.classList.toggle("hidden");
+
+});
+
+//resetting the form
+function resetNewRecipeForm() {
+ newRecipeName.value = "";
+ newRecipeImg.value = "";
+ newRecipeType.value = "--";
+ newRecipePitch.value = "";
+ newRecipeDescription.value = "";
+ newIngredientName.value = "";
+ newIngredientAmount.value = "";
+ newIngredientUnit.value = "";
+ moreIngredientsContainer.innerHTML = "";
+ ingredientCount = 1;
+}
+
+
+
+//adding a new ingredient to the new recipe
+let ingredientCount = 1;
+const maxIngredients = 20;
+addNewIngredientsBtn.addEventListener('click', ()=>{
+ if (ingredientCount < maxIngredients) {
+ moreIngredientsContainer.innerHTML += `
+
+
+
+
+ `;
+ ingredientCount++;
+ } else {
+ alert(`You can only add up to ${ingredientCount} ingredients`)
+ }
+
+});
+
+
+function addRecipe() {
+
+
+}
\ No newline at end of file
diff --git a/assets/full_screen_bg.png b/assets/full_screen_bg.png
new file mode 100644
index 0000000..1a42034
Binary files /dev/null and b/assets/full_screen_bg.png differ
diff --git a/assets/mobile_bg.png b/assets/mobile_bg.png
new file mode 100644
index 0000000..375194a
Binary files /dev/null and b/assets/mobile_bg.png differ
diff --git a/index.html b/index.html
index 1b30407..248438d 100644
--- a/index.html
+++ b/index.html
@@ -3,37 +3,116 @@
+
+
Tweat
-
+
+
-
+
+
+
Make a timer for less than 60min
+
+
-
-
-
-
-
![recipe illustration]()
-
-
-
-
-
+
+
tweat
+
Health, taste, and sustainability
+
to your health!
+
+
+
+
-