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.

'; + } + } catch (error) { + console.error('Error:', error); + recipeCardsContainer.innerHTML = '

Could not connect to the server.

'; + } + } + + function addIngredientRow(ingredient = { name: '', quantity: '', unit: '' }) { const row = document.createElement('div'); row.className = 'ingredient-row mb-2'; row.innerHTML = ` - - - + + + `; ingredientsContainer.appendChild(row); } - if (addIngredientBtn) { - addIngredientBtn.addEventListener('click', addIngredientRow); + function clearForm() { + recipeNameInput.value = ''; + guestCountInput.value = '1'; + ingredientsContainer.innerHTML = ''; + addIngredientRow(); + shoppingListContainer.innerHTML = ` +
+

Your Shopping List

+

Your calculated list will appear here.

+
+ `; } - if (ingredientsContainer) { - ingredientsContainer.addEventListener('click', function(e) { - if (e.target.classList.contains('remove-ingredient')) { - e.target.closest('.ingredient-row').remove(); - } + function renderRecipeCards(recipes) { + recipeCardsContainer.innerHTML = ''; + if (!recipes || recipes.length === 0) { + recipeCardsContainer.innerHTML = '

Здесь будут появляться ваши сохраненные рецепты.

'; + return; + } + + recipes.forEach(recipe => { + const cardCol = document.createElement('div'); + cardCol.className = 'col-lg-4 col-md-6 mb-4'; + cardCol.setAttribute('data-id', recipe.id); + + const card = document.createElement('div'); + card.className = 'card h-100'; + + const cardBody = document.createElement('div'); + cardBody.className = 'card-body d-flex flex-column'; + + const title = document.createElement('h5'); + title.className = 'card-title'; + title.textContent = recipe.name; + + const subtitle = document.createElement('h6'); + subtitle.className = 'card-subtitle mb-2 text-muted'; + subtitle.textContent = `${recipe.guests} guest(s)`; + + const text = document.createElement('p'); + text.className = 'card-text'; + text.textContent = `${recipe.ingredients.length} ingredients`; + + const buttonGroup = document.createElement('div'); + buttonGroup.className = 'mt-auto'; + buttonGroup.innerHTML = ` + + + `; + + cardBody.appendChild(title); + cardBody.appendChild(subtitle); + cardBody.appendChild(text); + cardBody.appendChild(buttonGroup); + card.appendChild(cardBody); + cardCol.appendChild(card); + recipeCardsContainer.appendChild(cardCol); }); } - if (calculateBtn) { - calculateBtn.addEventListener('click', function() { - const recipeName = document.getElementById('recipeName').value || 'My Festive Recipe'; - const guestCount = parseInt(document.getElementById('guestCount').value, 10); - - if (isNaN(guestCount) || guestCount <= 0) { - alert('Please enter a valid number of guests.'); - return; - } - - const ingredients = []; - const rows = ingredientsContainer.querySelectorAll('.ingredient-row'); - rows.forEach(row => { - const name = row.children[0].value; - const qty = parseFloat(row.children[1].value); - const unit = row.children[2].value; - - if (name && !isNaN(qty) && qty > 0) { - ingredients.push({ name, qty, unit }); - } - }); - - if (ingredients.length === 0) { - alert('Please add at least one ingredient.'); - return; - } - - // Calculate totals - const shoppingList = {}; - ingredients.forEach(ing => { - const totalQty = ing.qty * guestCount; - const key = ing.name.toLowerCase().trim() + '_' + (ing.unit || '').toLowerCase().trim(); - - if (shoppingList[key]) { - shoppingList[key].qty += totalQty; - } else { - shoppingList[key] = { - name: ing.name, - qty: totalQty, - unit: ing.unit - }; - } - }); - - // Render shopping list - renderShoppingList(recipeName, guestCount, Object.values(shoppingList)); - }); - } - - function renderShoppingList(recipeName, guestCount, list) { - let html = `

${recipeName} - Shopping List for ${guestCount} Guests


`; - + function renderShoppingList(list) { + let html = '

Общий список покупок


'; if (list.length === 0) { - html += '

No ingredients to show. Please fill out the recipe form.

'; + html += '

Нет ингредиентов для расчета.

'; } else { html += ''; } - shoppingListContainer.innerHTML = html; } - function formatQuantity(qty) { - // Simple formatting, can be expanded for fractions - return parseFloat(qty.toFixed(2)); - } - - const newRecipeBtn = document.getElementById('new-recipe-btn'); - - if (newRecipeBtn) { - newRecipeBtn.addEventListener('click', function() { - document.getElementById('recipeName').value = ''; - document.getElementById('guestCount').value = ''; - ingredientsContainer.innerHTML = ''; - addIngredientRow(); // Add a fresh row - shoppingListContainer.innerHTML = ` -
-

Your Shopping List

-

Your calculated list will appear here.

-
- `; + function getRecipeDataFromForm() { + const recipeName = recipeNameInput.value.trim(); + const guests = parseInt(guestCountInput.value, 10) || 0; + + const ingredients = []; + const rows = ingredientsContainer.querySelectorAll('.ingredient-row'); + rows.forEach(row => { + const name = row.querySelector('input[placeholder="Ingredient Name"]').value.trim(); + const qty = parseFloat(row.querySelector('input[placeholder="Qty"]').value); + const unit = row.querySelector('input[placeholder="Unit (e.g., grams, ml)"]').value.trim(); + if (name && !isNaN(qty) && qty > 0) { + ingredients.push({ name, quantity: qty, unit }); + } }); + + if (recipeName && guests > 0 && ingredients.length > 0) { + return { name: recipeName, guests, ingredients }; + } + return null; } - // Add one ingredient row by default + // --- Event Listeners --- + + addIngredientBtn.addEventListener('click', () => addIngredientRow()); + + ingredientsContainer.addEventListener('click', function(e) { + if (e.target.classList.contains('remove-ingredient')) { + e.target.closest('.ingredient-row').remove(); + } + }); + + recipeCardsContainer.addEventListener('click', async function(e) { + const target = e.target; + const card = target.closest('.col-lg-4'); + if (!card) return; + + const recipeId = card.getAttribute('data-id'); + + if (target.classList.contains('delete-recipe-btn')) { + if (confirm('Are you sure you want to delete this recipe?')) { + try { + const response = await fetch('api/delete_recipe.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id: recipeId }) + }); + const data = await response.json(); + if (data.success) { + card.remove(); + } else { + alert('Failed to delete recipe: ' + data.error); + } + } catch (error) { + alert('Error: ' + error.message); + } + } + } + + if (target.classList.contains('edit-recipe-btn')) { + // Find the recipe data from the currently rendered cards + const response = await fetch('api/get_recipes.php'); + const data = await response.json(); + if(!data.success) return; + const recipeToEdit = data.recipes.find(r => r.id == recipeId); + + if (recipeToEdit) { + // Populate form + recipeNameInput.value = recipeToEdit.name; + guestCountInput.value = recipeToEdit.guests; + ingredientsContainer.innerHTML = ''; + recipeToEdit.ingredients.forEach(ing => addIngredientRow(ing)); + + // Delete the old recipe from DB + try { + await fetch('api/delete_recipe.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id: recipeId }) + }); + card.remove(); // Remove from UI immediately + } catch (error) { + alert('Error preparing for edit: ' + error.message); + } + } + } + }); + + newRecipeBtn.addEventListener('click', async function() { + const recipeData = getRecipeDataFromForm(); + if (recipeData) { + try { + const response = await fetch('api/save_recipe.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(recipeData) + }); + const data = await response.json(); + if (data.success) { + await loadRecipes(); // Reload all recipes to show the new one + clearForm(); + } else { + alert('Failed to save recipe: ' + data.error); + } + } catch (error) { + alert('Error: ' + error.message); + } + } else { + alert('Please fill out the recipe name, guests, and at least one ingredient before saving.'); + } + }); + + calculateBtn.addEventListener('click', async function() { + try { + const response = await fetch('api/get_recipes.php'); + const data = await response.json(); + if (!data.success) { + alert('Could not get recipes for calculation.'); + return; + } + + const allRecipesToCalculate = data.recipes; + const currentRecipe = getRecipeDataFromForm(); + + if (currentRecipe) { + // Give it a temporary ID to avoid collisions + currentRecipe.id = 'current'; + allRecipesToCalculate.push(currentRecipe); + } + + if (allRecipesToCalculate.length === 0) { + alert('There are no recipes to calculate. Please fill out the form or save a recipe.'); + return; + } + + const combinedIngredients = new Map(); + + allRecipesToCalculate.forEach(recipe => { + const multiplier = recipe.guests; + recipe.ingredients.forEach(ing => { + const ingName = ing.name || ''; + const ingUnit = ing.unit || ''; + const key = `${ingName.trim().toLowerCase()}|${ingUnit.trim().toLowerCase()}`; + const existing = combinedIngredients.get(key); + + if (existing) { + existing.qty += (ing.quantity || 0) * multiplier; + } else { + combinedIngredients.set(key, { + name: ing.name, + qty: (ing.quantity || 0) * multiplier, + unit: ing.unit + }); + } + }); + }); + + renderShoppingList(Array.from(combinedIngredients.values())); + + } catch(error) { + alert('Calculation Error: ' + error.message); + } + }); + + // --- Initial State --- addIngredientRow(); + loadRecipes(); }); diff --git a/assets/pasted-20251109-213319-755f95ba.png b/assets/pasted-20251109-213319-755f95ba.png new file mode 100644 index 0000000..f90cd0c Binary files /dev/null and b/assets/pasted-20251109-213319-755f95ba.png differ diff --git a/assets/vm-shot-2025-11-09T21-33-11-565Z.jpg b/assets/vm-shot-2025-11-09T21-33-11-565Z.jpg new file mode 100644 index 0000000..153743e Binary files /dev/null and b/assets/vm-shot-2025-11-09T21-33-11-565Z.jpg differ diff --git a/db/migrate.php b/db/migrate.php new file mode 100644 index 0000000..73ea3bb --- /dev/null +++ b/db/migrate.php @@ -0,0 +1,26 @@ +exec($sql); + echo "Success.\n"; + } catch (PDOException $e) { + echo "Error: " . $e->getMessage() . "\n"; + // A simple way to track which migrations have been run is needed + // For now, we'll just stop on error. + exit(1); + } + } +} + +run_migrations(); + diff --git a/db/migrations/001_create_recipes_tables.sql b/db/migrations/001_create_recipes_tables.sql new file mode 100644 index 0000000..c07d4cf --- /dev/null +++ b/db/migrations/001_create_recipes_tables.sql @@ -0,0 +1,16 @@ + +CREATE TABLE IF NOT EXISTS `recipes` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + `guests` INT NOT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `ingredients` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `recipe_id` INT NOT NULL, + `name` VARCHAR(255) NOT NULL, + `quantity` FLOAT NOT NULL, + `unit` VARCHAR(50) NOT NULL, + FOREIGN KEY (`recipe_id`) REFERENCES `recipes`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/index.php b/index.php index fd979d1..eb2908f 100644 --- a/index.php +++ b/index.php @@ -23,6 +23,9 @@ + + + @@ -30,22 +33,44 @@
-