diff --git a/api/delete_recipe.php b/api/delete_recipe.php new file mode 100644 index 0000000..0e9bce4 --- /dev/null +++ b/api/delete_recipe.php @@ -0,0 +1,24 @@ + false, 'error' => 'Invalid input. Recipe ID is missing.']); + exit; +} + +$recipeId = $data['id']; + +$pdo = db(); + +try { + $stmt = $pdo->prepare("DELETE FROM recipes WHERE id = ?"); + $stmt->execute([$recipeId]); + + echo json_encode(['success' => true]); + +} catch (PDOException $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/api/get_recipes.php b/api/get_recipes.php new file mode 100644 index 0000000..e1fe415 --- /dev/null +++ b/api/get_recipes.php @@ -0,0 +1,26 @@ +query('SELECT * FROM recipes ORDER BY created_at DESC'); + $recipes = $recipes_stmt->fetchAll(); + + $ingredients_stmt = $pdo->query('SELECT * FROM ingredients'); + $all_ingredients = $ingredients_stmt->fetchAll(); + + $ingredients_by_recipe = []; + foreach ($all_ingredients as $ingredient) { + $ingredients_by_recipe[$ingredient['recipe_id']][] = $ingredient; + } + + foreach ($recipes as $i => $recipe) { + $recipes[$i]['ingredients'] = $ingredients_by_recipe[$recipe['id']] ?? []; + } + + echo json_encode(['success' => true, 'recipes' => $recipes]); + +} catch (PDOException $e) { + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/api/save_recipe.php b/api/save_recipe.php new file mode 100644 index 0000000..dbd8455 --- /dev/null +++ b/api/save_recipe.php @@ -0,0 +1,43 @@ + false, 'error' => 'Invalid input.']); + exit; +} + +$pdo = db(); + +try { + $pdo->beginTransaction(); + + $stmt = $pdo->prepare("INSERT INTO recipes (name, guests) VALUES (?, ?)"); + $stmt->execute([$data['name'], $data['guests']]); + $recipeId = $pdo->lastInsertId(); + + $stmt = $pdo->prepare("INSERT INTO ingredients (recipe_id, name, quantity, unit) VALUES (?, ?, ?, ?)"); + foreach ($data['ingredients'] as $ing) { + $stmt->execute([$recipeId, $ing['name'], $ing['quantity'], $ing['unit']]); + } + + $pdo->commit(); + + // Fetch the newly created recipe to return it to the client + $stmt = $pdo->prepare("SELECT * FROM recipes WHERE id = ?"); + $stmt->execute([$recipeId]); + $recipe = $stmt->fetch(); + + $stmt = $pdo->prepare("SELECT * FROM ingredients WHERE recipe_id = ?"); + $stmt->execute([$recipeId]); + $ingredients = $stmt->fetchAll(); + $recipe['ingredients'] = $ingredients; + + echo json_encode(['success' => true, 'recipe' => $recipe]); + +} catch (PDOException $e) { + $pdo->rollBack(); + echo json_encode(['success' => false, 'error' => $e->getMessage()]); +} diff --git a/assets/css/custom.css b/assets/css/custom.css index 4e20b74..bec5b1d 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,95 +1,245 @@ - -/* assets/css/custom.css */ - -@import url('https://fonts.googleapis.com/css2?family=Mountains+of+Christmas:wght@700&family=Lato:wght@400;700&display=swap'); - +/* General Body Styles */ body { - font-family: 'Lato', sans-serif; - background-color: #F0F8FF; - color: #292B2C; - overflow-x: hidden; + background-color: #0a2e36; /* Dark teal background */ + color: #ffffff; /* White text */ + font-family: 'Poppins', sans-serif; + padding-top: 40px; /* Make space for garland */ } -h1, h2, h3, .h1, .h2, .h3 { - font-family: 'Mountains of Christmas', cursive; - font-weight: 700; +/* Headings */ +h1, h2, h3, h4, h5, h6 { + color: #ffffff !important; /* Use !important to override other styles if necessary */ +} + +/* Christmas Tree */ +.christmas-tree-container { + position: fixed; + bottom: 0; + width: 100px; + height: 150px; + z-index: 100; +} + +.christmas-tree-container.left { + left: 20px; +} + +.christmas-tree-container.right { + right: 20px; +} + +.tree { + position: relative; + width: 100%; + height: 100%; +} + +.tree::before { /* The tree itself */ + content: ''; + position: absolute; + bottom: 30px; /* Height of the trunk */ + left: 0; + width: 0; + height: 0; + border-left: 50px solid transparent; + border-right: 50px solid transparent; + border-bottom: 120px solid #2C5F2D; /* Dark green */ +} + +.tree::after { /* The trunk */ + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 20px; + height: 30px; + background: #5C3D2E; /* Brown */ +} + +.ornament { + position: absolute; + width: 10px; + height: 10px; + border-radius: 50%; + background: #ff6f61; /* Coral red */ +} + +.ornament.o1 { top: 50px; left: 45px; } +.ornament.o2 { top: 70px; left: 30px; background: #ffff24; } +.ornament.o3 { top: 75px; left: 60px; background: #2424ff; } +.ornament.o4 { top: 95px; left: 40px; } +.ornament.o5 { top: 100px; left: 15px; background: #24ff24;} +.ornament.o6 { top: 105px; left: 70px; background: #ff24ff;} + + +/* Garland */ +body::before { + content: ''; + position: fixed; + top: 10px; + left: -5%; + width: 110%; + height: 20px; + background: + radial-gradient(circle, #ff2424 4px, transparent 5px), + radial-gradient(circle, #24ff24 4px, transparent 5px), + radial-gradient(circle, #2424ff 4px, transparent 5px), + radial-gradient(circle, #ffff24 4px, transparent 5px), + radial-gradient(circle, #ff24ff 4px, transparent 5px); + background-size: 100px 20px; + background-position: 0 0, 20px 0, 40px 0, 60px 0, 80px 0; + background-repeat: repeat-x; + z-index: 1031; + animation: garland-animation 1.5s infinite; +} + +@keyframes garland-animation { + 50% { + filter: brightness(0.7); + } +} + + +/* Navbar */ +.navbar { + background-color: rgba(10, 46, 54, 0.8) !important; /* Semi-transparent dark teal */ + backdrop-filter: blur(10px); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .navbar-brand { - font-family: 'Mountains of Christmas', cursive; + color: #ffffff !important; + font-weight: 600; } +/* Main Content */ +.display-4 { + font-weight: 700; + color: #ffffff; +} + +.lead { + color: #ffffff; +} + +/* Cards */ .card { - border-radius: 0.5rem; + background-color: rgba(255, 255, 255, 0.05); border: none; - box-shadow: 0 4px 15px rgba(0,0,0,0.05); + border-radius: 15px; } +.card-body { + color: #ffffff; +} + +/* Forms */ +.form-label { + color: #ffffff; +} + +.form-control { + background-color: rgba(0, 0, 0, 0.2); + border: 1px solid rgba(255, 255, 255, 0.1); + color: #ffffff; + border-radius: 8px; +} + +.form-control:focus { + background-color: rgba(0, 0, 0, 0.3); + border-color: #ff6f61; /* Coral red accent */ + box-shadow: 0 0 0 0.25rem rgba(255, 111, 97, 0.25); + color: #ffffff; +} + +.form-control::placeholder { + color: rgba(255, 255, 255, 0.5); +} + +/* Buttons */ .btn-primary { - background-color: #D9534F; - border-color: #D9534F; - transition: background-color 0.3s ease; + background-color: #ff6f61; /* Coral red */ + border-color: #ff6f61; + font-weight: 600; + padding: 12px 30px; + border-radius: 50px; + transition: all 0.3s ease; } .btn-primary:hover { - background-color: #c9302c; - border-color: #c9302c; + background-color: #e65a50; + border-color: #e65a50; + transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(255, 111, 97, 0.2); +} + +.btn-outline-secondary { + border-color: rgba(255, 255, 255, 0.8); + color: #ffffff; + font-weight: 600; + padding: 12px 30px; + border-radius: 50px; + transition: all 0.3s ease; +} + +.btn-outline-secondary:hover { + background-color: rgba(255, 255, 255, 0.1); + color: #ffffff; + border-color: #ffffff; } .btn-secondary { - background-color: #5CB85C; - border-color: #5CB85C; - transition: background-color 0.3s ease; + background-color: rgba(255, 255, 255, 0.15); + border: none; + color: #ffffff; } -.btn-secondary:hover { - background-color: #4cae4c; - border-color: #4cae4c; +/* Shopping List & Recipe Cards */ +#shopping-list-container .text-muted, +#recipe-cards-container .text-muted { + color: #ffffff !important; } -.btn-danger { - background-color: #F0AD4E; - border-color: #F0AD4E; - transition: background-color 0.3s ease; +.recipe-card { + background-color: rgba(255, 255, 255, 0.1); + border-radius: 10px; + padding: 15px; + margin-bottom: 15px; } -.btn-danger:hover { - background-color: #ec971f; - border-color: #ec971f; +/* Footer */ +footer.bg-light { + background-color: transparent !important; + border-top: 1px solid rgba(255, 255, 255, 0.1); + color: #ffffff; } -#shopping-list-container { - background-color: #fff; - padding: 2rem; - border-radius: 0.5rem; - min-height: 300px; -} - -.ingredient-row { - display: flex; - gap: 0.5rem; - align-items: center; -} - -.ingredient-row .form-control { - flex: 1; -} - -/* Snowflakes animation */ -.snowflake { - color: #fff; - font-size: 1em; - font-family: Arial, sans-serif; - text-shadow: 0 0 5px #000; +/* Snow Effect */ +#snow-container { position: fixed; - top: -5%; - z-index: -1; - user-select: none; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 1032; + overflow: hidden; +} + +.snowflake { + position: absolute; + top: -20px; + background: #fff; + border-radius: 50%; + opacity: 0.8; + pointer-events: none; animation: fall linear infinite; } @keyframes fall { to { transform: translateY(105vh); + opacity: 0; } } diff --git a/assets/js/main.js b/assets/js/main.js index bcb58f1..b02f1b0 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,147 +1,319 @@ - document.addEventListener('DOMContentLoaded', function () { // --- Snowflakes Effect --- function createSnowflakes() { - const snowflakeContainer = document.body; - for (let i = 0; i < 50; i++) { + const snowContainer = document.getElementById('snow-container'); + if (!snowContainer) return; + + snowContainer.innerHTML = ''; + + const numberOfSnowflakes = 50; + + for (let i = 0; i < numberOfSnowflakes; i++) { const snowflake = document.createElement('div'); snowflake.className = 'snowflake'; - snowflake.textContent = '❄'; + + const size = Math.random() * 4 + 2; // size from 2px to 6px + snowflake.style.width = `${size}px`; + snowflake.style.height = `${size}px`; + snowflake.style.left = Math.random() * 100 + 'vw'; - snowflake.style.animationDuration = (Math.random() * 3 + 2) + 's'; // 2-5 seconds - snowflake.style.animationDelay = Math.random() * 2 + 's'; - snowflake.style.opacity = Math.random(); - snowflake.style.fontSize = Math.random() * 10 + 10 + 'px'; - snowflakeContainer.appendChild(snowflake); + + const animationDuration = Math.random() * 5 + 5; // 5 to 10 seconds + snowflake.style.animationDuration = `${animationDuration}s`; + + const animationDelay = Math.random() * 5; // 0 to 5 seconds + snowflake.style.animationDelay = `${animationDelay}s`; + + snowflake.style.opacity = Math.random() * 0.7 + 0.3; // 0.3 to 1.0 + + snowContainer.appendChild(snowflake); } } - createSnowflakes(); - // --- Calculator Logic --- + // --- DOM Elements --- + const recipeNameInput = document.getElementById('recipeName'); + const guestCountInput = document.getElementById('guestCount'); const ingredientsContainer = document.getElementById('ingredients-container'); const addIngredientBtn = document.getElementById('add-ingredient'); const calculateBtn = document.getElementById('calculate-btn'); + const newRecipeBtn = document.getElementById('new-recipe-btn'); const shoppingListContainer = document.getElementById('shopping-list-container'); + const recipeCardsContainer = document.getElementById('recipe-cards-container'); - let ingredientIndex = 1; + // --- Core Functions --- - function addIngredientRow() { - ingredientIndex++; + async function loadRecipes() { + try { + const response = await fetch('api/get_recipes.php'); + const data = await response.json(); + if (data.success) { + renderRecipeCards(data.recipes); + } else { + console.error('Failed to load recipes:', data.error); + recipeCardsContainer.innerHTML = '
Error loading recipes.
Could not connect to the server.
Your calculated list will appear here.
+Здесь будут появляться ваши сохраненные рецепты.
No ingredients to show. Please fill out the recipe form.
'; + html += 'Нет ингредиентов для расчета.
'; } else { html += 'Your calculated list will appear here.
-