This commit is contained in:
Egor Deev 2025-11-19 11:29:11 +03:00 committed by GitHub
parent 66738488d5
commit bc04b552bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 1876 additions and 0 deletions

353
labs/lab-06/dishes.js Normal file
View file

@ -0,0 +1,353 @@
const dishes = [
{
keyword: "mushroom-soup",
name: "Грибной крем-суп",
price: 280,
category: "soup",
count: "300 мл",
image: "img/soup/mushroom-soup.jpg",
kind: "veg"
},
{
keyword: "beet-soup",
name: "Традиционный борщ",
price: 240,
category: "soup",
count: "350 мл",
image: "img/soup/beet-soup.jpg",
kind: "meat"
},
{
keyword: "chicken-noodle-soup",
name: "Куриный бульон с лапшой",
price: 220,
category: "soup",
count: "350 мл",
image: "img/soup/chicken-noodle-soup.jpg",
kind: "meat"
},
{
keyword: "minestrone-soup",
name: "Итальянский суп минестроне",
price: 260,
category: "soup",
count: "350 мл",
image: "img/soup/minestrone-soup.jpg",
kind: "veg"
},
{
keyword: "tomato-soup",
name: "Томатный крем-суп",
price: 250,
category: "soup",
count: "300 мл",
image: "img/soup/tomato-soup.jpg",
kind: "veg"
},
{
keyword: "pumpkin-soup",
name: "Тыквенный суп с имбирем",
price: 270,
category: "soup",
count: "300 мл",
image: "img/soup/pumpkin-soup.jpg",
kind: "veg"
},
{
keyword: "fish-soup",
name: "Уха из лосося",
price: 320,
category: "soup",
count: "350 мл",
image: "img/soup/fish-soup.jpg",
kind: "fish"
},
{
keyword: "seafood-soup",
name: "Суп том-ям с морепродуктами",
price: 340,
category: "soup",
count: "350 мл",
image: "img/soup/seafood-soup.jpg",
kind: "fish"
},
{
keyword: "french-onion-soup",
name: "Французский луковый суп",
price: 300,
category: "soup",
count: "350 мл",
image: "img/soup/french-onion-soup.jpg",
kind: "veg"
},
{
keyword: "vegan-bowl-chickpeas",
name: "Веганский боул с нутом",
price: 390,
category: "main-course",
count: "350 г",
image: "img/main-dishes/vegan-bowl-chickpeas.jpg",
kind: "veg"
},
{
keyword: "grilled-chicken-vegetables",
name: "Куриная грудка с овощами гриль",
price: 450,
category: "main-course",
count: "350 г",
image: "img/main-dishes/grilled-chicken-vegetables.jpg",
kind: "meat"
},
{
keyword: "salmon-quinoa-avocado",
name: "Лосось с киноа и авокадо",
price: 620,
category: "main-course",
count: "380 г",
image: "img/main-dishes/salmon-quinoa-avocado.jpg",
kind: "fish"
},
{
keyword: "seafood-pasta",
name: "Паста с морепродуктами",
price: 550,
category: "main-course",
count: "330 г",
image: "img/main-dishes/seafood-pasta.jpg",
kind: "fish"
},
{
keyword: "teriyaki-chicken-noodles",
name: "Рисовая лапша с курицей терияки",
price: 420,
category: "main-course",
count: "340 г",
image: "img/main-dishes/teriyaki-chicken-noodles.jpg",
kind: "meat"
},
{
keyword: "beef-salad",
name: "Теплый салат с говядиной",
price: 490,
category: "main-course",
count: "320 г",
image: "img/main-dishes/beef-salad.jpg",
kind: "meat"
},
{
keyword: "vegetable-stir-fry",
name: "Овощное рагу с тофу",
price: 380,
category: "main-course",
count: "350 г",
image: "img/main-dishes/vegetable-stir-fry.jpg",
kind: "veg"
},
{
keyword: "duck-orange-sauce",
name: "Утка в апельсиновом соусе",
price: 580,
category: "main-course",
count: "360 г",
image: "img/main-dishes/duck-orange-sauce.jpg",
kind: "meat"
},
{
keyword: "mushroom-risotto",
name: "Ризотто с белыми грибами",
price: 410,
category: "main-course",
count: "340 г",
image: "img/main-dishes/mushroom-risotto.jpg",
kind: "veg"
},
{
keyword: "berry-juice",
name: "Ягодный морс",
price: 120,
category: "drink",
count: "300 мл",
image: "img/drinks/berry-juice.jpg",
kind: "cold"
},
{
keyword: "water",
name: "Минеральная вода",
price: 80,
category: "drink",
count: "500 мл",
image: "img/drinks/water.jpg",
kind: "cold"
},
{
keyword: "spinach-smoothie",
name: "Зеленый смузи с шпинатом",
price: 180,
category: "drink",
count: "350 мл",
image: "img/drinks/spinach-smoothie.jpg",
kind: "cold"
},
{
keyword: "ginger-lemonade",
name: "Имбирный лимонад",
price: 140,
category: "drink",
count: "300 мл",
image: "img/drinks/ginger-lemonade.jpg",
kind: "cold"
},
{
keyword: "mango-smoothie",
name: "Смузи манго-маракуйя",
price: 170,
category: "drink",
count: "350 мл",
image: "img/drinks/mango-smoothie.jpg",
kind: "cold"
},
{
keyword: "orange-juice",
name: "Свежевыжатый апельсиновый сок",
price: 150,
category: "drink",
count: "300 мл",
image: "img/drinks/orange-juice.jpg",
kind: "cold"
},
{
keyword: "cappuccino",
name: "Капучино",
price: 150,
category: "drink",
count: "300 мл",
image: "img/drinks/cappuccino.jpg",
kind: "hot"
},
{
keyword: "green-tea",
name: "Зеленый чай",
price: 100,
category: "drink",
count: "400 мл",
image: "img/drinks/green-tea.jpg",
kind: "hot"
},
{
keyword: "hot-chocolate",
name: "Горячий шоколад",
price: 160,
category: "drink",
count: "300 мл",
image: "img/drinks/hot-chocolate.jpg",
kind: "hot"
},
{
keyword: "shrimp-salad",
name: "Салат с креветками и авокадо",
price: 380,
category: "salad",
count: "250 г",
image: "img/salads/shrimp-salad.jpg",
kind: "fish"
},
{
keyword: "caesar-salad",
name: "Цезарь с курицей",
price: 320,
category: "salad",
count: "280 г",
image: "img/salads/caesar-salad.jpg",
kind: "meat"
},
{
keyword: "greek-salad",
name: "Греческий салат",
price: 280,
category: "salad",
count: "260 г",
image: "img/salads/greek-salad.jpg",
kind: "veg"
},
{
keyword: "caprese-salad",
name: "Капрезе с моцареллой",
price: 290,
category: "salad",
count: "240 г",
image: "img/salads/caprese-salad.jpg",
kind: "veg"
},
{
keyword: "quinoa-salad",
name: "Салат с киноа и овощами",
price: 310,
category: "salad",
count: "270 г",
image: "img/salads/quinoa-salad.jpg",
kind: "veg"
},
{
keyword: "beetroot-salad",
name: "Салат со свеклой и орехами",
price: 260,
category: "salad",
count: "250 г",
image: "img/salads/beetroot-salad.jpg",
kind: "veg"
},
{
keyword: "chocolate-mousse",
name: "Шоколадный мусс",
price: 180,
category: "dessert",
count: "100 г",
image: "img/desserts/chocolate-mousse.jpg",
kind: "small"
},
{
keyword: "panna-cotta",
name: "Панна-котта с ягодным соусом",
price: 200,
category: "dessert",
count: "120 г",
image: "img/desserts/panna-cotta.jpg",
kind: "small"
},
{
keyword: "lemon-tart",
name: "Лимонный тарт",
price: 190,
category: "dessert",
count: "110 г",
image: "img/desserts/lemon-tart.jpg",
kind: "small"
},
{
keyword: "cheesecake",
name: "Чизкейк Нью-Йорк",
price: 280,
category: "dessert",
count: "150 г",
image: "img/desserts/cheesecake.jpg",
kind: "medium"
},
{
keyword: "tiramisu",
name: "Тирамису классический",
price: 290,
category: "dessert",
count: "160 г",
image: "img/desserts/tiramisu.jpg",
kind: "medium"
},
{
keyword: "chocolate-cake",
name: "Шоколадный торт",
price: 350,
category: "dessert",
count: "200 г",
image: "img/desserts/chocolate-cake.jpg",
kind: "large"
}
];

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
labs/lab-06/img/main.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

101
labs/lab-06/index.html Normal file
View file

@ -0,0 +1,101 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="https://deev.space/media/favicon.ico" type="image/x-icon">
<title>ЭкоЛанч - Доставка здоровых бизнес-ланчей в Москве</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<h1>ЭкоЛанч</h1>
<nav>
<a href="index.html" id="active">Главная</a>
<a href="menu.html">Собрать ланч</a>
<a href="delivery.html">Доставка</a>
<a href="about.html">О нас</a>
<a href="#contacts">Контакты</a>
</nav>
</header>
<main>
<section class="about-company">
<h2>О компании ЭкоЛанч</h2>
<img src="img/main.jpg" alt="Здоровая еда в контейнерах">
<p>ЭкоЛанч - это современная компания по доставке здоровых бизнес-ланчей в офисы Москвы. Наша миссия - сделать правильное питание доступным для занятых людей, которые ценят свое время и здоровье.</p>
<p>Мы используем только свежие продукты от проверенных поставщиков, а наши повара готовят блюда каждое утро. Все ланчи упакованы в экологичную биоразлагаемую упаковку, потому что мы заботимся не только о вашем здоровье, но и о здоровье нашей планеты.</p>
</section>
<section class="advantages">
<h2>Наши преимущества</h2>
<ul>
<li>Быстрая доставка в течение 60 минут по всей Москве в пределах МКАД</li>
<li>Свежие блюда, приготовленные утром из качественных продуктов</li>
<li>Сбалансированное меню, разработанное профессиональными диетологами</li>
<li>Экологичная упаковка, безопасная для окружающей среды</li>
<li>Гибкая система скидок для корпоративных клиентов и постоянных заказчиков</li>
</ul>
</section>
<section class="popular-dishes">
<h2>Самые популярные блюда</h2>
<table>
<tr>
<th>Название блюда</th>
<th>Описание</th>
<th>Калорийность</th>
<th>Цена</th>
</tr>
<tr>
<td>Куриная грудка с овощами гриль</td>
<td>Нежная куриная грудка с цукини, баклажанами и болгарским перцем</td>
<td>420 ккал</td>
<td>450 руб.</td>
</tr>
<tr>
<td>Лосось с киноа и авокадо</td>
<td>Запеченный лосось с гарниром из киноа и свежим авокадо</td>
<td>520 ккал</td>
<td>620 руб.</td>
</tr>
<tr>
<td>Теплый салат с говядиной</td>
<td>Сочная говядина с микс-салатом, томатами черри и кедровыми орешками</td>
<td>380 ккал</td>
<td>490 руб.</td>
</tr>
<tr>
<td>Веганский боул с нутом</td>
<td>Запеченный нут, хумус, овощи и тахини соус</td>
<td>450 ккал</td>
<td>390 руб.</td>
</tr>
<tr>
<td>Паста с морепродуктами</td>
<td>Паста из твердых сортов пшеницы с креветками, мидиями и томатным соусом</td>
<td>480 ккал</td>
<td>550 руб.</td>
</tr>
<tr>
<td>Рисовая лапша с курицей терияки</td>
<td>Рисовая лапша wok с куриным филе в соусе терияки и овощами</td>
<td>460 ккал</td>
<td>420 руб.</td>
</tr>
</table>
</section>
</main>
<footer id="contacts">
<p><b>Контактная информация</b></p>
<p>Телефон: <a href="tel:+79993737737">+7 (999) 373-77-37</a></p>
<p>Email: <a href="mailto:egor@deev.space">egor@deev.space</a></p>
<p>Адрес: г. Москва, ул. Михалковская, д. 7, к. 1, офис 813А</p>
<p>Режим работы: Пн-Пт с 8:00 до 20:00</p>
</footer>
</body>
</html>

246
labs/lab-06/menu.html Normal file
View file

@ -0,0 +1,246 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="https://deev.space/media/favicon.ico" type="image/x-icon">
<title>Собрать ланч - ЭкоЛанч</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<h1>ЭкоЛанч</h1>
<nav>
<a href="index.html">Главная</a>
<a href="menu.html" id="active">Собрать ланч</a>
<a href="delivery.html">Доставка</a>
<a href="about.html">О нас</a>
<a href="#contacts">Контакты</a>
</nav>
</header>
<main>
<section class="combo-section">
<h2>Доступные для заказа варианты ланча</h2>
<div class="combo-grid">
<div class="combo-card">
<div class="combo-item">
<span class="combo-icon">🍲</span>
<p>Суп</p>
</div>
<div class="combo-item">
<span class="combo-icon">🍽️</span>
<p>Главное блюдо</p>
</div>
<div class="combo-item">
<span class="combo-icon">🥗</span>
<p>Салат</p>
</div>
<div class="combo-item">
<span class="combo-icon">🥤</span>
<p>Напиток</p>
</div>
</div>
<div class="combo-card">
<div class="combo-item">
<span class="combo-icon">🍲</span>
<p>Суп</p>
</div>
<div class="combo-item">
<span class="combo-icon">🍽️</span>
<p>Главное блюдо</p>
</div>
<div class="combo-item">
<span class="combo-icon">🥤</span>
<p>Напиток</p>
</div>
</div>
<div class="combo-card">
<div class="combo-item">
<span class="combo-icon">🍲</span>
<p>Суп</p>
</div>
<div class="combo-item">
<span class="combo-icon">🥗</span>
<p>Салат</p>
</div>
<div class="combo-item">
<span class="combo-icon">🥤</span>
<p>Напиток</p>
</div>
</div>
<div class="combo-card">
<div class="combo-item">
<span class="combo-icon">🍽️</span>
<p>Главное блюдо</p>
</div>
<div class="combo-item">
<span class="combo-icon">🥗</span>
<p>Салат</p>
</div>
<div class="combo-item">
<span class="combo-icon">🥤</span>
<p>Напиток</p>
</div>
</div>
<div class="combo-card">
<div class="combo-item">
<span class="combo-icon">🍽️</span>
<p>Главное блюдо</p>
</div>
<div class="combo-item">
<span class="combo-icon">🥤</span>
<p>Напиток</p>
</div>
</div>
<div class="combo-card combo-dessert">
<div class="combo-item">
<span class="combo-icon">🍰</span>
<p>Десерт</p>
</div>
<p class="combo-note-inline">(Можно добавить к любому заказу)</p>
</div>
</div>
<p class="combo-note">Десерты можно добавить к любому варианту ланча</p>
</section>
<section>
<div class="section-header">
<h2>Супы</h2>
<div class="filter-buttons">
<button class="filter-btn" data-kind="fish">Рыбный</button>
<button class="filter-btn" data-kind="meat">Мясной</button>
<button class="filter-btn" data-kind="veg">Вегетарианский</button>
</div>
</div>
<div class="dishes-grid" id="soup-section"></div>
</section>
<section>
<div class="section-header">
<h2>Главные блюда</h2>
<div class="filter-buttons">
<button class="filter-btn" data-kind="fish">Рыбное</button>
<button class="filter-btn" data-kind="meat">Мясное</button>
<button class="filter-btn" data-kind="veg">Вегетарианское</button>
</div>
</div>
<div class="dishes-grid" id="main-course-section"></div>
</section>
<section>
<div class="section-header">
<h2>Салаты и стартеры</h2>
<div class="filter-buttons">
<button class="filter-btn" data-kind="fish">Рыбный</button>
<button class="filter-btn" data-kind="meat">Мясной</button>
<button class="filter-btn" data-kind="veg">Вегетарианский</button>
</div>
</div>
<div class="dishes-grid" id="salad-section"></div>
</section>
<section>
<div class="section-header">
<h2>Напитки</h2>
<div class="filter-buttons">
<button class="filter-btn" data-kind="cold">Холодный</button>
<button class="filter-btn" data-kind="hot">Горячий</button>
</div>
</div>
<div class="dishes-grid" id="drink-section"></div>
</section>
<section>
<div class="section-header">
<h2>Десерты</h2>
<div class="filter-buttons">
<button class="filter-btn" data-kind="small">Маленькая порция</button>
<button class="filter-btn" data-kind="medium">Средняя порция</button>
<button class="filter-btn" data-kind="large">Большая порция</button>
</div>
</div>
<div class="dishes-grid" id="dessert-section"></div>
</section>
</main>
<section class="order-form">
<h2>Оформить заказ</h2>
<form id="order-form" action="https://httpbin.org/post" method="POST">
<div class="form-container">
<div class="order-section">
<h3>Ваш заказ</h3>
<div id="order-summary">
<p class="empty-order">Ничего не выбрано</p>
</div>
</div>
<div class="customer-section">
<h3>Данные для доставки</h3>
<label for="name">Имя</label>
<input type="text" name="name" id="name" placeholder="Введите ваше имя" required>
<label for="email">Email</label>
<input type="email" name="email" id="email" placeholder="Введите ваш email" required>
<div class="checkbox-group">
<input type="checkbox" name="subscribe" id="subscribe" checked>
<label for="subscribe">Подписаться на рассылку</label>
</div>
<label for="phone">Телефон</label>
<input type="tel" name="phone" id="phone" placeholder="Введите ваш телефон" required>
<label for="address">Адрес доставки</label>
<input type="text" name="address" id="address" placeholder="Введите адрес доставки" required>
<small class="form-hint">Доставка осуществляется только по Москве</small>
<div class="radio-group">
<label>Время доставки</label>
<div>
<input type="radio" name="delivery-time-type" id="asap" value="asap" required>
<label for="asap">Как можно скорее</label>
</div>
<div>
<input type="radio" name="delivery-time-type" id="specific-time" value="specific-time" required>
<label for="specific-time">К определенному времени</label>
</div>
</div>
<label for="delivery-time">Указать время</label>
<input type="time" name="delivery-time" id="delivery-time" min="07:00" max="23:00" step="300">
<small class="form-hint">Доступное время доставки с 7:00 до 23:00</small>
<label for="comment">Комментарий к заказу</label>
<textarea name="comment" id="comment" placeholder="Введите комментарий к заказу"></textarea>
<div class="form-buttons">
<button type="reset">Сбросить</button>
<button type="submit">Отправить заказ</button>
</div>
</div>
</div>
</form>
</section>
<footer id="contacts">
<p><b>Контактная информация</b></p>
<p>Телефон: <a href="tel:+79993737737">+7 (999) 373-77-37</a></p>
<p>Email: <a href="mailto:egor@deev.space">egor@deev.space</a></p>
<p>Адрес: г. Москва, ул. Михалковская, д. 7, к. 1, офис 813А</p>
<p>Режим работы: Пн-Пт с 8:00 до 20:00</p>
</footer>
<script src="dishes.js"></script>
<script src="menu.js"></script>
</body>
</html>

406
labs/lab-06/menu.js Normal file
View file

@ -0,0 +1,406 @@
const selectedDishes = {
soup: null,
'main-course': null,
salad: null,
drink: null,
dessert: null
};
let activeFilters = {
soup: null,
'main-course': null,
salad: null,
drink: null,
dessert: null
};
function sortDishes() {
dishes.sort(function(a, b) {
return a.name.localeCompare(b.name, 'ru');
});
}
function displayDishes() {
const soupSection = document.getElementById('soup-section');
const mainCourseSection = document.getElementById('main-course-section');
const saladSection = document.getElementById('salad-section');
const drinkSection = document.getElementById('drink-section');
const dessertSection = document.getElementById('dessert-section');
soupSection.innerHTML = '';
mainCourseSection.innerHTML = '';
saladSection.innerHTML = '';
drinkSection.innerHTML = '';
dessertSection.innerHTML = '';
dishes.forEach(function(dish) {
const dishCard = createDishCard(dish);
if (dish.category === 'soup') {
if (!activeFilters.soup || dish.kind === activeFilters.soup) {
soupSection.insertAdjacentHTML('beforeend', dishCard);
}
} else if (dish.category === 'main-course') {
if (!activeFilters['main-course'] ||
dish.kind === activeFilters['main-course']) {
mainCourseSection.insertAdjacentHTML('beforeend', dishCard);
}
} else if (dish.category === 'salad') {
if (!activeFilters.salad || dish.kind === activeFilters.salad) {
saladSection.insertAdjacentHTML('beforeend', dishCard);
}
} else if (dish.category === 'drink') {
if (!activeFilters.drink || dish.kind === activeFilters.drink) {
drinkSection.insertAdjacentHTML('beforeend', dishCard);
}
} else if (dish.category === 'dessert') {
if (!activeFilters.dessert ||
dish.kind === activeFilters.dessert) {
dessertSection.insertAdjacentHTML('beforeend', dishCard);
}
}
});
addDishClickHandlers();
restoreSelection();
}
function createDishCard(dish) {
return `
<div class="dish-card" data-dish="${dish.keyword}">
<img src="${dish.image}" alt="${dish.name}">
<p class="dish-price">${dish.price} руб.</p>
<p class="dish-name">${dish.name}</p>
<p class="dish-weight">${dish.count}</p>
<button>Добавить</button>
</div>
`;
}
function addDishClickHandlers() {
const dishCards = document.querySelectorAll('.dish-card');
dishCards.forEach(function(card) {
card.addEventListener('click', function() {
const keyword = this.dataset.dish;
const dish = dishes.find(function(d) {
return d.keyword === keyword;
});
if (dish) {
selectDish(dish);
}
});
});
}
function selectDish(dish) {
const previousDish = selectedDishes[dish.category];
if (previousDish) {
const previousCard = document.querySelector(
`.dish-card[data-dish="${previousDish.keyword}"]`
);
if (previousCard) {
previousCard.classList.remove('selected');
}
}
selectedDishes[dish.category] = dish;
const currentCard = document.querySelector(
`.dish-card[data-dish="${dish.keyword}"]`
);
if (currentCard) {
currentCard.classList.add('selected');
}
updateOrderSummary();
}
function updateOrderSummary() {
const orderSummary = document.getElementById('order-summary');
const hasSelection = selectedDishes.soup ||
selectedDishes['main-course'] ||
selectedDishes.salad ||
selectedDishes.drink ||
selectedDishes.dessert;
if (!hasSelection) {
orderSummary.innerHTML = '<p class="empty-order">Ничего не выбрано</p>';
return;
}
let summaryHTML = '';
let totalPrice = 0;
if (selectedDishes.soup) {
summaryHTML += `
<div class="order-category">
<h3>Суп</h3>
<div class="order-item">
<span>${selectedDishes.soup.name}</span>
<span>${selectedDishes.soup.price} руб.</span>
</div>
</div>
`;
totalPrice += selectedDishes.soup.price;
} else {
summaryHTML += `
<div class="order-category">
<h3>Суп</h3>
<p class="not-selected">Блюдо не выбрано</p>
</div>
`;
}
if (selectedDishes['main-course']) {
summaryHTML += `
<div class="order-category">
<h3>Главное блюдо</h3>
<div class="order-item">
<span>${selectedDishes['main-course'].name}</span>
<span>${selectedDishes['main-course'].price} руб.</span>
</div>
</div>
`;
totalPrice += selectedDishes['main-course'].price;
} else {
summaryHTML += `
<div class="order-category">
<h3>Главное блюдо</h3>
<p class="not-selected">Блюдо не выбрано</p>
</div>
`;
}
if (selectedDishes.salad) {
summaryHTML += `
<div class="order-category">
<h3>Салат</h3>
<div class="order-item">
<span>${selectedDishes.salad.name}</span>
<span>${selectedDishes.salad.price} руб.</span>
</div>
</div>
`;
totalPrice += selectedDishes.salad.price;
} else {
summaryHTML += `
<div class="order-category">
<h3>Салат</h3>
<p class="not-selected">Блюдо не выбрано</p>
</div>
`;
}
if (selectedDishes.drink) {
summaryHTML += `
<div class="order-category">
<h3>Напиток</h3>
<div class="order-item">
<span>${selectedDishes.drink.name}</span>
<span>${selectedDishes.drink.price} руб.</span>
</div>
</div>
`;
totalPrice += selectedDishes.drink.price;
} else {
summaryHTML += `
<div class="order-category">
<h3>Напиток</h3>
<p class="not-selected">Напиток не выбран</p>
</div>
`;
}
if (selectedDishes.dessert) {
summaryHTML += `
<div class="order-category">
<h3>Десерт</h3>
<div class="order-item">
<span>${selectedDishes.dessert.name}</span>
<span>${selectedDishes.dessert.price} руб.</span>
</div>
</div>
`;
totalPrice += selectedDishes.dessert.price;
} else {
summaryHTML += `
<div class="order-category">
<h3>Десерт</h3>
<p class="not-selected">Десерт не выбран</p>
</div>
`;
}
summaryHTML += `
<div class="order-total">
<h3>Стоимость заказа</h3>
<p class="total-price">${totalPrice} руб.</p>
</div>
`;
orderSummary.innerHTML = summaryHTML;
}
function setupFilters() {
const filterButtons = document.querySelectorAll('.filter-btn');
filterButtons.forEach(function(button) {
button.addEventListener('click', function() {
const kind = this.dataset.kind;
const section = this.closest('section');
const category = section.querySelector('.dishes-grid').id
.replace('-section', '');
if (this.classList.contains('active')) {
this.classList.remove('active');
activeFilters[category] = null;
} else {
const sectionButtons = section
.querySelectorAll('.filter-btn');
sectionButtons.forEach(function(btn) {
btn.classList.remove('active');
});
this.classList.add('active');
activeFilters[category] = kind;
}
displayDishes();
});
});
}
function isValidCombo() {
const hasSoup = selectedDishes.soup !== null;
const hasMainCourse = selectedDishes['main-course'] !== null;
const hasSalad = selectedDishes.salad !== null;
const hasDrink = selectedDishes.drink !== null;
if (hasSoup && hasMainCourse && hasSalad && hasDrink) {
return true;
}
if (hasSoup && hasMainCourse && hasDrink) {
return true;
}
if (hasSoup && hasSalad && hasDrink) {
return true;
}
if (hasMainCourse && hasSalad && hasDrink) {
return true;
}
if (hasMainCourse && hasDrink) {
return true;
}
return false;
}
function getValidationMessage() {
const hasSoup = selectedDishes.soup !== null;
const hasMainCourse = selectedDishes['main-course'] !== null;
const hasSalad = selectedDishes.salad !== null;
const hasDrink = selectedDishes.drink !== null;
if (!hasSoup && !hasMainCourse && !hasSalad && !hasDrink) {
return 'Ничего не выбрано. Выберите блюда для заказа';
}
if (!hasDrink) {
return 'Выберите напиток';
}
if (hasSoup && !hasMainCourse && !hasSalad) {
return 'Выберите главное блюдо/салат/стартер';
}
if (hasSalad && !hasSoup && !hasMainCourse) {
return 'Выберите суп или главное блюдо';
}
if (!hasMainCourse && !hasSoup && (hasDrink || selectedDishes.dessert)) {
return 'Выберите главное блюдо';
}
return '';
}
function showNotification(message) {
const notification = document.createElement('div');
notification.classList.add('notification');
const messageText = document.createElement('p');
messageText.textContent = message;
const okButton = document.createElement('button');
okButton.textContent = 'Окей';
okButton.addEventListener('click', function() {
notification.remove();
});
notification.append(messageText);
notification.append(okButton);
document.body.append(notification);
}
function setupFormValidation() {
const orderForm = document.getElementById('order-form');
orderForm.addEventListener('submit', function(event) {
if (!isValidCombo()) {
event.preventDefault();
const message = getValidationMessage();
showNotification(message);
}
});
}
function restoreSelection() {
Object.keys(selectedDishes).forEach(function(category) {
const dish = selectedDishes[category];
if (dish) {
const card = document.querySelector(
`.dish-card[data-dish="${dish.keyword}"]`
);
if (card) {
card.classList.add('selected');
}
}
});
}
function resetSelection() {
Object.keys(selectedDishes).forEach(function(category) {
if (selectedDishes[category]) {
const card = document.querySelector(
`.dish-card[data-dish="${selectedDishes[category].keyword}"]`
);
if (card) {
card.classList.remove('selected');
}
}
selectedDishes[category] = null;
});
updateOrderSummary();
}
document.addEventListener('DOMContentLoaded', function() {
sortDishes();
displayDishes();
setupFilters();
setupFormValidation();
const resetButton = document.querySelector('button[type="reset"]');
if (resetButton) {
resetButton.addEventListener('click', function() {
resetSelection();
});
}
});

View file

@ -0,0 +1,18 @@
Валидация форм
1. Для чего нужен метод preventDefault()?
2. Какие события связаны с формами и их элементами? В чём их отличия?
3. Что такое объект ValidityState и какие свойства он содержит?
4. Что позволяет задать метод setCustomValidity()?
5. Почему требуется проверять данные на стороне клиента?
6. Почему недостаточно проверять данные только на стороне клиента?
7. Как программно проверить валидность всей формы перед отправкой?
8. Какие события связаны непосредственно с процессом валидации?
9. Как программно отправить форму или сбросить её значения?

View file

@ -0,0 +1,86 @@
# Лабораторная работа №6. Проверка данных на стороне клиента. Уведомления
---
Добавьте блок с доступными для заказа вариантами ланча. Создайте скрипт, проверяющий состав ланча при отправке формы. Реализуйте показ уведомлений для пользователей.
## Порядок выполнения
Примерные макеты представлены ниже.
* Макет блока с доступными для заказа вариантами
* Макет уведомления
[Полный макет](https://lms.mospolytech.ru/pluginfile.php/1314144/mod_assign/intro/mockupfull.png "Полный макет") страницы:
1. Создайте блок с доступными для заказа вариантами ланча.
Структура блока:
- Заголовок
- Блок с вариантами (grid-контейнер, с 3 колонками на каждой строке)
- Блок каждого варианта ланча (flex-контейнер)
- Блок каждого блюда (flex-контейнер, направление главной оси - сверху вниз)
- Изображение блюда
- Подпись
Изображения с блюдами можно скачать [здесь](https://lms.mospolytech.ru/pluginfile.php/1314144/mod_assign/intro/icons.zip?time=1730060948701 "здесь").
Доступные для заказа варианты:
|1|2|3|4|5|
|---|---|---|---|---|
|Суп <br>Главное блюдо <br>Салат <br>Напиток|Суп <br>Главное блюдо <br>Напиток|Суп <br>Салат <br>Напиток|Главное блюдо <br>Салат <br>Напиток|Главное блюдо <br>Напиток|
В отдельном блоке необходимо разместить информацию о десертах. Их можно добавлять в любой вид ланча. Эту информацию нужно также отобразить на странице (поместить ниже подписи; шрифт меньше, чем у подписи на 2 пункта).
Измените свойства для изображений данного блока при наведении на них курсора. Используйте для этого transform с функциями трансформации translateY и scale.
На видео продемонстрирована работа transform:
2. Создайте скрипт, который будет проверять все ли необходимые блюда добавил пользователь.
Скрипт должен запускаться, когда пользователь отправляет форму. Пользователь может добавлять в заказ произвольные блюда. В итоге у него должен получиться один из вариантов ланчей, описанных выше. Если перечень выбранных блюд не соответствует ни одному из возможных вариантов (комбо), у пользователя не должно быть возможности оформить заказ (форма не должна отправляться), и на странице должно быть выведено уведомление с информацией о недостающих блюдах.
Существует 5 видов уведомлений:
|Текст уведомления|Когда выводится|Изображение|
|---|---|---|
|«Ничего не выбрано. Выберите блюда для заказа»|Не добавлено ни одно блюдо|![Ничего не выбрано](https://lms.mospolytech.ru/pluginfile.php/1314144/mod_assign/intro/mockup3.png)|
|«Выберите напиток»|Выбраны все необходимые блюда, кроме напитка|![Выберите напиток](https://lms.mospolytech.ru/pluginfile.php/1314144/mod_assign/intro/mockup4.png)|
|«Выберите главное блюдо/салат/стартер»|Выбран суп, но не выбраны главное блюдо/салат/стартер|![Выберите главное блюдо/салат/стартер](https://lms.mospolytech.ru/pluginfile.php/1314144/mod_assign/intro/mockup5.png)|
|«Выберите суп или главное блюдо»|Выбран салат/стартер, но не выбраны суп/главное блюдо|![Выберите суп или главное блюдо](https://lms.mospolytech.ru/pluginfile.php/1314144/mod_assign/intro/mockup6.png)|
|«Выберите главное блюдо»|Выбран напиток/десерт|![Выберите главное блюдо](https://lms.mospolytech.ru/pluginfile.php/1314144/mod_assign/intro/mockup7.png)|
Уведомление должно создаваться динамически каждый раз, когда при попытке отправить форму скрипт обнаруживает, что какое-то блюдо не добавлено.
Оно должно отображаться поверх остальных элементов на странице и не менять положение при прокрутке. Также, его нужно отцентровать по вертикали и горизонтали.
При наведении на кнопку "Окей", изменяется цвет фона и текста кнопки.
При нажатии на кнопку, уведомление исчезает и удаляется со страницы.
На видео продемонстрирована работа уведомления:
### Материалы для изучения
#### CSS
[transform [Doka]](https://doka.guide/css/transform/)
[translateX() [MDN]](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translateX)
[translateY() [MDN]](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/translateY) 
[scale() [MDN]](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/scale)
[position [Doka]](https://doka.guide/css/position/)
[top, left, right, bottom [Doka]](https://doka.guide/css/top-left-right-bottom/)[](https://lms.mospolytech.ru/mod/assign/view.php?id=540007&forceview=1)
#### JS
[Отправка формы: событие и метод submit [JS.RU]](https://learn.javascript.ru/forms-submit)
[submit [Doka]](https://doka.guide/js/event-submit/)
[preventDefault() [Doka]](https://doka.guide/js/event-prevent-default/)
[preventDefault() [MDN]](https://developer.mozilla.org/ru/docs/Web/API/Event/preventDefault)
[Изменение документа [JS.RU]](https://learn.javascript.ru/modifying-document)
[document.createElement [MDN]](https://developer.mozilla.org/ru/docs/Web/API/Document/createElement)
[Создание элемента [JS.RU]](https://learn.javascript.ru/modifying-document#sozdanie-elementa
[Element.append() [MDN]](https://developer.mozilla.org/ru/docs/Web/API/Element/append)
[before() [MDN]](https://developer.mozilla.org/en-US/docs/Web/API/Element/before)
[after() [MDN]](https://developer.mozilla.org/en-US/docs/Web/API/Element/after)
[Методы вставки [JS.RU]](https://learn.javascript.ru/modifying-document#metody-vstavki)
[Element.remove() [MDN]](https://developer.mozilla.org/ru/docs/Web/API/Element/remove)
[Удаление узлов [JS.RU]](https://learn.javascript.ru/modifying-document#udalenie-uzlov)
#### Другое
[Юникод, внутреннее устройство строк](https://learn.javascript.ru/unicode)
[Emoji юникод](https://symbl.cc/ru/emoji/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

666
labs/lab-06/styles.css Normal file
View file

@ -0,0 +1,666 @@
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
font-family: 'Montserrat', sans-serif;
display: flex;
flex-direction: column;
min-height: 100vh;
}
header {
background-color: #2d5016;
color: white;
padding: 20px 40px;
margin: 0;
}
h1 {
margin: 0 0 15px 0;
font-size: 36px;
color: white;
}
nav {
margin: 0;
padding: 10px 0;
display: flex;
flex-wrap: wrap;
gap: 15px;
}
nav a {
text-decoration: none;
color: white;
font-family: 'Montserrat', sans-serif;
font-size: 16px;
}
nav a:hover {
color: #a8d08d;
}
nav a#active {
color: tomato;
}
main {
margin: 0 auto;
padding: 40px 60px;
max-width: 1200px;
flex: 1;
}
section {
margin: 0 0 50px 0;
padding: 30px;
background-color: #f9f9f9;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.section-header h2 {
margin: 0;
}
h2 {
font-size: 28px;
color: #2d5016;
margin: 0 0 20px 0;
}
p {
font-family: 'Montserrat', sans-serif;
font-size: 16px;
color: #333;
line-height: 1.6;
margin: 0 0 15px 0;
}
img {
width: 100%;
height: 400px;
object-fit: cover;
margin: 0 0 20px 0;
}
ul {
margin: 20px 0;
padding: 0 0 0 25px;
}
ul li {
font-family: 'Montserrat', sans-serif;
font-size: 16px;
color: #333;
margin: 0 0 12px 0;
line-height: 1.5;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
table th {
background-color: #2d5016;
color: white;
padding: 15px;
text-align: left;
border: 1px solid #2d5016;
font-size: 16px;
}
table td {
padding: 12px 15px;
border: 1px solid #ddd;
text-align: left;
font-size: 15px;
color: #333;
}
table tr:nth-child(even) {
background-color: #f2f2f2;
}
.combo-section {
margin-bottom: 50px;
}
.combo-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.combo-card {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 20px;
background-color: white;
padding: 20px;
border-radius: 15px;
}
.combo-dessert {
flex-direction: column;
gap: 10px;
}
.combo-item {
display: flex;
flex-direction: column;
align-items: center;
}
.combo-icon {
font-size: 50px;
display: block;
margin: 0 0 8px 0;
transition: transform 0.3s;
}
.combo-item:hover .combo-icon {
transform: translateY(-5px) scale(1.1);
}
.combo-item p {
margin: 0;
font-size: 14px;
text-align: center;
}
.combo-note-inline {
font-size: 12px;
text-align: center;
color: #666;
margin: 0;
font-style: italic;
}
.filter-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.filter-btn {
background-color: white;
border: 2px solid #2d5016;
color: #2d5016;
padding: 10px 20px;
border-radius: 10px;
cursor: pointer;
font-family: 'Montserrat', sans-serif;
font-size: 16px;
font-weight: 600;
transition: all 0.3s;
}
.filter-btn:hover {
background-color: #f1eee9;
}
.filter-btn.active {
background-color: #2d5016;
color: white;
}
.dishes-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 10px;
}
.dish-card {
display: flex;
flex-direction: column;
padding: 30px 40px;
border-radius: 35px;
cursor: pointer;
filter: drop-shadow(17px 19px 24px rgba(0, 0, 0, 0.13));
background-color: white;
}
.dish-card.selected {
border: 3px solid #2d5016;
background-color: #f0f7ec;
}
.dish-card.selected button {
background-color: #2d5016;
color: white;
}
.dish-card:hover {
border: 2px solid tomato;
}
.dish-card:hover button {
background-color: tomato;
color: white;
}
.dish-card img {
width: 100%;
height: auto;
aspect-ratio: 12 / 9;
object-fit: cover;
border-radius: 35px;
margin: 0 0 15px 0;
}
.dish-price {
font-size: 20px;
font-weight: 600;
margin: 0 0 10px 0;
}
.dish-name {
font-size: 18px;
font-weight: 600;
margin: 0 0 10px 0;
}
.dish-weight {
color: #888;
margin: 0 0 10px 0;
margin-top: auto;
}
.dish-card button {
background-color: #f1eee9;
border: none;
padding: 10px 30px;
border-radius: 10px;
cursor: pointer;
font-family: 'Montserrat', sans-serif;
font-size: 16px;
margin-top: 10px;
}
.order-form {
margin: 0 auto;
padding: 40px 60px;
max-width: 1200px;
background-color: white;
}
.order-form h2 {
text-align: center;
margin-bottom: 30px;
}
#order-summary {
background-color: #f9f9f9;
padding: 30px;
border-radius: 10px;
margin-bottom: 20px;
}
.empty-order {
text-align: center;
color: #888;
font-size: 18px;
}
.order-category {
margin-bottom: 25px;
}
.order-category h3 {
font-size: 20px;
color: #2d5016;
margin: 0 0 10px 0;
}
.order-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
font-size: 16px;
}
.order-item span:first-child {
font-weight: 600;
word-wrap: break-word;
overflow-wrap: break-word;
max-width: 70%;
}
.order-item span:last-child {
color: #2d5016;
font-weight: 600;
white-space: nowrap;
}
.not-selected {
color: #888;
font-style: italic;
margin: 5px 0;
}
.order-total {
margin-top: 30px;
padding-top: 20px;
border-top: 2px solid #2d5016;
}
.order-total h3 {
font-size: 22px;
color: #2d5016;
margin: 0 0 10px 0;
}
.total-price {
font-size: 24px;
font-weight: 700;
color: #2d5016;
margin: 0;
}
.form-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 40px;
}
.order-section {
display: flex;
flex-direction: column;
}
.order-section h3 {
font-size: 22px;
color: #2d5016;
margin: 0 0 20px 0;
}
.customer-section {
display: flex;
flex-direction: column;
}
.customer-section h3 {
font-size: 22px;
color: #2d5016;
margin: 0 0 20px 0;
}
.order-form label {
font-size: 16px;
color: #333;
margin: 0 0 8px 0;
font-weight: 600;
}
.order-form select,
.order-form input[type="text"],
.order-form input[type="email"],
.order-form input[type="tel"],
.order-form input[type="time"],
.order-form textarea {
width: 100%;
padding: 12px 15px;
margin: 0 0 20px 0;
border: 1px solid #ddd;
border-radius: 8px;
font-family: 'Montserrat', sans-serif;
font-size: 15px;
}
.order-form select {
cursor: pointer;
background-color: white;
}
.order-form textarea {
height: 100px;
resize: vertical;
}
.checkbox-group {
display: flex;
align-items: center;
margin: 0 0 20px 0;
}
.checkbox-group input[type="checkbox"] {
width: 18px;
height: 18px;
margin: 0 10px 0 0;
cursor: pointer;
}
.checkbox-group label {
margin: 0;
font-weight: 400;
cursor: pointer;
}
.radio-group {
margin: 0 0 20px 0;
}
.radio-group > label {
display: block;
margin-bottom: 10px;
}
.radio-group > div {
display: flex;
align-items: center;
margin: 0 0 8px 0;
}
.radio-group input[type="radio"] {
width: 18px;
height: 18px;
margin: 0 10px 0 0;
cursor: pointer;
}
.radio-group input[type="radio"] + label {
margin: 0;
font-weight: 400;
cursor: pointer;
}
.form-buttons {
display: flex;
gap: 15px;
margin-top: 10px;
}
.form-buttons button {
flex: 1;
padding: 15px 30px;
border: none;
border-radius: 10px;
font-family: 'Montserrat', sans-serif;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s;
}
.form-buttons button[type="reset"] {
background-color: #f1eee9;
color: #333;
}
.form-buttons button[type="reset"]:hover {
background-color: #e0ddd8;
}
.form-buttons button[type="submit"] {
background-color: #2d5016;
color: white;
}
.form-buttons button[type="submit"]:hover {
background-color: #3d6020;
}
.form-hint {
display: block;
font-size: 13px;
color: #666;
margin: -15px 0 20px 0;
line-height: 1.4;
}
.notification {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 40px;
border-radius: 15px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
z-index: 1000;
text-align: center;
max-width: 500px;
}
.notification p {
font-size: 18px;
margin: 0 0 25px 0;
color: #333;
}
.notification button {
background-color: #2d5016;
color: white;
border: none;
padding: 12px 40px;
border-radius: 10px;
font-family: 'Montserrat', sans-serif;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.notification button:hover {
background-color: white;
color: #2d5016;
border: 2px solid #2d5016;
}
footer {
background-color: black;
color: white;
padding: 30px 60px;
margin: 0;
}
footer p {
color: white;
margin: 0 0 10px 0;
}
footer a {
text-decoration: none;
color: white;
font-family: 'Montserrat', sans-serif;
font-size: 16px;
}
footer a:hover {
color: #a8d08d;
}
@media (max-width: 800px) {
.combo-grid {
grid-template-columns: 1fr 1fr;
}
.dishes-grid {
grid-template-columns: 1fr 1fr;
}
nav {
justify-content: space-between;
}
.about-company img {
width: 500px;
}
.form-container {
grid-template-columns: 1fr;
gap: 20px;
}
}
@media (max-width: 600px) {
h1 {
text-align: center;
}
nav {
flex-direction: column;
align-items: center;
}
nav a {
font-size: 20px;
}
section h2 {
text-align: center;
}
.combo-grid {
grid-template-columns: 1fr;
}
.dishes-grid {
grid-template-columns: 1fr;
}
.about-company img {
width: 100%;
}
table th,
table td {
font-size: 14px;
}
.form-container {
grid-template-columns: 1fr;
}
.form-buttons {
flex-direction: column;
}
.notification {
max-width: 90%;
padding: 30px 20px;
}
}
@media (max-width: 400px) {
table th,
table td {
font-size: 12px;
}
}