diff --git a/labs/lab-09/img/main.jpg b/labs/lab-09/img/main.jpg
new file mode 100644
index 0000000..f9f6558
Binary files /dev/null and b/labs/lab-09/img/main.jpg differ
diff --git a/labs/lab-09/index.html b/labs/lab-09/index.html
new file mode 100644
index 0000000..5e173cb
--- /dev/null
+++ b/labs/lab-09/index.html
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+ ЭкоЛанч - Доставка здоровых бизнес-ланчей в Москве
+
+
+
+
+
+
+
+
ЭкоЛанч
+
+
+
+
+
+
О компании ЭкоЛанч
+
+
ЭкоЛанч - это современная компания по доставке здоровых бизнес-ланчей в офисы Москвы. Наша миссия - сделать правильное питание доступным для занятых людей, которые ценят свое время и здоровье.
+
Мы используем только свежие продукты от проверенных поставщиков, а наши повара готовят блюда каждое утро. Все ланчи упакованы в экологичную биоразлагаемую упаковку, потому что мы заботимся не только о вашем здоровье, но и о здоровье нашей планеты.
+
+
+
+
Наши преимущества
+
+
Быстрая доставка в течение 60 минут по всей Москве в пределах МКАД
+
Свежие блюда, приготовленные утром из качественных продуктов
+ `;
+ itemsContainer.insertAdjacentHTML('beforeend', itemHTML);
+ });
+
+ const total = calculateOrderPrice(order);
+ document.getElementById('edit-total').textContent = total + 'Р';
+
+ modal.classList.remove('hidden');
+}
+
+function showDeleteModal(order) {
+ const modal = document.getElementById('delete-modal');
+
+ if (!modal) {
+ return;
+ }
+
+ currentOrderId = order.id;
+ modal.classList.remove('hidden');
+}
+
+function getOrderItems(order) {
+ const items = [];
+
+ if (order.main_course_id) {
+ const dish = dishes.find(function(d) {
+ return d.id === order.main_course_id;
+ });
+ if (dish) {
+ items.push({
+ label: 'Основное блюдо',
+ name: dish.name,
+ price: dish.price
+ });
+ }
+ }
+
+ if (order.drink_id) {
+ const dish = dishes.find(function(d) {
+ return d.id === order.drink_id;
+ });
+ if (dish) {
+ items.push({
+ label: 'Напиток',
+ name: dish.name,
+ price: dish.price
+ });
+ }
+ }
+
+ if (order.dessert_id) {
+ const dish = dishes.find(function(d) {
+ return d.id === order.dessert_id;
+ });
+ if (dish) {
+ items.push({
+ label: 'Десерт',
+ name: dish.name,
+ price: dish.price
+ });
+ }
+ }
+
+ return items;
+}
+
+function closeModal(modalId) {
+ const modal = document.getElementById(modalId);
+ if (modal) {
+ modal.classList.add('hidden');
+ }
+ currentOrderId = null;
+}
+
+function setupModalHandlers() {
+ const closeButtons = document.querySelectorAll('.modal-close');
+ closeButtons.forEach(function(button) {
+ button.addEventListener('click', function() {
+ const modal = this.closest('.modal');
+ if (modal) {
+ modal.classList.add('hidden');
+ }
+ currentOrderId = null;
+ });
+ });
+
+ const viewOkButton = document.getElementById('view-modal-ok');
+ if (viewOkButton) {
+ viewOkButton.addEventListener('click', function() {
+ closeModal('view-modal');
+ });
+ }
+
+ const editCancelButton = document.getElementById('edit-modal-cancel');
+ if (editCancelButton) {
+ editCancelButton.addEventListener('click', function() {
+ closeModal('edit-modal');
+ });
+ }
+
+ const editSaveButton = document.getElementById('edit-modal-save');
+ if (editSaveButton) {
+ editSaveButton.addEventListener('click', function() {
+ saveOrderEdit();
+ });
+ }
+
+ const deleteCancelButton = document.getElementById('delete-modal-cancel');
+ if (deleteCancelButton) {
+ deleteCancelButton.addEventListener('click', function() {
+ closeModal('delete-modal');
+ });
+ }
+
+ const deleteConfirmButton =
+ document.getElementById('delete-modal-confirm');
+ if (deleteConfirmButton) {
+ deleteConfirmButton.addEventListener('click', function() {
+ deleteOrder();
+ });
+ }
+
+ const modals = document.querySelectorAll('.modal');
+ modals.forEach(function(modal) {
+ modal.addEventListener('click', function(event) {
+ if (event.target === modal) {
+ modal.classList.add('hidden');
+ currentOrderId = null;
+ }
+ });
+ });
+}
+
+function saveOrderEdit() {
+ const form = document.getElementById('edit-form');
+
+ if (!form.checkValidity()) {
+ form.reportValidity();
+ return;
+ }
+
+ const formData = new FormData(form);
+ const updateData = {
+ full_name: formData.get('full_name'),
+ email: formData.get('email'),
+ phone: formData.get('phone'),
+ delivery_address: formData.get('delivery_address'),
+ delivery_type: formData.get('delivery_time') ? 'by_time' : 'now',
+ delivery_time: formData.get('delivery_time') || '',
+ comment: formData.get('comment') || ''
+ };
+
+ const url = API_URL + '/orders/' + currentOrderId + '?api_key=' + API_KEY;
+
+ console.log('Отправка изменений заказа:', updateData);
+
+ fetch(url, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(updateData)
+ })
+ .then(function(response) {
+ console.log('Ответ от сервера:', response.status);
+ if (!response.ok) {
+ return response.json().then(function(errorData) {
+ throw new Error(errorData.error ||
+ 'Ошибка при редактировании заказа');
+ });
+ }
+ return response.json();
+ })
+ .then(function(data) {
+ console.log('Заказ успешно изменен:', data);
+ closeModal('edit-modal');
+ showNotification('Заказ успешно изменён', 'success');
+ loadOrders();
+ })
+ .catch(function(error) {
+ console.error('Ошибка при редактировании заказа:', error);
+ showNotification('Не удалось изменить заказ: ' + error.message,
+ 'error');
+ });
+}
+
+function deleteOrder() {
+ const url = API_URL + '/orders/' + currentOrderId + '?api_key=' + API_KEY;
+
+ console.log('Удаление заказа:', currentOrderId);
+
+ fetch(url, {
+ method: 'DELETE'
+ })
+ .then(function(response) {
+ console.log('Ответ от сервера:', response.status);
+ if (!response.ok) {
+ return response.json().then(function(errorData) {
+ throw new Error(errorData.error ||
+ 'Ошибка при удалении заказа');
+ });
+ }
+ return response.json();
+ })
+ .then(function(data) {
+ console.log('Заказ успешно удален:', data);
+ closeModal('delete-modal');
+ showNotification('Заказ успешно удалён', 'success');
+ loadOrders();
+ })
+ .catch(function(error) {
+ console.error('Ошибка при удалении заказа:', error);
+ showNotification('Не удалось удалить заказ: ' + error.message,
+ 'error');
+ });
+}
+
+function showNotification(message, type) {
+ const notification = document.getElementById('notification');
+
+ if (!notification) {
+ return;
+ }
+
+ notification.textContent = message;
+ notification.className = 'notification notification-' + type;
+ notification.classList.remove('hidden');
+
+ setTimeout(function() {
+ notification.classList.add('hidden');
+ }, 3000);
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+ console.log('DOM загружен для страницы заказов');
+ setupModalHandlers();
+ loadOrders();
+});
\ No newline at end of file
diff --git a/labs/lab-09/other/Вопросы для подготовки к защите ЛР №9.txt b/labs/lab-09/other/Вопросы для подготовки к защите ЛР №9.txt
new file mode 100644
index 0000000..48399f8
--- /dev/null
+++ b/labs/lab-09/other/Вопросы для подготовки к защите ЛР №9.txt
@@ -0,0 +1,16 @@
+Модальные окна
+1. Что такое модальное окно? В каких случаях оно используется?
+2. Какие способы создания модальных окон вы знаете? Приведите примеры.
+3. Каковы преимущества использования модального окна перед другими элементами интерфейса?
+4. Назовите основные элементы структуры HTML-кода для реализации модального окна.
+5. Как реализовать закрытие модального окна при нажатии на кнопку или за пределами самого окна?
+6. Как сделать так, чтобы при открытии модального окна основной контент страницы затемнялся?
+7. Какие стили CSS используются для позиционирования модального окна на странице?
+8. Как предотвратить скроллинг основного контента страницы при открытом модальном окне?
+
+Взаимодействие клиента и сервера
+1. Опишите процесс обработки ошибок на сервере и отправку сообщений об ошибках клиенту. Какие коды состояния HTTP обычно используются для различных типов ошибок?
+2. Что такое CORS (Cross-Origin Resource Sharing)? Для чего нужен этот механизм и как он реализуется на практике?
+3. Почему важно учитывать производительность и оптимизацию запросов при взаимодействии клиента с сервером? Приведите примеры способов повышения эффективности взаимодействия.
+4. Как можно организовать кэширование данных на стороне клиента и сервера? В чем преимущество использования кэширования и какие инструменты можно применять для этого?
+5. Чем отличается статическое содержимое от динамически генерируемого содержимого на сервере? Приведите примеры задач, требующих генерации динамических страниц.
\ No newline at end of file
diff --git a/labs/lab-09/other/Задание - Лабораторная работа 9.md b/labs/lab-09/other/Задание - Лабораторная работа 9.md
new file mode 100644
index 0000000..1d31797
--- /dev/null
+++ b/labs/lab-09/other/Задание - Лабораторная работа 9.md
@@ -0,0 +1,75 @@
+# Лабораторная работа № 9. Реализация страницы просмотра и управления оформленными заказами
+---
+
+Добавьте страницу просмотра истории заказов и реализуйте функции редактирования и удаления заказов.
+
+## Порядок выполнения
+
+1. Добавьте страницу "Заказы", разместите ссылку на неё в навигационном меню. Примерный макет страницы приведён ниже.
+ - На странице должен располагаться список заказов, оформленных ранее текущим пользователем. Сортировка списка – по убыванию даты оформления (т. е. сначала новые заказы).
+ - Для каждого заказа должны отображаться: порядковый номер в списке, дата оформления заказа, состав заказа (названия блюд, перечисленные через запятую), стоимость заказа, время доставки (для заказов ко времени, для остальных – подпись "Как можно скорее (с 7:00 до 23:00)"), кнопки "Подробнее", "Редактирование", "Удаление".
+ - Вместо кнопок можно использовать иконки (например, [Bootstrap Icons](https://icons.getbootstrap.com/)).
+
+2. Реализуйте функциональность кнопок "Подробнее", "Редактирование", "Удаление".
+ - По нажатию на кнопку "Подробнее" должно открываться модальное окно с полной информацией о заказе (см. макет ниже).
+ - По нажатию на кнопку "Редактирование" должно открываться модальное окно с формой редактирования заказа (см. макет ниже). Значения полей формы должны быть установлены значениями соответствующих полей редактируемого заказа. Для редактирования доступны поля full_name, email, phone, delivery_address, delivery_type, delivery_time, comment.
+ - По нажатию на кнопку "Удаление" должно открываться модальное окно подтверждения удаления (см. макет ниже).
+ - В правом верхнем углу каждого модального окна должен быть "крестик", по нажатию на который модальное окно закрывается. Внизу модального окна должны располагаться кнопки действий: для просмотра – "Ок" (закрытие окна), для редактирования – "Сохранить" (отправка данных на сервер) и "Отмена" (закрытие окна), для удаления – "Да" (отправка запроса на сервер) и "Отмена" (закрытие окна).
+ - При возникновении ошибок при отправке запроса или его обработке на стороне сервера пользователю должно быть отображено уведомление об ошибке. В случае успешного выполнения операции (редактирования, удаления), модальное окно должно быть скрыто, и пользователю должно быть отображено уведомление об успешном завершении операции (например, "Заказ успешно изменён"). Формат отображения уведомлений на усмотрение студента.
+ - При успешном изменении или удалении заказа список заказов должен быть обновлён (т. е. удалённый заказ должен быть убран из списка, а информация об изменившемся заказе должна быть обновлена).
+
+## Инструкция по работе с API
+
+Для получения доступа к API необходимо пройти процедуру авторизации. Для авторизации нужно указать в качестве параметра запроса api_key значение уникального ключа, который выдаётся каждому пользователю. Ключ представляет собой идентификатор UUIDv4, который является случайным 16-байтным номером (например, 123e4567-e89b-12d3-a456-426655440000).
+
+**Обратите внимание, что параметр api_key всегда передаётся в строке запроса.**
+
+Пользователь может просматривать, редактировать и удалять только свои заказы. В один момент времени в базе данных может быть не более 10 заказов, созданных одним и тем же пользователем.
+
+Если пользователь попробует совершить действие, не пройдя авторизацию, в качестве ответа на его запрос придёт сообщение:
+
+{"error": "Для получения доступа к API необходимо пройти процедуру авторизации. Для этого нужно передать в запросе персональный API Key."}
+
+При передаче параметров в POST- и PUT-запросах данные должны передаваться в теле запроса в формате application/json (нужно вручную сериализовать данные и установить значение заголовка Content-Type) или multipart/form-data (достаточно при отправке данных формы воспользоваться объектом FormData).
+
+При оформлении заказа в запросе к серверу должны быть указаны значения обязательных полей:
+
+|Название|Тип|Обязательное|Только для чтения|Примечание|
+|---|---|---|---|---|
+|id|Integer||Да|Устанавливается сервером.|
+|full_name|String|Да|||
+|email|String|Да||
+|subscribe|Boolean|Нет||Допустимы значения 0 и 1.|
+|phone|String|Да|||
+|delivery_address|String|Да|||
+|delivery_type|String|Да||Допустимы значения "now" и "by_time".|
+|delivery_time|Time|Нет||Значение передаётся в формате HH:MM. Доступное время доставки: с 7:00 до 23:00 с шагом 5 минут. Не должно быть пустым, если delivery_type=by_time. Не должно быть раньше текущего времени.|
+|comment|String|Нет|||
+|soup_id|Integer|Нет||Обязательность поля зависит от состава комбо.|
+|main_course_id|Integer|Нет||Обязательность поля зависит от состава комбо.|
+|salad_id|Integer|Нет||Обязательность поля зависит от состава комбо.|
+|drink_id|Integer|Да|||
+|dessert_id|Integer|Нет|||
+|created_at|DateTime||Да|Устанавливается сервером.|
+|updated_at|DateTime||Да|Устанавливается сервером.|
+|student_id|Integer||Да|Устанавливается сервером.|
+
+Доступные пользователю действия представлены в таблице:
+
+|Действие|Метод и путь|Формат ответа|Примечание|
+|---|---|---|---|
+|Получить данные всех блюд|GET /labs/api/dishes|JSON [{item1},{item2},...{itemN}]||
+|Получить данные конкретного блюда|GET /labs/api/dishes/{int:dish_id}|JSON {Item}|Вместо {int:dish_id} нужно подставить целое число – идентификатор блюда.|
+|Получить данные всех заказов|GET /labs/api/orders|JSON [{item1},{item2},...{itemN}]||
+|Получить данные конкретного заказа|GET /labs/api/orders/{int:order_id}|JSON {Item}|Вместо {int:order_id} нужно подставить целое число – идентификатор заказа.|
+|Создать новый заказ|POST /labs/api/orders|JSON {newItem}|Нужно передать значения всех обязательных полей.|
+|Изменить заказ|PUT /labs/api/orders/{int:order_id}|JSON {updateItem}|Вместо {int:order_id} нужно подставить целое число – идентификатор заказа. Достаточно передать только значения изменившихся полей.|
+|Удалить заказ|DELETE /labs/api/orders/{int:order_id}|JSON {Item}|Вместо {int:order_id} нужно подставить целое число – идентификатор заказа.|
+
+### Материалы для изучения
+[Что такое модальные окна [SkillBox]](https://skillbox.ru/media/code/chto-takoe-modalnye-okna-i-kak-ikh-effektivno-ispolzovat/)
+[Попап [Doka]](https://doka.guide/recipes/popup/)
+[Fetch [Learn JS]](https://learn.javascript.ru/fetch)
+[Fetch API [MDN]](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
+[fetch() [Doka]](https://doka.guide/js/fetch/)
+[Cross-Origin Resource Sharing (CORS) [MDN]](https://developer.mozilla.org/ru/docs/Web/HTTP/CORS)
\ No newline at end of file
diff --git a/labs/lab-09/styles/menu.css b/labs/lab-09/styles/menu.css
new file mode 100644
index 0000000..8e05438
--- /dev/null
+++ b/labs/lab-09/styles/menu.css
@@ -0,0 +1,298 @@
+.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;
+}
+
+.combo-note {
+ font-size: 14px;
+ color: #666;
+ font-style: italic;
+ text-align: center;
+ margin: 10px 0 0 0;
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ flex-wrap: wrap;
+ gap: 15px;
+}
+
+.section-header h2 {
+ margin: 0;
+}
+
+.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;
+}
+
+.remove-dish-btn {
+ background-color: tomato;
+ color: white;
+}
+
+.remove-dish-btn:hover {
+ background-color: #ff4500;
+}
+
+.order-panel {
+ position: sticky;
+ bottom: 20px;
+ background-color: white;
+ padding: 20px 40px;
+ margin: 0 auto;
+ max-width: 1200px;
+ border-radius: 15px;
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
+ z-index: 100;
+}
+
+.order-panel.hidden {
+ display: none;
+}
+
+.order-panel-content {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 20px;
+}
+
+.order-panel-info {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+}
+
+.order-panel-label {
+ font-size: 18px;
+ font-weight: 600;
+ margin: 0;
+}
+
+.order-panel-price {
+ font-size: 24px;
+ font-weight: 700;
+ color: #2d5016;
+ margin: 0;
+}
+
+.order-panel-button {
+ background-color: #2d5016;
+ color: white;
+ text-decoration: none;
+ padding: 15px 40px;
+ border-radius: 10px;
+ font-family: 'Montserrat', sans-serif;
+ font-size: 16px;
+ font-weight: 600;
+ transition: all 0.3s;
+}
+
+.order-panel-button:hover {
+ background-color: #3d6020;
+}
+
+.order-panel-button.disabled {
+ background-color: #ccc;
+ color: #888;
+ cursor: not-allowed;
+ pointer-events: none;
+}
+
+@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;
+ }
+
+ .order-panel-content {
+ flex-direction: column;
+ }
+}
+
+@media (max-width: 600px) {
+ .combo-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .dishes-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .order-panel {
+ padding: 15px 20px;
+ }
+
+ .order-panel-content {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .order-panel-button {
+ text-align: center;
+ }
+}
\ No newline at end of file
diff --git a/labs/lab-09/styles/order.css b/labs/lab-09/styles/order.css
new file mode 100644
index 0000000..aed29b7
--- /dev/null
+++ b/labs/lab-09/styles/order.css
@@ -0,0 +1,266 @@
+.empty-order-message {
+ text-align: center;
+ padding: 40px 20px;
+}
+
+.empty-order-message.hidden {
+ display: none;
+}
+
+.empty-order-message p {
+ font-size: 18px;
+ color: #666;
+}
+
+.empty-order-message a {
+ color: #2d5016;
+ text-decoration: underline;
+}
+
+.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="button"] {
+ background-color: #f1eee9;
+ color: #333;
+}
+
+.form-buttons button[type="button"]: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;
+}
+
+@media (max-width: 800px) {
+ .form-container {
+ grid-template-columns: 1fr;
+ gap: 20px;
+ }
+}
+
+@media (max-width: 600px) {
+ .form-container {
+ grid-template-columns: 1fr;
+ }
+
+ .form-buttons {
+ flex-direction: column;
+ }
+}
\ No newline at end of file
diff --git a/labs/lab-09/styles/orders.css b/labs/lab-09/styles/orders.css
new file mode 100644
index 0000000..6e88dbc
--- /dev/null
+++ b/labs/lab-09/styles/orders.css
@@ -0,0 +1,414 @@
+.orders-list {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.empty-orders-message {
+ text-align: center;
+ padding: 60px 20px;
+}
+
+.empty-orders-message.hidden {
+ display: none;
+}
+
+.empty-orders-message p {
+ font-size: 18px;
+ color: #666;
+ line-height: 1.6;
+}
+
+.empty-orders-message a {
+ color: #2d5016;
+ text-decoration: underline;
+ font-weight: 600;
+}
+
+.order-card {
+ background-color: white;
+ border-radius: 15px;
+ padding: 25px 30px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ transition: box-shadow 0.3s;
+}
+
+.order-card:hover {
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
+}
+
+.order-card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+ padding-bottom: 15px;
+ border-bottom: 2px solid #f0f0f0;
+}
+
+.order-number {
+ font-size: 20px;
+ font-weight: 700;
+ color: #2d5016;
+}
+
+.order-date {
+ font-size: 16px;
+ color: #666;
+}
+
+.order-card-body {
+ margin-bottom: 20px;
+}
+
+.order-info p {
+ margin: 10px 0;
+ font-size: 16px;
+ line-height: 1.6;
+}
+
+.order-info strong {
+ color: #2d5016;
+}
+
+.order-card-footer {
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.order-btn {
+ padding: 10px 20px;
+ border: none;
+ border-radius: 8px;
+ font-family: 'Montserrat', sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.3s;
+}
+
+.order-btn-view {
+ background-color: #2d5016;
+ color: white;
+}
+
+.order-btn-view:hover {
+ background-color: #3d6020;
+}
+
+.order-btn-edit {
+ background-color: #f1eee9;
+ color: #333;
+}
+
+.order-btn-edit:hover {
+ background-color: #e0ddd8;
+}
+
+.order-btn-delete {
+ background-color: tomato;
+ color: white;
+}
+
+.order-btn-delete:hover {
+ background-color: #ff4500;
+}
+
+.modal {
+ display: flex;
+ position: fixed;
+ z-index: 1000;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ justify-content: center;
+ align-items: center;
+}
+
+.modal.hidden {
+ display: none;
+}
+
+.modal-content {
+ background-color: white;
+ border-radius: 15px;
+ max-width: 600px;
+ width: 90%;
+ max-height: 90vh;
+ overflow-y: auto;
+ box-shadow: 0 5px 30px rgba(0, 0, 0, 0.3);
+}
+
+.modal-content-small {
+ max-width: 400px;
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20px 30px;
+ border-bottom: 1px solid #e0e0e0;
+}
+
+.modal-header h3 {
+ margin: 0;
+ font-size: 20px;
+ font-weight: 700;
+ color: #000;
+}
+
+.modal-close {
+ font-size: 28px;
+ font-weight: 400;
+ color: #999;
+ cursor: pointer;
+ line-height: 1;
+ transition: color 0.3s;
+}
+
+.modal-close:hover {
+ color: #333;
+}
+
+.modal-body {
+ padding: 20px 30px;
+}
+
+.modal-section {
+ margin-bottom: 25px;
+}
+
+.modal-section:last-child {
+ margin-bottom: 0;
+}
+
+.modal-section-title {
+ font-size: 16px;
+ font-weight: 700;
+ color: #000;
+ margin: 0 0 15px 0;
+}
+
+.modal-label {
+ font-size: 14px;
+ color: #666;
+ display: block;
+ margin-bottom: 5px;
+}
+
+.modal-value {
+ font-size: 16px;
+ color: #000;
+ display: block;
+}
+
+.modal-info-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 12px;
+}
+
+.modal-info-item {
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+}
+
+.modal-order-items {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-bottom: 15px;
+}
+
+.modal-order-item {
+ display: flex;
+ justify-content: space-between;
+ font-size: 16px;
+ color: #000;
+}
+
+.modal-order-item span:first-child {
+ color: #666;
+}
+
+.modal-total {
+ display: flex;
+ justify-content: space-between;
+ font-size: 16px;
+ font-weight: 700;
+ color: #000;
+ padding-top: 15px;
+ border-top: 1px solid #e0e0e0;
+}
+
+.modal-delete-text {
+ font-size: 16px;
+ color: #000;
+ text-align: center;
+ margin: 0;
+}
+
+.modal-body form label {
+ display: block;
+ font-size: 14px;
+ color: #000;
+ margin: 0 0 8px 0;
+ font-weight: 400;
+}
+
+.modal-body form input[type="text"],
+.modal-body form input[type="email"],
+.modal-body form input[type="tel"],
+.modal-body form input[type="time"],
+.modal-body form textarea {
+ width: 100%;
+ padding: 10px 12px;
+ margin: 0 0 15px 0;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+ font-family: 'Montserrat', sans-serif;
+ font-size: 15px;
+}
+
+.modal-body form textarea {
+ resize: vertical;
+}
+
+.modal-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ padding: 20px 30px;
+ border-top: 1px solid #e0e0e0;
+}
+
+.modal-btn {
+ padding: 10px 30px;
+ border: none;
+ border-radius: 5px;
+ font-family: 'Montserrat', sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.3s;
+ min-width: 100px;
+}
+
+.modal-btn-ok {
+ background-color: #e8e8e8;
+ color: #000;
+}
+
+.modal-btn-ok:hover {
+ background-color: #d0d0d0;
+}
+
+.modal-btn-cancel {
+ background-color: #e8e8e8;
+ color: #000;
+}
+
+.modal-btn-cancel:hover {
+ background-color: #d0d0d0;
+}
+
+.modal-btn-save {
+ background-color: #4caf50;
+ color: white;
+}
+
+.modal-btn-save:hover {
+ background-color: #45a049;
+}
+
+.modal-btn-delete {
+ background-color: #c62828;
+ color: white;
+}
+
+.modal-btn-delete:hover {
+ background-color: #b71c1c;
+}
+
+.notification {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ padding: 15px 25px;
+ border-radius: 10px;
+ font-family: 'Montserrat', sans-serif;
+ font-size: 16px;
+ font-weight: 600;
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
+ z-index: 2000;
+ animation: slideIn 0.3s ease-out;
+}
+
+.notification.hidden {
+ display: none;
+}
+
+.notification-success {
+ background-color: #4caf50;
+ color: white;
+}
+
+.notification-error {
+ background-color: #c62828;
+ color: white;
+}
+
+@keyframes slideIn {
+ from {
+ transform: translateX(400px);
+ opacity: 0;
+ }
+ to {
+ transform: translateX(0);
+ opacity: 1;
+ }
+}
+
+@media (max-width: 800px) {
+ .modal-content {
+ width: 95%;
+ }
+
+ .order-card-footer {
+ flex-direction: column;
+ }
+
+ .order-btn {
+ width: 100%;
+ }
+}
+
+@media (max-width: 600px) {
+ .modal-header {
+ padding: 15px 20px;
+ }
+
+ .modal-body {
+ padding: 15px 20px;
+ }
+
+ .modal-footer {
+ padding: 15px 20px;
+ flex-direction: column;
+ }
+
+ .modal-btn {
+ width: 100%;
+ }
+
+ .notification {
+ right: 10px;
+ left: 10px;
+ text-align: center;
+ }
+}
\ No newline at end of file
diff --git a/labs/lab-09/styles/styles.css b/labs/lab-09/styles/styles.css
new file mode 100644
index 0000000..158c58c
--- /dev/null
+++ b/labs/lab-09/styles/styles.css
@@ -0,0 +1,180 @@
+* {
+ 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;
+}
+
+h2 {
+ font-size: 28px;
+ color: #2d5016;
+ margin: 0 0 20px 0;
+}
+
+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;
+}
+
+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;
+}
+
+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: 600px) {
+ h1 {
+ text-align: center;
+ }
+
+ nav {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ nav a {
+ font-size: 20px;
+ }
+
+ section h2 {
+ text-align: center;
+ }
+
+ .about-company img {
+ width: 100%;
+ }
+
+ table th,
+ table td {
+ font-size: 14px;
+ }
+}
+
+@media (max-width: 400px) {
+ table th,
+ table td {
+ font-size: 12px;
+ }
+}
\ No newline at end of file
diff --git a/labs/lab-09/templates/about.html b/labs/lab-09/templates/about.html
new file mode 100644
index 0000000..f09e109
--- /dev/null
+++ b/labs/lab-09/templates/about.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ О нас - ЭкоЛанч
+
+
+
+
+
+
+
+
ЭкоЛанч
+
+
+
+
+
+
О нашей компании
+
ЭкоЛанч - это команда профессионалов, которые заботятся о вашем здоровье и комфорте. Мы готовим вкусные и полезные блюда из свежих продуктов и доставляем их прямо в ваш офис.
+
Наша миссия - сделать здоровое питание доступным и удобным для каждого занятого человека в Москве.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/labs/lab-09/templates/delivery.html b/labs/lab-09/templates/delivery.html
new file mode 100644
index 0000000..2b510f8
--- /dev/null
+++ b/labs/lab-09/templates/delivery.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ Доставка - ЭкоЛанч
+
+
+
+
+
+
+
+
ЭкоЛанч
+
+
+
+
+
+
Условия доставки
+
Мы осуществляем доставку здоровых бизнес-ланчей по всей Москве. Доставка производится с понедельника по пятницу с 7:00 до 23:00.
+
Доставка по всей Москве в пределах МКАД осуществляется бесплатно!
+
+
+
+
+
+
\ No newline at end of file
diff --git a/labs/lab-09/templates/menu.html b/labs/lab-09/templates/menu.html
new file mode 100644
index 0000000..ecaf51a
--- /dev/null
+++ b/labs/lab-09/templates/menu.html
@@ -0,0 +1,199 @@
+
+
+
+
+
+
+ Собрать ланч - ЭкоЛанч
+
+
+
+
+
+
+
+
+