diff --git a/ init_db.php b/ init_db.php
deleted file mode 100644
index 901a438..0000000
--- a/ init_db.php
+++ /dev/null
@@ -1,122 +0,0 @@
-getConnection();
-
-// Создаем таблицы, если они не существуют
-$tables = [
- 'users' => "
- CREATE TABLE IF NOT EXISTS users (
- user_id SERIAL PRIMARY KEY,
- email VARCHAR(255) UNIQUE NOT NULL,
- password_hash VARCHAR(255) NOT NULL,
- full_name VARCHAR(100) NOT NULL,
- phone VARCHAR(20),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- is_active BOOLEAN DEFAULT TRUE,
- is_admin BOOLEAN DEFAULT FALSE
- )
- ",
-
- 'categories' => "
- CREATE TABLE IF NOT EXISTS categories (
- category_id SERIAL PRIMARY KEY,
- name VARCHAR(100) NOT NULL,
- slug VARCHAR(100) UNIQUE NOT NULL,
- parent_id INTEGER REFERENCES categories(category_id),
- description TEXT,
- sort_order INTEGER DEFAULT 0,
- is_active BOOLEAN DEFAULT TRUE
- )
- ",
-
- 'products' => "
- CREATE TABLE IF NOT EXISTS products (
- product_id SERIAL PRIMARY KEY,
- category_id INTEGER REFERENCES categories(category_id),
- name VARCHAR(200) NOT NULL,
- slug VARCHAR(200) UNIQUE NOT NULL,
- description TEXT,
- price DECIMAL(10, 2) NOT NULL,
- old_price DECIMAL(10, 2),
- sku VARCHAR(50) UNIQUE,
- stock_quantity INTEGER DEFAULT 0,
- is_available BOOLEAN DEFAULT TRUE,
- is_featured BOOLEAN DEFAULT FALSE,
- rating DECIMAL(3, 2) DEFAULT 0,
- review_count INTEGER DEFAULT 0,
- image_url VARCHAR(500),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- )
- "
-];
-
-foreach ($tables_to_create as $table_name => $sql) {
- try {
- $db->exec($sql);
- echo "Таблица '$table_name' создана/проверена ";
- } catch (PDOException $e) {
- echo "Ошибка создания таблицы '$table_name': " . $e->getMessage() . " ";
- }
-}
-
-// Добавляем тестовые данные
-// Проверяем, есть ли уже категории
-$check_categories = $db->query("SELECT COUNT(*) FROM categories")->fetchColumn();
-
-if ($check_categories == 0) {
- // Добавляем категории
- $categories = [
- ['Мягкая мебель', 'myagkaya-mebel', NULL, 'Диваны, кресла, пуфы'],
- ['Диваны', 'divany', 1, 'Прямые и угловые диваны'],
- ['Кресла', 'kresla', 1, 'Кресла для гостиной и офиса'],
- ['Спальня', 'spalnya', NULL, 'Кровати, тумбы, комоды'],
- ['Кровати', 'krovati', 4, 'Односпальные и двуспальные кровати']
- ];
-
- foreach ($categories as $category) {
- $stmt = $db->prepare("INSERT INTO categories (name, slug, parent_id, description) VALUES (?, ?, ?, ?)");
- $stmt->execute($category);
- }
- echo "Добавлены категории ";
-}
-
-// Проверяем, есть ли уже товары
-$check_products = $db->query("SELECT COUNT(*) FROM products")->fetchColumn();
-
-if ($check_products == 0) {
- // Добавляем товары
- $products = [
- [2, 'Диван VELVET', 'divan-velvet', 'Прямой диван с тканевой обивкой', 45999, 54999, 'DIV-VEL-001', 10],
- [2, 'Диван MODERN', 'divan-modern', 'Угловой диван с кожаной обивкой', 78999, 89999, 'DIV-MOD-002', 5],
- [3, 'Кресло OPPORTUNITY', 'kreslo-opportunity', 'Кресло с деревянными ножками', 16999, 19999, 'KRES-OPP-001', 15],
- [3, 'Кресло GOLDEN', 'kreslo-golden', 'Золотистое кресло для гостиной', 19999, 23999, 'KRES-GOL-002', 8],
- [5, 'Кровать CLASSIC', 'krovat-classic', 'Двуспальная кровать из массива дуба', 64999, 74999, 'KROV-CLA-001', 3]
- ];
-
- foreach ($products as $product) {
- $stmt = $db->prepare("INSERT INTO products (category_id, name, slug, description, price, old_price, sku, stock_quantity)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
- $stmt->execute($product);
- }
- echo "Добавлены товары ";
-}
-
-// Проверяем, есть ли администратор
-$check_admin = $db->prepare("SELECT COUNT(*) FROM users WHERE email = ?");
-$check_admin->execute(['admin@aeterna.ru']);
-
-if ($check_admin->fetchColumn() == 0) {
- // Добавляем администратора (пароль: admin123)
- $admin_password = password_hash('admin123', PASSWORD_DEFAULT);
- $stmt = $db->prepare("INSERT INTO users (email, password_hash, full_name, phone, is_admin)
- VALUES (?, ?, ?, ?, ?)");
- $stmt->execute(['admin@aeterna.ru', $admin_password, 'Администратор AETERNA', '+79129991223', true]);
- echo "Добавлен администратор (email: admin@aeterna.ru, пароль: admin123) ";
-}
-
-echo "
База данных успешно инициализирована! ";
-echo "Перейти в каталог ";
-?>
\ No newline at end of file
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..06c7753
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,8 @@
+.git
+.gitignore
+.DS_Store
+node_modules
+*.log
+.env
+.env.local
+
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..a1811fb
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,10 @@
+FROM php:8.2-apache
+
+RUN apt-get update && apt-get install -y libpq-dev \
+ && docker-php-ext-install pdo pdo_pgsql \
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+RUN a2enmod rewrite headers alias
+
+WORKDIR /var/www/html
+
diff --git a/admin_actions.php b/admin_actions.php
deleted file mode 100644
index ea9563b..0000000
--- a/admin_actions.php
+++ /dev/null
@@ -1,170 +0,0 @@
-getConnection();
-$action = $_GET['action'] ?? '';
-
-try {
- switch ($action) {
- case 'delete_product':
- if (isset($_GET['id'])) {
- $productId = intval($_GET['id']);
- // Делаем товар недоступным
- $stmt = $db->prepare("
- UPDATE products
- SET is_available = FALSE, stock_quantity = 0, updated_at = CURRENT_TIMESTAMP
- WHERE product_id = ?
- ");
- $stmt->execute([$productId]);
- header('Location: admin_panel.php?action=products&message=Товар помечен как недоступный');
- exit();
- }
- break;
-
- case 'restore_product':
- if (isset($_GET['id'])) {
- $productId = intval($_GET['id']);
- // Восстанавливаем товар
- $stmt = $db->prepare("
- UPDATE products
- SET is_available = TRUE, stock_quantity = 10, updated_at = CURRENT_TIMESTAMP
- WHERE product_id = ?
- ");
- $stmt->execute([$productId]);
- header('Location: admin_panel.php?action=products&message=Товар восстановлен');
- exit();
- }
- break;
-
- case 'delete_category':
- if (isset($_GET['id'])) {
- $categoryId = intval($_GET['id']);
-
- try {
- // 1. Проверяем, есть ли товары в этой категории
- $checkProducts = $db->prepare("SELECT COUNT(*) FROM products WHERE category_id = ?");
- $checkProducts->execute([$categoryId]);
- $productCount = $checkProducts->fetchColumn();
-
- if ($productCount > 0) {
- // Если есть товары, делаем категорию неактивной
- $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?");
- $stmt->execute([$categoryId]);
- header('Location: admin_panel.php?action=categories&message=Категория скрыта (содержит товары)');
- exit();
- }
-
- // 2. Проверяем, есть ли дочерние категории
- $checkChildren = $db->prepare("SELECT COUNT(*) FROM categories WHERE parent_id = ?");
- $checkChildren->execute([$categoryId]);
- $childCount = $checkChildren->fetchColumn();
-
- if ($childCount > 0) {
- // Вариант A: Делаем категорию неактивной
- $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?");
- $stmt->execute([$categoryId]);
- header('Location: admin_panel.php?action=categories&message=Категория скрыта (имеет дочерние категории)');
- exit();
-
- // Вариант B: Удаляем вместе с дочерними (раскомментируйте если нужно)
- /*
- // Сначала удаляем дочерние категории
- $stmt = $db->prepare("DELETE FROM categories WHERE parent_id = ?");
- $stmt->execute([$categoryId]);
-
- // Затем удаляем саму категорию
- $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
- $stmt->execute([$categoryId]);
-
- header('Location: admin_panel.php?action=categories&message=Категория и её дочерние категории удалены');
- exit();
- */
- }
-
- // 3. Если нет товаров и дочерних категорий, удаляем
- $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
- $stmt->execute([$categoryId]);
-
- header('Location: admin_panel.php?action=categories&message=Категория удалена');
- exit();
-
- } catch (PDOException $e) {
- header('Location: admin_panel.php?action=categories&error=' . urlencode($e->getMessage()));
- exit();
- }
- }
- break;
-
- case 'delete_category_force':
- // Принудительное удаление с дочерними категориями
- if (isset($_GET['id'])) {
- $categoryId = intval($_GET['id']);
-
- try {
- // Сначала перемещаем товары в другую категорию (например, в первую)
- $firstCategory = $db->query("SELECT category_id FROM categories WHERE category_id != ? LIMIT 1")->fetchColumn();
-
- if ($firstCategory) {
- $moveProducts = $db->prepare("UPDATE products SET category_id = ? WHERE category_id = ?");
- $moveProducts->execute([$firstCategory, $categoryId]);
- }
-
- // Обнуляем parent_id у дочерних категорий
- $stmt = $db->prepare("UPDATE categories SET parent_id = NULL WHERE parent_id = ?");
- $stmt->execute([$categoryId]);
-
- // Удаляем категорию
- $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
- $stmt->execute([$categoryId]);
-
- header('Location: admin_panel.php?action=categories&message=Категория удалена. Товары перемещены.');
- exit();
-
- } catch (PDOException $e) {
- header('Location: admin_panel.php?action=categories&error=' . urlencode($e->getMessage()));
- exit();
- }
- }
- break;
-
- case 'toggle_user':
- if (isset($_GET['id'])) {
- $userId = intval($_GET['id']);
- $stmt = $db->prepare("
- UPDATE users
- SET is_active = NOT is_active, updated_at = CURRENT_TIMESTAMP
- WHERE user_id = ?
- ");
- $stmt->execute([$userId]);
- header('Location: admin_panel.php?action=users&message=Статус пользователя изменен');
- exit();
- }
- break;
-
- case 'make_admin':
- if (isset($_GET['id'])) {
- $userId = intval($_GET['id']);
- $stmt = $db->prepare("UPDATE users SET is_admin = TRUE WHERE user_id = ?");
- $stmt->execute([$userId]);
- header('Location: admin_panel.php?action=users&message=Пользователь назначен администратором');
- exit();
- }
- break;
- }
-} catch (PDOException $e) {
- header('Location: admin_panel.php?error=' . urlencode($e->getMessage()));
- exit();
-}
-
-// Если действие не распознано
-header('Location: admin_panel.php');
-exit();
-?>
\ No newline at end of file
diff --git a/catalog_admin.php b/catalog_admin.php
deleted file mode 100644
index 524f499..0000000
--- a/catalog_admin.php
+++ /dev/null
@@ -1,924 +0,0 @@
-getConnection();
-
- // ОБРАБОТКА POST ЗАПРОСОВ ДЛЯ КАТЕГОРИЙ
- if ($_SERVER['REQUEST_METHOD'] === 'POST' && $is_admin) {
- $form_action = $_POST['action'] ?? '';
-
- if ($form_action === 'add_category' || $form_action === 'edit_category') {
- $name = trim($_POST['name'] ?? '');
- $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : null;
- $description = trim($_POST['description'] ?? '');
- $sort_order = (int)($_POST['sort_order'] ?? 0);
- $is_active = isset($_POST['is_active']) ? 1 : 0;
- $category_id_post = (int)($_POST['category_id'] ?? 0);
-
- // Валидация
- if (empty($name)) {
- $_SESSION['error'] = 'Название категории обязательно';
- header('Location: catalog_admin.php?action=' . $form_action . ($category_id_post ? '&id=' . $category_id_post : ''));
- exit();
- }
-
- // Создаем slug
- $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
- $slug = preg_replace('/^-+|-+$/', '', $slug); // Убираем дефисы по краям
-
- if ($form_action === 'add_category') {
- // Проверяем существование slug
- $check = $db->prepare("SELECT COUNT(*) FROM categories WHERE slug = ?");
- $check->execute([$slug]);
- if ($check->fetchColumn() > 0) {
- $slug = $slug . '-' . time(); // Добавляем timestamp для уникальности
- }
-
- $sql = "INSERT INTO categories (name, slug, parent_id, description, sort_order, is_active)
- VALUES (?, ?, ?, ?, ?, ?)";
- $stmt = $db->prepare($sql);
- $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active]);
-
- $_SESSION['message'] = 'Категория успешно добавлена';
- header('Location: catalog_admin.php?action=categories');
- exit();
-
- } elseif ($form_action === 'edit_category' && $category_id_post > 0) {
- // Проверяем существование slug для других категорий
- $check = $db->prepare("SELECT COUNT(*) FROM categories WHERE slug = ? AND category_id != ?");
- $check->execute([$slug, $category_id_post]);
- if ($check->fetchColumn() > 0) {
- $slug = $slug . '-' . $category_id_post;
- }
-
- $sql = "UPDATE categories SET
- name = ?, slug = ?, parent_id = ?, description = ?,
- sort_order = ?, is_active = ?, updated_at = CURRENT_TIMESTAMP
- WHERE category_id = ?";
- $stmt = $db->prepare($sql);
- $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active, $category_id_post]);
-
- $_SESSION['message'] = 'Категория успешно обновлена';
- header('Location: catalog_admin.php?action=categories');
- exit();
- }
- }
-
- // Удаление категории
- if ($form_action === 'delete_category') {
- $category_id_del = (int)($_POST['category_id'] ?? 0);
-
- if ($category_id_del > 0) {
- // Проверяем наличие активных товаров
- $check_products = $db->prepare("
- SELECT COUNT(*) as product_count
- FROM products
- WHERE category_id = ? AND is_available = TRUE
- ");
- $check_products->execute([$category_id_del]);
- $active_products = $check_products->fetchColumn();
-
- if ($active_products > 0) {
- $_SESSION['error'] = 'Невозможно удалить категорию с активными товарами';
- header('Location: catalog_admin.php?action=categories');
- exit();
- }
-
- // Проверяем дочерние категории
- $check_children = $db->prepare("
- SELECT COUNT(*) as child_count
- FROM categories
- WHERE parent_id = ? AND is_active = TRUE
- ");
- $check_children->execute([$category_id_del]);
- $active_children = $check_children->fetchColumn();
-
- if ($active_children > 0) {
- $_SESSION['error'] = 'Невозможно удалить категорию с активными дочерними категориями';
- header('Location: catalog_admin.php?action=categories');
- exit();
- }
-
- // Удаляем категорию
- $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
- $stmt->execute([$category_id_del]);
-
- $_SESSION['message'] = 'Категория успешно удалена';
- header('Location: catalog_admin.php?action=categories');
- exit();
- }
- }
- }
-
- // Получаем категории для отображения
- $categories_stmt = $db->query("
- SELECT c1.*, c2.name as parent_name,
- (SELECT COUNT(*) FROM products WHERE category_id = c1.category_id) as product_count
- FROM categories c1
- LEFT JOIN categories c2 ON c1.parent_id = c2.category_id
- ORDER BY c1.sort_order, c1.name
- ");
- $categories = $categories_stmt->fetchAll();
-
- // Для редактирования категории
- if ($action === 'edit_category' && $product_id > 0) {
- $cat_stmt = $db->prepare("SELECT * FROM categories WHERE category_id = ?");
- $cat_stmt->execute([$product_id]);
- $current_category = $cat_stmt->fetch();
-
- if (!$current_category) {
- $_SESSION['error'] = 'Категория не найдена';
- header('Location: catalog_admin.php?action=categories');
- exit();
- }
- }
-
- // Получаем товары
- $sql = "SELECT p.*, c.name as category_name FROM products p
- LEFT JOIN categories c ON p.category_id = c.category_id
- WHERE p.is_available = TRUE";
-
- $params = [];
- if ($category_id && is_numeric($category_id)) {
- $sql .= " AND p.category_id = ?";
- $params[] = $category_id;
- }
-
- $sql .= " ORDER BY p.product_id DESC";
-
- if ($params) {
- $stmt = $db->prepare($sql);
- $stmt->execute($params);
- } else {
- $stmt = $db->query($sql);
- }
-
- $products = $stmt->fetchAll();
-
- // Сообщения из сессии
- $message = $_SESSION['message'] ?? '';
- $error = $_SESSION['error'] ?? '';
-
- // Очищаем сообщения после использования
- unset($_SESSION['message']);
- unset($_SESSION['error']);
-
-} catch (PDOException $e) {
- $error = "Ошибка подключения к базе данных: " . $e->getMessage();
-}
-?>
-
-
-
-
-
-
- AETERNA - Каталог
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- = htmlspecialchars($message) ?>
-
-
-
-
-
- = htmlspecialchars($error) ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ID
- Название
- Родительская
- Товаров
- Порядок
- Статус
- Действия
-
-
-
-
-
- = $cat['category_id'] ?>
-
- = htmlspecialchars($cat['name']) ?>
- = htmlspecialchars($cat['slug']) ?>
-
- = htmlspecialchars($cat['parent_name'] ?? '—') ?>
-
- = $cat['product_count'] ?> товаров
-
- = $cat['sort_order'] ?>
-
-
- Активна
-
- Неактивна
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $product['price']): ?>
-
- -= round(($product['old_price'] - $product['price']) / $product['old_price'] * 100) ?>%
-
-
-
-
-
-
-
-
= htmlspecialchars($product['name']) ?>
-
- = htmlspecialchars(mb_substr($product['description'], 0, 100)) ?>...
-
-
- $product['price']): ?>
-
- = number_format($product['old_price'], 0, '', ' ') ?> ₽
-
-
- = number_format($product['price'], 0, '', ' ') ?> ₽
-
-
-
- В наличии: = $product['stock_quantity'] ?> шт.
-
-
-
- В корзину
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-getConnection();
-$action = $_POST['action'] ?? '';
-
-if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- try {
- switch ($action) {
- case 'add_category':
- $name = $_POST['name'] ?? '';
- $parent_id = $_POST['parent_id'] ?: null;
- $description = $_POST['description'] ?? null;
- $sort_order = $_POST['sort_order'] ?? 0;
- $is_active = isset($_POST['is_active']) ? 1 : 0;
-
- // Создаем slug из названия
- $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
-
- $stmt = $db->prepare("
- INSERT INTO categories (name, slug, parent_id, description, sort_order, is_active)
- VALUES (?, ?, ?, ?, ?, ?)
- ");
-
- $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active]);
-
- header('Location: catalog_admin.php?action=categories&message=Категория успешно добавлена');
- exit();
-
- case 'edit_category':
- $category_id = $_POST['category_id'] ?? 0;
- $name = $_POST['name'] ?? '';
- $parent_id = $_POST['parent_id'] ?: null;
- $description = $_POST['description'] ?? null;
- $sort_order = $_POST['sort_order'] ?? 0;
- $is_active = isset($_POST['is_active']) ? 1 : 0;
-
- // Создаем slug из названия
- $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
-
- $stmt = $db->prepare("
- UPDATE categories SET
- name = ?,
- slug = ?,
- parent_id = ?,
- description = ?,
- sort_order = ?,
- is_active = ?
- WHERE category_id = ?
- ");
-
- $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active, $category_id]);
-
- header('Location: catalog_admin.php?action=categories&message=Категория успешно обновлена');
- exit();
-
- case 'delete_category':
- $category_id = $_POST['category_id'] ?? 0;
-
- // Проверяем, есть ли активные товары в этой категории
- $checkStmt = $db->prepare("
- SELECT COUNT(*)
- FROM products
- WHERE category_id = ? AND is_available = TRUE
- ");
- $checkStmt->execute([$category_id]);
- $active_products = $checkStmt->fetchColumn();
-
- if ($active_products > 0) {
- header('Location: catalog_admin.php?action=categories&error=Невозможно удалить категорию с активными товарами');
- exit();
- }
-
- // Проверяем дочерние категории
- $checkChildStmt = $db->prepare("
- SELECT COUNT(*)
- FROM categories
- WHERE parent_id = ? AND is_active = TRUE
- ");
- $checkChildStmt->execute([$category_id]);
- $active_children = $checkChildStmt->fetchColumn();
-
- if ($active_children > 0) {
- header('Location: catalog_admin.php?action=categories&error=Невозможно удалить категорию с активными дочерними категориями');
- exit();
- }
-
- // Удаляем категорию
- $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
- $stmt->execute([$category_id]);
-
- header('Location: catalog_admin.php?action=categories&message=Категория успешно удалена');
- exit();
- }
-
- } catch (PDOException $e) {
- header('Location: catalog_admin.php?action=categories&error=' . urlencode('Ошибка базы данных: ' . $e->getMessage()));
- exit();
- }
-}
-
-header('Location: catalog_admin.php');
-exit();
-?>
\ No newline at end of file
diff --git a/check_admin.php b/check_admin.php
deleted file mode 100644
index e64c585..0000000
--- a/check_admin.php
+++ /dev/null
@@ -1,17 +0,0 @@
-
\ No newline at end of file
diff --git a/check_auth_status.php b/check_auth_status.php
deleted file mode 100644
index 5ddf663..0000000
--- a/check_auth_status.php
+++ /dev/null
@@ -1,21 +0,0 @@
- isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true
-];
-
-if ($response['loggedIn']) {
- $response['user'] = [
- 'user_id' => $_SESSION['user_id'] ?? 0,
- 'email' => $_SESSION['user_email'] ?? '',
- 'full_name' => $_SESSION['full_name'] ?? '',
- 'is_admin' => $_SESSION['isAdmin'] ?? false,
- 'login_time' => $_SESSION['login_time'] ?? time()
- ];
-}
-
-header('Content-Type: application/json');
-echo json_encode($response);
-?>
\ No newline at end of file
diff --git a/check_categories_table.php b/check_categories_table.php
deleted file mode 100644
index 6fa72ca..0000000
--- a/check_categories_table.php
+++ /dev/null
@@ -1,51 +0,0 @@
-getConnection();
-
-echo "Проверка категорий в базе данных ";
-
-try {
- $stmt = $db->query("SELECT category_id, name, slug, parent_id FROM categories ORDER BY category_id");
- $categories = $stmt->fetchAll();
-
- if (empty($categories)) {
- echo "Категорий нет! Нужно сначала добавить категории.
";
-
- // Добавим тестовые категории
- $insert_sql = "
- INSERT INTO categories (name, slug, parent_id, description) VALUES
- ('Мягкая мебель', 'myagkaya-mebel', NULL, 'Диваны, кресла, пуфы'),
- ('Диваны', 'divany', 1, 'Прямые и угловые диваны'),
- ('Кресла', 'kresla', 1, 'Кресла для гостиной и офиса'),
- ('Спальня', 'spalnya', NULL, 'Кровати, тумбы, комоды'),
- ('Кровати', 'krovati', 4, 'Односпальные и двуспальные кровати')
- RETURNING category_id
- ";
-
- $db->exec($insert_sql);
- echo "Добавлены тестовые категории
";
-
- // Снова проверим
- $stmt = $db->query("SELECT category_id, name, slug, parent_id FROM categories ORDER BY category_id");
- $categories = $stmt->fetchAll();
- }
-
- echo "";
- echo "ID Название Slug Родитель ";
-
- foreach ($categories as $category) {
- echo "";
- echo "" . $category['category_id'] . " ";
- echo "" . htmlspecialchars($category['name']) . " ";
- echo "" . htmlspecialchars($category['slug']) . " ";
- echo "" . ($category['parent_id'] ?: '-') . " ";
- echo " ";
- }
-
- echo "
";
-
-} catch (PDOException $e) {
- echo "Ошибка: " . $e->getMessage();
-}
-?>
\ No newline at end of file
diff --git a/config/check_auth.js b/config/check_auth.js
deleted file mode 100644
index e67bbe1..0000000
--- a/config/check_auth.js
+++ /dev/null
@@ -1,114 +0,0 @@
-// check_auth.js
-$(document).ready(function() {
- // Проверка авторизации при загрузке страницы
- checkAuthStatus();
-
- // Обработка формы входа
- $('#loginForm').on('submit', function(e) {
- e.preventDefault();
-
- const email = $('#login-email').val();
- const password = $('#login-password').val();
- const remember = $('#remember').is(':checked');
-
- $.ajax({
- url: 'login_handler.php',
- method: 'POST',
- data: {
- email: email,
- password: password
- },
- success: function(response) {
- try {
- const result = JSON.parse(response);
- if (result.success) {
- // Сохраняем в localStorage если выбрано "Запомнить меня"
- if (remember) {
- localStorage.setItem('rememberedEmail', email);
- } else {
- localStorage.removeItem('rememberedEmail');
- }
-
- // Перенаправляем
- window.location.href = result.redirect || 'catalog.php';
- } else {
- showMessage('error', result.message || 'Ошибка авторизации');
- }
- } catch(e) {
- showMessage('error', 'Ошибка обработки ответа');
- }
- },
- error: function() {
- showMessage('error', 'Ошибка сервера');
- }
- });
- });
-
- // Проверка статуса авторизации
- function checkAuthStatus() {
- $.ajax({
- url: 'check_auth_status.php',
- method: 'GET',
- success: function(response) {
- try {
- const result = JSON.parse(response);
- if (result.loggedIn) {
- updateUserProfile(result.user);
- }
- } catch(e) {
- console.error('Ошибка проверки авторизации', e);
- }
- }
- });
- }
-
- // Обновление профиля пользователя
- function updateUserProfile(user) {
- // Обновляем шапку, если есть элементы для профиля
- if ($('#userEmail').length) {
- $('#userEmail').text(user.email);
- }
- if ($('#userName').length) {
- $('#userName').text(user.full_name);
- }
- }
-
- // Показать сообщение
- function showMessage(type, text) {
- const $message = $('#' + type + 'Message');
- if ($message.length) {
- $message.text(text).fadeIn();
- setTimeout(() => $message.fadeOut(), 5000);
- } else {
- alert(text);
- }
- }
-
- // Проверка авторизации для ссылок
- function checkAuth(redirectUrl) {
- $.ajax({
- url: 'check_auth_status.php',
- method: 'GET',
- success: function(response) {
- try {
- const result = JSON.parse(response);
- if (result.loggedIn) {
- window.location.href = redirectUrl;
- } else {
- // Показываем модальное окно или перенаправляем на вход
- showLoginModal(redirectUrl);
- }
- } catch(e) {
- showLoginModal(redirectUrl);
- }
- }
- });
- return false;
- }
-
- // Показать модальное окно входа
- function showLoginModal(redirectUrl) {
- // Можно реализовать модальное окно или перенаправить на страницу входа
- window.location.href = 'вход.php?redirect=' + encodeURIComponent(redirectUrl);
- }
-});
\ No newline at end of file
diff --git a/debug_db.php b/debug_db.php
deleted file mode 100644
index 32ca002..0000000
--- a/debug_db.php
+++ /dev/null
@@ -1,55 +0,0 @@
-getConnection();
-
-echo "Проверка базы данных: ";
-
-// Проверка таблиц
-$tables = ['users', 'categories', 'products', 'orders', 'order_items', 'cart'];
-foreach ($tables as $table) {
- try {
- $result = $db->query("SELECT COUNT(*) FROM $table")->fetchColumn();
- echo "✅ Таблица '$table': $result записей ";
- } catch (Exception $e) {
- echo "❌ Таблица '$table': НЕ СУЩЕСТВУЕТ ";
- }
-}
-
-echo "Содержимое таблиц: ";
-
-// Показать категории
-echo "Категории: ";
-try {
- $categories = $db->query("SELECT * FROM categories")->fetchAll();
- if (empty($categories)) {
- echo "Категорий нет! ";
- } else {
- echo "ID Название Slug Родитель ";
- foreach ($categories as $cat) {
- echo "{$cat['category_id']} {$cat['name']} {$cat['slug']} {$cat['parent_id']} ";
- }
- echo "
";
- }
-} catch (Exception $e) {
- echo "Ошибка: " . $e->getMessage();
-}
-
-// Показать товары
-echo "Товары: ";
-try {
- $products = $db->query("SELECT * FROM products")->fetchAll();
- if (empty($products)) {
- echo "Товаров нет! ";
- } else {
- echo "ID Название Цена Категория Статус ";
- foreach ($products as $product) {
- echo "{$product['product_id']} {$product['name']} {$product['price']} {$product['category_id']} " . ($product['is_available'] ? 'Активен' : 'Неактивен') . " ";
- }
- echo "
";
- }
-} catch (Exception $e) {
- echo "Ошибка: " . $e->getMessage();
-}
-?>
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..d89d53b
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,27 @@
+version: '3.8'
+
+services:
+ apache:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: cite_practica_apache
+ ports:
+ - "80:80"
+ volumes:
+ - ./public:/var/www/html/cite_practica:rw
+ - ./docker/apache/vhosts.conf:/etc/apache2/sites-available/000-default.conf:ro
+ environment:
+ - APACHE_DOCUMENT_ROOT=/var/www/html/cite_practica
+ command: >
+ bash -c "
+ echo '127.0.0.1 admin' >> /etc/hosts &&
+ apache2-foreground
+ "
+ networks:
+ - cite_practica_network
+ restart: unless-stopped
+
+networks:
+ cite_practica_network:
+ driver: bridge
diff --git a/docker/apache/entrypoint.sh b/docker/apache/entrypoint.sh
new file mode 100755
index 0000000..f3844b9
--- /dev/null
+++ b/docker/apache/entrypoint.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -e
+
+a2enmod rewrite
+a2enmod headers
+
+echo "127.0.0.1 admin" >> /etc/hosts
+
+a2ensite 000-default
+
+exec apache2-foreground
+
diff --git a/docker/apache/vhosts.conf b/docker/apache/vhosts.conf
new file mode 100644
index 0000000..c5833bd
--- /dev/null
+++ b/docker/apache/vhosts.conf
@@ -0,0 +1,26 @@
+
+ ServerName admin
+ ServerAlias localhost
+ DocumentRoot /var/www/html
+ Alias /cite_practica /var/www/html/cite_practica
+
+
+ Options Indexes FollowSymLinks
+ AllowOverride All
+ Require all granted
+
+
+
+ Options Indexes FollowSymLinks
+ AllowOverride All
+ Require all granted
+ DirectoryIndex cite_mebel.php index.php index.html
+
+
+
+ SetHandler application/x-httpd-php
+
+
+ ErrorLog ${APACHE_LOG_DIR}/cite_practica_error.log
+ CustomLog ${APACHE_LOG_DIR}/cite_practica_access.log combined
+
diff --git a/fix_categories.php b/fix_categories.php
deleted file mode 100644
index 62503c1..0000000
--- a/fix_categories.php
+++ /dev/null
@@ -1,69 +0,0 @@
-getConnection();
-
-echo "Исправление проблем с категориями ";
-
-try {
- // 1. Удаляем категорию с ID=0 если она есть
- $db->exec("DELETE FROM categories WHERE category_id = 0");
-
- // 2. Проверяем, есть ли категории
- $catCount = $db->query("SELECT COUNT(*) FROM categories")->fetchColumn();
-
- if ($catCount == 0) {
- echo "Добавляем основные категории...
";
- $db->exec("
- INSERT INTO categories (name, slug, description, is_active) VALUES
- ('Диваны', 'divany', 'Мягкая мебель для гостиной', TRUE),
- ('Кресла', 'kresla', 'Кресла для гостиной и офиса', TRUE),
- ('Кровати', 'krovati', 'Мебель для спальни', TRUE),
- ('Столы', 'stoly', 'Обеденные и рабочие столы', TRUE),
- ('Стулья', 'stulya', 'Стулья для кухни и офиса', TRUE)
- ");
- echo "✓ Категории добавлены
";
- }
-
- // 3. Исправляем товары с category_id = 0 или NULL
- $badProducts = $db->query("
- SELECT COUNT(*) FROM products
- WHERE category_id IS NULL OR category_id = 0 OR
- category_id NOT IN (SELECT category_id FROM categories)
- ")->fetchColumn();
-
- if ($badProducts > 0) {
- echo "Исправляем товары с некорректными категориями ($badProducts шт)...
";
-
- // Получаем первую категорию
- $firstCat = $db->query("SELECT category_id FROM categories LIMIT 1")->fetchColumn();
-
- if ($firstCat) {
- $db->exec("
- UPDATE products
- SET category_id = $firstCat
- WHERE category_id IS NULL OR category_id = 0 OR
- category_id NOT IN (SELECT category_id FROM categories)
- ");
- echo "✓ Товары исправлены (category_id установлен в $firstCat)
";
- }
- }
-
- // 4. Показываем текущее состояние
- echo "Текущие категории: ";
- $cats = $db->query("SELECT category_id, name FROM categories ORDER BY category_id")->fetchAll();
-
- echo "";
- echo "ID Название ";
- foreach ($cats as $cat) {
- echo "{$cat['category_id']} {$cat['name']} ";
- }
- echo "
";
-
- echo "✓ База данных исправлена!
";
-
-} catch (PDOException $e) {
- echo "Ошибка: " . $e->getMessage() . "
";
-}
-?>
\ No newline at end of file
diff --git a/fix_database.php b/fix_database.php
deleted file mode 100644
index 9edc526..0000000
--- a/fix_database.php
+++ /dev/null
@@ -1,89 +0,0 @@
-getConnection();
-
-echo "Исправление проблем с базой данных ";
-
-try {
- // 1. Проверяем есть ли категории
- $stmt = $db->query("SELECT COUNT(*) FROM categories");
- $cat_count = $stmt->fetchColumn();
-
- if ($cat_count == 0) {
- echo "Добавляем тестовые категории...
";
- $db->exec("
- INSERT INTO categories (name, slug, description) VALUES
- ('Диваны', 'divany', 'Мягкая мебель для гостиной'),
- ('Кресла', 'kresla', 'Кресла для гостиной и офиса'),
- ('Кровати', 'krovati', 'Мебель для спальни')
- ");
- echo "✓ Категории добавлены
";
- }
-
- // 2. Проверяем товары с некорректными category_id
- $stmt = $db->query("
- SELECT COUNT(*) as bad_count
- FROM products
- WHERE category_id IS NULL OR category_id NOT IN (SELECT category_id FROM categories)
- ");
- $bad_count = $stmt->fetchColumn();
-
- if ($bad_count > 0) {
- echo "Исправляем товары с некорректными категориями ($bad_count шт)...
";
-
- // Устанавливаем первую доступную категорию
- $stmt = $db->query("SELECT category_id FROM categories LIMIT 1");
- $first_cat = $stmt->fetchColumn();
-
- if ($first_cat) {
- $db->exec("
- UPDATE products
- SET category_id = $first_cat
- WHERE category_id IS NULL OR category_id NOT IN (SELECT category_id FROM categories)
- ");
- echo "✓ Товары исправлены (установлена категория ID: $first_cat)
";
- }
- }
-
- // 3. Показываем текущее состояние
- echo "Текущее состояние: ";
-
- // Категории
- $stmt = $db->query("SELECT category_id, name FROM categories ORDER BY category_id");
- echo "Категории:
";
- while ($row = $stmt->fetch()) {
- echo "ID: {$row['category_id']} - {$row['name']} ";
- }
- echo " ";
-
- // Товары
- $stmt = $db->query("
- SELECT p.product_id, p.name, p.category_id, c.name as cat_name
- FROM products p
- LEFT JOIN categories c ON p.category_id = c.category_id
- ORDER BY p.product_id
- ");
-
- echo "Товары:
";
- echo "";
- echo "ID Название Категория ID Категория ";
-
- while ($row = $stmt->fetch()) {
- echo "";
- echo "{$row['product_id']} ";
- echo "" . htmlspecialchars($row['name']) . " ";
- echo "" . ($row['category_id'] ?: 'NULL') . " ";
- echo "" . ($row['cat_name'] ?: 'Без категории') . " ";
- echo " ";
- }
-
- echo "
";
-
- echo "✓ База данных исправлена!
";
-
-} catch (PDOException $e) {
- echo "Ошибка: " . $e->getMessage() . "
";
-}
-?>
\ No newline at end of file
diff --git a/header_common.php b/header_common.php
deleted file mode 100644
index 9b197d9..0000000
--- a/header_common.php
+++ /dev/null
@@ -1,142 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/image_upload.php b/image_upload.php
deleted file mode 100644
index b9833a9..0000000
--- a/image_upload.php
+++ /dev/null
@@ -1,53 +0,0 @@
- false, 'message' => 'Доступ запрещен']);
- exit();
-}
-
-if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['image'])) {
- $uploadDir = 'uploads/products/';
-
- // Создаем директорию если не существует
- if (!file_exists($uploadDir)) {
- mkdir($uploadDir, 0777, true);
- }
-
- $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
- $maxSize = 5 * 1024 * 1024; // 5MB
-
- $file = $_FILES['image'];
-
- // Проверка типа файла
- if (!in_array($file['type'], $allowedTypes)) {
- echo json_encode(['success' => false, 'message' => 'Допустимые форматы: JPEG, PNG, GIF, WebP']);
- exit();
- }
-
- // Проверка размера
- if ($file['size'] > $maxSize) {
- echo json_encode(['success' => false, 'message' => 'Максимальный размер файла: 5MB']);
- exit();
- }
-
- // Генерируем уникальное имя
- $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
- $fileName = 'product_' . time() . '_' . rand(1000, 9999) . '.' . $extension;
- $filePath = $uploadDir . $fileName;
-
- if (move_uploaded_file($file['tmp_name'], $filePath)) {
- echo json_encode([
- 'success' => true,
- 'url' => $filePath,
- 'name' => $fileName
- ]);
- } else {
- echo json_encode(['success' => false, 'message' => 'Ошибка загрузки файла']);
- }
-} else {
- echo json_encode(['success' => false, 'message' => 'Файл не получен']);
-}
-?>
\ No newline at end of file
diff --git a/img/2_2.jpg b/img/2_2.jpg
deleted file mode 100644
index 1f9b783..0000000
Binary files a/img/2_2.jpg and /dev/null differ
diff --git a/img/3.jpg b/img/3.jpg
deleted file mode 100644
index 134dd4e..0000000
Binary files a/img/3.jpg and /dev/null differ
diff --git a/img/4.jpg b/img/4.jpg
deleted file mode 100644
index c462942..0000000
Binary files a/img/4.jpg and /dev/null differ
diff --git a/img/6.jpg b/img/6.jpg
deleted file mode 100644
index f94188c..0000000
Binary files a/img/6.jpg and /dev/null differ
diff --git a/img/7.jpg b/img/7.jpg
deleted file mode 100644
index 8ba2fe5..0000000
Binary files a/img/7.jpg and /dev/null differ
diff --git a/img/8.jpg b/img/8.jpg
deleted file mode 100644
index e869ff9..0000000
Binary files a/img/8.jpg and /dev/null differ
diff --git a/img/9.jpg b/img/9.jpg
deleted file mode 100644
index 967e002..0000000
Binary files a/img/9.jpg and /dev/null differ
diff --git a/login.php b/login.php
deleted file mode 100644
index 9023dc1..0000000
--- a/login.php
+++ /dev/null
@@ -1,48 +0,0 @@
-getConnection();
-
- try {
- // Проверяем пользователя
- $stmt = $db->prepare("
- SELECT user_id, email, password_hash, full_name
- FROM users
- WHERE email = ? AND is_active = TRUE
- ");
- $stmt->execute([$email]);
- $user = $stmt->fetch();
-
- if ($user && password_verify($password, $user['password_hash'])) {
- // Сохраняем в сессию
- $_SESSION['user_id'] = $user['user_id'];
- $_SESSION['user_email'] = $user['email'];
- $_SESSION['full_name'] = $user['full_name'];
- $_SESSION['isLoggedIn'] = true;
- $_SESSION['login_time'] = time();
-
- // Обновляем время последнего входа
- $update_stmt = $db->prepare("
- UPDATE users
- SET updated_at = CURRENT_TIMESTAMP
- WHERE user_id = ?
- ");
- $update_stmt->execute([$user['user_id']]);
-
- header('Location: catalog.php');
- exit();
- } else {
- header('Location: вход.php?error=invalid_credentials');
- exit();
- }
- } catch (PDOException $e) {
- header('Location: вход.php?error=db_error');
- exit();
- }
-}
-?>
\ No newline at end of file
diff --git a/logout.php b/logout.php
deleted file mode 100644
index 3b5d01e..0000000
--- a/logout.php
+++ /dev/null
@@ -1,22 +0,0 @@
-
\ No newline at end of file
diff --git a/migrations/001_initial_schema.sql b/migrations/001_initial_schema.sql
new file mode 100644
index 0000000..3b55c9b
--- /dev/null
+++ b/migrations/001_initial_schema.sql
@@ -0,0 +1,64 @@
+CREATE TABLE IF NOT EXISTS users (
+ user_id SERIAL PRIMARY KEY,
+ email VARCHAR(255) UNIQUE NOT NULL,
+ password_hash VARCHAR(255) NOT NULL,
+ full_name VARCHAR(100) NOT NULL,
+ phone VARCHAR(20),
+ city VARCHAR(100),
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ last_login TIMESTAMP,
+ is_active BOOLEAN DEFAULT TRUE,
+ is_admin BOOLEAN DEFAULT FALSE
+);
+
+CREATE TABLE IF NOT EXISTS categories (
+ category_id SERIAL PRIMARY KEY,
+ name VARCHAR(100) NOT NULL,
+ slug VARCHAR(100) UNIQUE NOT NULL,
+ parent_id INTEGER REFERENCES categories(category_id) ON DELETE SET NULL,
+ description TEXT,
+ sort_order INTEGER DEFAULT 0,
+ is_active BOOLEAN DEFAULT TRUE,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS subcategories (
+ subcategory_id SERIAL PRIMARY KEY,
+ category_id INTEGER REFERENCES categories(category_id) ON DELETE CASCADE,
+ name VARCHAR(100) NOT NULL,
+ slug VARCHAR(100) UNIQUE NOT NULL,
+ description TEXT,
+ sort_order INTEGER DEFAULT 0,
+ is_active BOOLEAN DEFAULT TRUE,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS products (
+ product_id SERIAL PRIMARY KEY,
+ category_id INTEGER REFERENCES categories(category_id) ON DELETE SET NULL,
+ name VARCHAR(200) NOT NULL,
+ slug VARCHAR(200) UNIQUE NOT NULL,
+ description TEXT,
+ price DECIMAL(10, 2) NOT NULL,
+ old_price DECIMAL(10, 2),
+ sku VARCHAR(50) UNIQUE,
+ stock_quantity INTEGER DEFAULT 0,
+ is_available BOOLEAN DEFAULT TRUE,
+ is_featured BOOLEAN DEFAULT FALSE,
+ rating DECIMAL(3, 2) DEFAULT 0,
+ review_count INTEGER DEFAULT 0,
+ image_url VARCHAR(500),
+ color VARCHAR(50),
+ material VARCHAR(100),
+ card_size VARCHAR(20) DEFAULT 'small',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX IF NOT EXISTS idx_products_category ON products(category_id);
+CREATE INDEX IF NOT EXISTS idx_products_available ON products(is_available);
+CREATE INDEX IF NOT EXISTS idx_products_price ON products(price);
+CREATE INDEX IF NOT EXISTS idx_categories_parent ON categories(parent_id);
+CREATE INDEX IF NOT EXISTS idx_categories_active ON categories(is_active);
diff --git a/migrations/002_add_cart_orders.sql b/migrations/002_add_cart_orders.sql
new file mode 100644
index 0000000..7f741f6
--- /dev/null
+++ b/migrations/002_add_cart_orders.sql
@@ -0,0 +1,50 @@
+CREATE TABLE IF NOT EXISTS cart (
+ cart_id SERIAL PRIMARY KEY,
+ user_id INTEGER REFERENCES users(user_id) ON DELETE CASCADE,
+ product_id INTEGER REFERENCES products(product_id) ON DELETE CASCADE,
+ quantity INTEGER DEFAULT 1 CHECK (quantity > 0),
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE(user_id, product_id)
+);
+
+CREATE TABLE IF NOT EXISTS orders (
+ order_id SERIAL PRIMARY KEY,
+ order_number VARCHAR(50) UNIQUE NOT NULL,
+ user_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL,
+ customer_name VARCHAR(100) NOT NULL,
+ customer_email VARCHAR(255) NOT NULL,
+ customer_phone VARCHAR(20) NOT NULL,
+ delivery_address TEXT NOT NULL,
+ delivery_region VARCHAR(100),
+ postal_code VARCHAR(20),
+ delivery_method VARCHAR(50) DEFAULT 'courier',
+ payment_method VARCHAR(50) DEFAULT 'card',
+ subtotal DECIMAL(10, 2) NOT NULL,
+ discount_amount DECIMAL(10, 2) DEFAULT 0,
+ delivery_price DECIMAL(10, 2) DEFAULT 0,
+ final_amount DECIMAL(10, 2) NOT NULL,
+ promo_code VARCHAR(50),
+ status VARCHAR(30) DEFAULT 'pending',
+ notes TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ completed_at TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS order_items (
+ item_id SERIAL PRIMARY KEY,
+ order_id INTEGER REFERENCES orders(order_id) ON DELETE CASCADE,
+ product_id INTEGER REFERENCES products(product_id) ON DELETE SET NULL,
+ product_name VARCHAR(200) NOT NULL,
+ product_price DECIMAL(10, 2) NOT NULL,
+ quantity INTEGER NOT NULL CHECK (quantity > 0),
+ total_price DECIMAL(10, 2) NOT NULL,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE INDEX IF NOT EXISTS idx_cart_user ON cart(user_id);
+CREATE INDEX IF NOT EXISTS idx_orders_user ON orders(user_id);
+CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status);
+CREATE INDEX IF NOT EXISTS idx_orders_created ON orders(created_at);
+CREATE INDEX IF NOT EXISTS idx_order_items_order ON order_items(order_id);
diff --git a/migrations/003_add_product_fields.sql b/migrations/003_add_product_fields.sql
new file mode 100644
index 0000000..0b337cd
--- /dev/null
+++ b/migrations/003_add_product_fields.sql
@@ -0,0 +1,43 @@
+DO $$
+BEGIN
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'products' AND column_name = 'color') THEN
+ ALTER TABLE products ADD COLUMN color VARCHAR(50);
+ END IF;
+
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'products' AND column_name = 'material') THEN
+ ALTER TABLE products ADD COLUMN material VARCHAR(100);
+ END IF;
+
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'products' AND column_name = 'card_size') THEN
+ ALTER TABLE products ADD COLUMN card_size VARCHAR(20) DEFAULT 'small';
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'users' AND column_name = 'city') THEN
+ ALTER TABLE users ADD COLUMN city VARCHAR(100);
+ END IF;
+
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'users' AND column_name = 'last_login') THEN
+ ALTER TABLE users ADD COLUMN last_login TIMESTAMP;
+ END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'categories' AND column_name = 'updated_at') THEN
+ ALTER TABLE categories ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
+ END IF;
+
+ IF NOT EXISTS (SELECT 1 FROM information_schema.columns
+ WHERE table_name = 'categories' AND column_name = 'created_at') THEN
+ ALTER TABLE categories ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP;
+ END IF;
+END $$;
diff --git a/migrations/004_grant_admin_to_admin_mail.sql b/migrations/004_grant_admin_to_admin_mail.sql
new file mode 100644
index 0000000..f60a702
--- /dev/null
+++ b/migrations/004_grant_admin_to_admin_mail.sql
@@ -0,0 +1,42 @@
+UPDATE users
+SET is_admin = TRUE,
+ is_active = TRUE,
+ updated_at = CURRENT_TIMESTAMP
+WHERE email = 'admin@mail.ru';
+
+DO $$
+DECLARE
+ updated_count INTEGER;
+ user_info RECORD;
+BEGIN
+ GET DIAGNOSTICS updated_count = ROW_COUNT;
+
+ IF updated_count > 0 THEN
+ SELECT user_id, email, full_name, is_admin, is_active
+ INTO user_info
+ FROM users
+ WHERE email = 'admin@mail.ru';
+
+ RAISE NOTICE 'Пользователь % (ID: %) успешно получил права администратора',
+ user_info.email, user_info.user_id;
+ RAISE NOTICE 'ФИО: %, Админ: %, Активен: %',
+ user_info.full_name, user_info.is_admin, user_info.is_active;
+ ELSE
+ INSERT INTO users (email, password_hash, full_name, phone, city, is_admin, is_active)
+ VALUES (
+ 'admin@mail.ru',
+ '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
+ 'Администратор',
+ '+79129991223',
+ 'Москва',
+ TRUE,
+ TRUE
+ )
+ ON CONFLICT (email) DO UPDATE
+ SET is_admin = TRUE,
+ is_active = TRUE,
+ updated_at = CURRENT_TIMESTAMP;
+
+ RAISE NOTICE 'Пользователь admin@mail.ru создан/обновлен с правами администратора';
+ END IF;
+END $$;
diff --git a/migrations/grant_admin.php b/migrations/grant_admin.php
new file mode 100644
index 0000000..40d7258
--- /dev/null
+++ b/migrations/grant_admin.php
@@ -0,0 +1,85 @@
+getConnection();
+ echo "[OK] Подключение к базе данных успешно\n\n";
+
+ $email = 'admin@mail.ru';
+
+ $checkStmt = $db->prepare("SELECT user_id, email, full_name, is_admin, is_active FROM users WHERE email = ?");
+ $checkStmt->execute([$email]);
+ $user = $checkStmt->fetch(PDO::FETCH_ASSOC);
+
+ if ($user) {
+ echo "[INFO] Найден пользователь:\n";
+ echo " Email: {$user['email']}\n";
+ echo " ФИО: {$user['full_name']}\n";
+ echo " Админ: " . ($user['is_admin'] ? 'ДА' : 'НЕТ') . "\n";
+ echo " Активен: " . ($user['is_active'] ? 'ДА' : 'НЕТ') . "\n\n";
+
+ if ($user['is_admin']) {
+ echo "[INFO] Пользователь уже имеет права администратора\n";
+ } else {
+
+ $updateStmt = $db->prepare("
+ UPDATE users
+ SET is_admin = TRUE,
+ is_active = TRUE,
+ updated_at = CURRENT_TIMESTAMP
+ WHERE email = ?
+ ");
+ $updateStmt->execute([$email]);
+
+ echo "[SUCCESS] Права администратора успешно назначены!\n";
+ }
+ } else {
+ echo "[WARN] Пользователь с email $email не найден\n";
+ echo "[INFO] Создаю нового пользователя с правами администратора...\n";
+
+ $password_hash = password_hash('admin123', PASSWORD_DEFAULT);
+
+ $insertStmt = $db->prepare("
+ INSERT INTO users (email, password_hash, full_name, phone, city, is_admin, is_active)
+ VALUES (?, ?, ?, ?, ?, CAST(? AS boolean), TRUE)
+ RETURNING user_id
+ ");
+
+ $insertStmt->execute([
+ $email,
+ $password_hash,
+ 'Администратор',
+ '+79129991223',
+ 'Москва',
+ 'true'
+ ]);
+
+ $user_id = $insertStmt->fetchColumn();
+ echo "[SUCCESS] Пользователь создан с ID: $user_id\n";
+ echo "[INFO] Email: $email\n";
+ echo "[INFO] Пароль по умолчанию: admin123\n";
+ echo "[WARN] Рекомендуется сменить пароль после первого входа!\n";
+ }
+
+ $verifyStmt = $db->prepare("SELECT user_id, email, full_name, is_admin, is_active FROM users WHERE email = ?");
+ $verifyStmt->execute([$email]);
+ $finalUser = $verifyStmt->fetch(PDO::FETCH_ASSOC);
+
+ echo "\n===========================================\n";
+ echo " Итоговый статус:\n";
+ echo "===========================================\n";
+ echo " Email: {$finalUser['email']}\n";
+ echo " ФИО: {$finalUser['full_name']}\n";
+ echo " Админ: " . ($finalUser['is_admin'] ? 'ДА ✓' : 'НЕТ ✗') . "\n";
+ echo " Активен: " . ($finalUser['is_active'] ? 'ДА ✓' : 'НЕТ ✗') . "\n";
+ echo "===========================================\n";
+
+} catch (PDOException $e) {
+ echo "[ERROR] Ошибка: " . $e->getMessage() . "\n";
+ exit(1);
+}
diff --git a/migrations/migrate.php b/migrations/migrate.php
new file mode 100644
index 0000000..7f009a7
--- /dev/null
+++ b/migrations/migrate.php
@@ -0,0 +1,95 @@
+getConnection();
+ echo "[OK] Подключение к базе данных успешно\n\n";
+
+ $db->exec("
+ CREATE TABLE IF NOT EXISTS migrations (
+ id SERIAL PRIMARY KEY,
+ filename VARCHAR(255) NOT NULL UNIQUE,
+ applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ )
+ ");
+ echo "[OK] Таблица migrations готова\n";
+
+ $stmt = $db->query("SELECT filename FROM migrations ORDER BY filename");
+ $applied = $stmt->fetchAll(PDO::FETCH_COLUMN);
+ echo "[INFO] Уже применено миграций: " . count($applied) . "\n\n";
+
+ $migrationFiles = glob(__DIR__ . '/*.sql');
+ sort($migrationFiles);
+
+ $newMigrations = 0;
+
+ foreach ($migrationFiles as $file) {
+ $filename = basename($file);
+
+ if ($filename === 'seed_data.sql') {
+ continue;
+ }
+
+ if (in_array($filename, $applied)) {
+ echo "[SKIP] $filename (уже применена)\n";
+ continue;
+ }
+
+ echo "[RUN] Применяю $filename... ";
+
+ $sql = file_get_contents($file);
+
+ try {
+ $db->exec($sql);
+
+ $stmt = $db->prepare("INSERT INTO migrations (filename) VALUES (?)");
+ $stmt->execute([$filename]);
+
+ echo "OK\n";
+ $newMigrations++;
+ } catch (PDOException $e) {
+ echo "ОШИБКА!\n";
+ echo " Причина: " . $e->getMessage() . "\n";
+ echo "\n[!] Миграция остановлена из-за ошибки\n";
+ exit(1);
+ }
+ }
+
+ echo "\n-------------------------------------------\n";
+
+ if ($newMigrations > 0) {
+ echo "[SUCCESS] Применено новых миграций: $newMigrations\n";
+ } else {
+ echo "[INFO] Все миграции уже применены\n";
+ }
+
+ $seedFile = __DIR__ . '/seed_data.sql';
+ if (file_exists($seedFile)) {
+ echo "\n[?] Хотите загрузить начальные данные (seed_data.sql)?\n";
+ echo " Запустите: php migrations/migrate.php --seed\n";
+
+ if (isset($argv[1]) && $argv[1] === '--seed') {
+ echo "\n[RUN] Загружаю seed_data.sql... ";
+ try {
+ $sql = file_get_contents($seedFile);
+ $db->exec($sql);
+ echo "OK\n";
+ } catch (PDOException $e) {
+ echo "ОШИБКА: " . $e->getMessage() . "\n";
+ }
+ }
+ }
+
+ echo "\n===========================================\n";
+ echo " Миграции завершены!\n";
+ echo "===========================================\n";
+
+} catch (PDOException $e) {
+ echo "[ERROR] Ошибка подключения к БД: " . $e->getMessage() . "\n";
+ exit(1);
+}
diff --git a/migrations/seed_data.sql b/migrations/seed_data.sql
new file mode 100644
index 0000000..0cc3ea1
--- /dev/null
+++ b/migrations/seed_data.sql
@@ -0,0 +1,56 @@
+INSERT INTO users (email, password_hash, full_name, phone, city, is_admin, is_active)
+VALUES (
+ 'admin@aeterna.ru',
+ '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi',
+ 'Администратор AETERNA',
+ '+79129991223',
+ 'Москва',
+ TRUE,
+ TRUE
+) ON CONFLICT (email) DO NOTHING;
+
+INSERT INTO users (email, password_hash, full_name, phone, city, is_admin, is_active)
+VALUES (
+ 'user@test.com',
+ '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm',
+ 'Тестовый Пользователь',
+ '+79111234567',
+ 'Санкт-Петербург',
+ FALSE,
+ TRUE
+) ON CONFLICT (email) DO NOTHING;
+
+INSERT INTO categories (name, slug, description, sort_order, is_active) VALUES
+ ('Диваны', 'divany', 'Прямые и угловые диваны для гостиной', 1, TRUE),
+ ('Кресла', 'kresla', 'Кресла для гостиной и офиса', 2, TRUE),
+ ('Кровати', 'krovati', 'Односпальные и двуспальные кровати', 3, TRUE),
+ ('Столы', 'stoly', 'Обеденные и рабочие столы', 4, TRUE),
+ ('Стулья', 'stulya', 'Стулья для кухни и офиса', 5, TRUE),
+ ('Светильники', 'svetilniki', 'Торшеры, люстры и настольные лампы', 6, TRUE)
+ON CONFLICT (slug) DO NOTHING;
+
+INSERT INTO products (category_id, name, slug, description, price, old_price, sku, stock_quantity, is_available, image_url, color, material, card_size) VALUES
+ (1, 'Светильник MINNIGHT', 'svetilnik-minnight', 'Настольный светильник в современном стиле', 7999, 9999, 'LAMP-MIN-001', 15, TRUE, 'img2/1_2.png', 'Черный', 'Металл', 'small'),
+ (3, 'Кровать MODER', 'krovat-moder', 'Двуспальная кровать с мягким изголовьем', 45999, 55999, 'BED-MOD-001', 5, TRUE, 'img2/3_3.png', 'Серый', 'Дерево/Ткань', 'large'),
+ (6, 'Торшер MARCIA', 'torsher-marcia', 'Напольный торшер с регулируемой высотой', 11999, 14999, 'LAMP-MAR-001', 8, TRUE, 'img2/2_2.png', 'Золотой', 'Металл', 'tall'),
+ (6, 'Светильник POLET', 'svetilnik-polet', 'Подвесной светильник для гостиной', 5499, NULL, 'LAMP-POL-001', 20, TRUE, 'img2/4.jpg', 'Белый', 'Стекло', 'wide'),
+ (4, 'Стол NORD', 'stol-nord', 'Обеденный стол в скандинавском стиле', 23999, 28999, 'TABLE-NOR-001', 7, TRUE, 'img2/5_5.png', 'Натуральный', 'Дерево', 'small1'),
+ (1, 'Диван ROYALTY', 'divan-royalty', 'Роскошный угловой диван с велюровой обивкой', 78999, 95999, 'SOFA-ROY-001', 3, TRUE, 'img2/6_6.png', 'Зеленый', 'Велюр', 'wide2'),
+ (2, 'Кресло MINIMAL', 'kreslo-minimal', 'Кресло в минималистичном стиле', 29999, 35999, 'ARM-MIN-001', 10, TRUE, 'img2/7_7.png', 'Бежевый', 'Ткань', 'wide3'),
+ (4, 'Стол LONKI', 'stol-lonki', 'Журнальный столик с мраморной столешницей', 34999, NULL, 'TABLE-LON-001', 12, TRUE, 'img2/8_8.png', 'Белый мрамор', 'Мрамор/Металл', 'wide2_1'),
+ (1, 'Диван HEMMINS', 'divan-hemmins', 'Большой модульный диван для всей семьи', 89999, 110000, 'SOFA-HEM-001', 2, TRUE, 'img2/9_9.png', 'Темно-серый', 'Ткань', 'full-width')
+ON CONFLICT (slug) DO NOTHING;
+
+DO $$
+DECLARE
+ users_count INTEGER;
+ categories_count INTEGER;
+ products_count INTEGER;
+BEGIN
+ SELECT COUNT(*) INTO users_count FROM users;
+ SELECT COUNT(*) INTO categories_count FROM categories;
+ SELECT COUNT(*) INTO products_count FROM products;
+
+ RAISE NOTICE 'Загружено: % пользователей, % категорий, % товаров',
+ users_count, categories_count, products_count;
+END $$;
diff --git a/print_order.php b/print_order.php
deleted file mode 100644
index 9e92cf9..0000000
--- a/print_order.php
+++ /dev/null
@@ -1,208 +0,0 @@
-getConnection();
-
-try {
- $orderStmt = $db->prepare("
- SELECT o.*, u.email, u.full_name
- FROM orders o
- LEFT JOIN users u ON o.user_id = u.user_id
- WHERE o.order_id = ?
- ");
- $orderStmt->execute([$orderId]);
- $order = $orderStmt->fetch();
-
- if (!$order) {
- die('Заказ не найден');
- }
-
- $itemsStmt = $db->prepare("
- SELECT * FROM order_items
- WHERE order_id = ?
- ");
- $itemsStmt->execute([$orderId]);
- $order_items = $itemsStmt->fetchAll();
-
-} catch (PDOException $e) {
- die('Ошибка базы данных: ' . $e->getMessage());
-}
-?>
-
-
-
-
-
- Печать заказа #= $order['order_number'] ?>
-
-
-
-
- Печать
-
-
-
-
-
-
Заказ #= htmlspecialchars($order['order_number']) ?>
-
Дата: = date('d.m.Y H:i', strtotime($order['created_at'])) ?>
-
Статус: = $order['status'] ?>
-
-
-
-
-
Информация о клиенте
-
ФИО: = htmlspecialchars($order['customer_name']) ?>
-
Email: = htmlspecialchars($order['customer_email']) ?>
-
Телефон: = htmlspecialchars($order['customer_phone']) ?>
-
-
-
-
Информация о доставке
-
Адрес: = nl2br(htmlspecialchars($order['delivery_address'])) ?>
-
Способ доставки: = $order['delivery_method'] == 'courier' ? 'Курьер' : 'Самовывоз' ?>
-
Способ оплаты: = $order['payment_method'] == 'card' ? 'Карта' : 'Наличные' ?>
-
-
-
- Состав заказа
-
-
-
- №
- Товар
- Кол-во
- Цена
- Сумма
-
-
-
-
-
-
- = $counter++ ?>
- = htmlspecialchars($item['product_name']) ?>
- = $item['quantity'] ?>
- = number_format($item['unit_price'], 0, '', ' ') ?> ₽
- = number_format($item['total_price'], 0, '', ' ') ?> ₽
-
-
-
-
-
- Сумма товаров:
- = number_format($order['total_amount'], 0, '', ' ') ?> ₽
-
- 0): ?>
-
- Скидка:
- -= number_format($order['discount_amount'], 0, '', ' ') ?> ₽
-
-
- 0): ?>
-
- Доставка:
- = number_format($order['delivery_cost'], 0, '', ' ') ?> ₽
-
-
-
- Итого к оплате:
- = number_format($order['final_amount'], 0, '', ' ') ?> ₽
-
-
-
-
-
-
-
Примечания
-
= nl2br(htmlspecialchars($order['notes'])) ?>
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/product_modern.php b/product_modern.php
deleted file mode 100644
index ebc63d9..0000000
--- a/product_modern.php
+++ /dev/null
@@ -1,350 +0,0 @@
-getConnection();
-
-// Получаем информацию о товаре "Диван MODERN" (ID 2 из базы данных)
-try {
- $productStmt = $db->prepare("
- SELECT p.*, c.name as category_name
- FROM products p
- LEFT JOIN categories c ON p.category_id = c.category_id
- WHERE p.product_id = 2 AND p.is_available = TRUE
- ");
- $productStmt->execute();
- $product = $productStmt->fetch();
-
- if (!$product) {
- header('Location: catalog.php?error=product_not_found');
- exit();
- }
-
- // Получаем похожие товары
- $similarStmt = $db->prepare("
- SELECT * FROM products
- WHERE category_id = ? AND product_id != ? AND is_available = TRUE
- ORDER BY RANDOM()
- LIMIT 3
- ");
- $similarStmt->execute([$product['category_id'], $product['product_id']]);
- $similarProducts = $similarStmt->fetchAll();
-
-} catch (PDOException $e) {
- die("Ошибка базы данных: " . $e->getMessage());
-}
-?>
-
-
-
-
-
- AETERNA - = htmlspecialchars($product['name']) ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Диван MODERN
-
-
-
- ★
- ★
- ★
- ★
- ★
-
-
4.5
-
(24 отзыва)
-
-
-
-
- = number_format($product['price'], 0, '', ' ') ?> ₽
-
- $product['price']): ?>
-
- = number_format($product['old_price'], 0, '', ' ') ?> ₽
-
-
- -= round(($product['old_price'] - $product['price']) / $product['old_price'] * 100) ?>%
-
-
-
-
-
-
- Артикул:
- = $product['sku'] ?? 'DIV-MOD-001' ?>
-
-
- Категория:
- Диваны
-
-
- Цвет:
- Серый, Бежевый, Темно-синий
-
-
- Материал:
- Экокожа, Дерево
-
-
- Размеры (Ш×Г×В):
- 220 × 95 × 85 см
-
-
-
-
- 0): ?>
- В наличии: = $product['stock_quantity'] ?> шт.
-
- Нет в наличии
-
-
-
-
- Угловой диван MODERN – сочетание современного дизайна и бескомпромиссного комфорта.
- Каркас из массива дерева обеспечивает долговечность, а наполнитель из высокоэластичного
- пенополиуретана гарантирует оптимальную поддержку спины. Диван оснащен механизмом
- трансформации «еврокнижка», что позволяет использовать его как спальное место.
-
- Особенности:
- • Прочная конструкция из массива дерева
- • Механизм трансформации «еврокнижка»
- • Наполнитель: пенополиуретан + периотек
- • Съемные чехлы для легкой чистки
- • Встроенные подлокотники с полками
-
-
- 0): ?>
-
-
- -
-
- +
-
-
-
-
- В корзину
-
-
- Купить сейчас
-
-
-
-
-
-
- Уведомить о поступлении
-
-
-
-
-
-
-
-
- Похожие товары
-
-
-
-
-
-
-
-
= htmlspecialchars($similar['name']) ?>
-
- = htmlspecialchars(mb_substr($similar['description'] ?? '', 0, 80)) ?>...
-
-
- = number_format($similar['price'], 0, '', ' ') ?> ₽
-
-
- Подробнее
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/public/admin/fix_delete_category.php b/public/admin/fix_delete_category.php
new file mode 100644
index 0000000..436536b
--- /dev/null
+++ b/public/admin/fix_delete_category.php
@@ -0,0 +1,42 @@
+ false, 'message' => 'Доступ запрещен']);
+ exit();
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $categoryId = $_POST['category_id'] ?? 0;
+
+ if (!$categoryId) {
+ echo json_encode(['success' => false, 'message' => 'Категория не указана']);
+ exit();
+ }
+
+ try {
+ $db = Database::getInstance()->getConnection();
+
+ $checkStmt = $db->prepare("SELECT COUNT(*) FROM products WHERE category_id = ?");
+ $checkStmt->execute([$categoryId]);
+ $productCount = $checkStmt->fetchColumn();
+
+ if ($productCount > 0) {
+ echo json_encode(['success' => false, 'message' => 'Нельзя удалить категорию с товарами. Сначала удалите или переместите товары.']);
+ exit();
+ }
+
+ $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
+ $stmt->execute([$categoryId]);
+
+ echo json_encode(['success' => true, 'message' => 'Категория удалена']);
+ } catch (PDOException $e) {
+ echo json_encode(['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()]);
+ }
+} else {
+ echo json_encode(['success' => false, 'message' => 'Неверный запрос']);
+}
+
diff --git a/admin_panel.php b/public/admin/index.php
similarity index 58%
rename from admin_panel.php
rename to public/admin/index.php
index f8a4252..fd44b8d 100644
--- a/admin_panel.php
+++ b/public/admin/index.php
@@ -1,772 +1,988 @@
-Сначала добавьте категории!';
-}
-
-// Проверка прав администратора
-if (!isset($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) {
- echo "";
- exit();
-}
-
-$db = Database::getInstance()->getConnection();
-
-// Обработка действий
-$action = $_GET['action'] ?? 'dashboard';
-$message = $_GET['message'] ?? '';
-$error = $_GET['error'] ?? '';
-
-// Обработка POST запросов - ДОБАВЛЕНО ПРОСТОЕ И РАБОТАЮЩЕЕ!
-if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- $post_action = $_POST['action'] ?? '';
-
- try {
- if ($post_action === 'add_category') {
- $name = trim($_POST['name'] ?? '');
- $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
- $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : NULL;
- $description = trim($_POST['description'] ?? '');
- $sort_order = (int)($_POST['sort_order'] ?? 0);
- $is_active = isset($_POST['is_active']) ? 1 : 0;
-
- if (empty($name)) {
- throw new Exception('Название категории обязательно');
- }
-
- $stmt = $db->prepare("
- INSERT INTO categories (name, slug, parent_id, description, sort_order, is_active)
- VALUES (?, ?, ?, ?, ?, ?)
- ");
-
- $result = $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active]);
-
- if ($result) {
- header('Location: admin_panel.php?action=categories&message=Категория+успешно+добавлена');
- exit();
- }
- }
-
- // ИСПРАВЬТЕ БЛОК edit_category или добавьте его если его нет:
- if ($post_action === 'edit_category' && isset($_POST['category_id'])) {
- $category_id = (int)$_POST['category_id'];
- $name = trim($_POST['name'] ?? '');
- $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : NULL;
- $description = trim($_POST['description'] ?? '');
- $sort_order = (int)($_POST['sort_order'] ?? 0);
- $is_active = isset($_POST['is_active']) ? 1 : 0;
-
- if (empty($name)) {
- throw new Exception('Название категории обязательно');
- }
-
- $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
-
- $stmt = $db->prepare("
- UPDATE categories SET
- name = ?,
- slug = ?,
- parent_id = ?,
- description = ?,
- sort_order = ?,
- is_active = ?,
- updated_at = CURRENT_TIMESTAMP
- WHERE category_id = ?
- ");
-
- $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active, $category_id]);
-
- header('Location: admin_panel.php?action=categories&message=Категория+обновлена');
- exit();
- }
-
- if ($post_action === 'add_product') {
- $name = trim($_POST['name'] ?? '');
- $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
- $category_id = (int)($_POST['category_id'] ?? 0);
- $description = trim($_POST['description'] ?? '');
- $price = (float)($_POST['price'] ?? 0);
- $old_price = !empty($_POST['old_price']) ? (float)$_POST['old_price'] : NULL;
- $sku = trim($_POST['sku'] ?? '');
- $stock_quantity = (int)($_POST['stock_quantity'] ?? 0);
- $is_available = isset($_POST['is_available']) ? 1 : 0;
- $is_featured = isset($_POST['is_featured']) ? 1 : 0;
- $image_url = trim($_POST['image_url'] ?? '');
- $color = trim($_POST['color'] ?? '');
- $material = trim($_POST['material'] ?? '');
- $card_size = trim($_POST['card_size'] ?? 'small');
-
-
- // ВАЖНО: Проверяем category_id
- if ($category_id <= 0) {
- $_SESSION['error'] = 'Выберите корректную категорию';
- header('Location: admin_panel.php?action=add_product');
- exit();
- }
-
- // Проверяем существование категории
- $check_category = $db->prepare("SELECT COUNT(*) FROM categories WHERE category_id = ?");
- $check_category->execute([$category_id]);
- if ($check_category->fetchColumn() == 0) {
- $_SESSION['error'] = 'Выбранная категория не существует';
- header('Location: admin_panel.php?action=add_product');
- exit();
- }
-
- if (empty($name)) throw new Exception('Название товара обязательно');
- if ($price <= 0) throw new Exception('Цена должна быть больше 0');
-
- // Генерируем SKU если пустой
- if (empty($sku)) {
- $sku = 'PROD-' . strtoupper(substr(preg_replace('/[^a-z0-9]/i', '', $name), 0, 6)) . '-' . rand(100, 999);
- }
-
- $stmt = $db->prepare("
- INSERT INTO products (
- category_id, name, slug, description, price, old_price,
- sku, stock_quantity, is_available, is_featured, image_url,
- color, material, card_size
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- ");
-
- $result = $stmt->execute([
- $category_id, $name, $slug, $description, $price, $old_price,
- $sku, $stock_quantity, $is_available, $is_featured, $image_url,
- $color, $material, $card_size
- ]);
-
- if ($result) {
- $_SESSION['message'] = 'Товар успешно добавлен';
- header('Location: admin_panel.php?action=products');
- exit();
- }
- }
-
- // ИСПРАВЛЕННЫЙ КОД для edit_product в admin_panel.php:
- if ($post_action === 'edit_product' && isset($_POST['product_id'])) {
- $product_id = (int)$_POST['product_id'];
- $name = trim($_POST['name'] ?? '');
- $category_id = (int)($_POST['category_id'] ?? 1); // ПО УМОЛЧАНИЮ 1, чтобы избежать 0
- $description = trim($_POST['description'] ?? '');
- $price = (float)($_POST['price'] ?? 0);
- $old_price = !empty($_POST['old_price']) ? (float)$_POST['old_price'] : NULL;
- $stock_quantity = (int)($_POST['stock_quantity'] ?? 0);
- $is_available = isset($_POST['is_available']) ? 1 : 0;
- $image_url = trim($_POST['image_url'] ?? '');
- $color = trim($_POST['color'] ?? '');
- $material = trim($_POST['material'] ?? '');
-
- // ВАЖНО: Проверяем category_id
- if ($category_id <= 0) {
- // Если category_id = 0, устанавливаем первую доступную категорию
- $firstCat = $db->query("SELECT category_id FROM categories LIMIT 1")->fetchColumn();
- $category_id = $firstCat ?: 1;
- }
-
- $stmt = $db->prepare("
- UPDATE products SET
- name = ?,
- category_id = ?,
- description = ?,
- price = ?,
- old_price = ?,
- stock_quantity = ?,
- is_available = ?,
- image_url = ?,
- color = ?,
- material = ?,
- updated_at = CURRENT_TIMESTAMP
- WHERE product_id = ?
- ");
-
- $stmt->execute([
- $name, $category_id, $description, $price, $old_price,
- $stock_quantity, $is_available, $image_url, $color, $material, $product_id
- ]);
-
- header('Location: admin_panel.php?action=products&message=Товар+обновлен');
- exit();
- }
-
- if ($post_action === 'delete_category' && isset($_POST['category_id'])) {
- $categoryId = intval($_POST['category_id']);
-
- // 1. Проверяем, есть ли товары в этой категории
- $checkProducts = $db->prepare("SELECT COUNT(*) FROM products WHERE category_id = ?");
- $checkProducts->execute([$categoryId]);
- $productCount = $checkProducts->fetchColumn();
-
- // 2. Проверяем, есть ли дочерние категории
- $checkChildren = $db->prepare("SELECT COUNT(*) FROM categories WHERE parent_id = ?");
- $checkChildren->execute([$categoryId]);
- $childCount = $checkChildren->fetchColumn();
-
- if ($productCount > 0) {
- // Если есть товары, делаем категорию неактивной вместо удаления
- $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?");
- $stmt->execute([$categoryId]);
-
- header('Location: admin_panel.php?action=categories&message=Категория+скрыта+(содержит+товары)');
- exit();
- } elseif ($childCount > 0) {
- // Если есть дочерние категории, делаем неактивной
- $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?");
- $stmt->execute([$categoryId]);
-
- header('Location: admin_panel.php?action=categories&message=Категория+скрыта+(имеет+дочерние+категории)');
- exit();
- } else {
- // Если нет товаров и дочерних категорий, удаляем
- $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
- $stmt->execute([$categoryId]);
-
- header('Location: admin_panel.php?action=categories&message=Категория+удалена');
- exit();
- }
- }
- } catch (PDOException $e) {
- header('Location: admin_panel.php?action=' . $action . '&error=' . urlencode('Ошибка БД: ' . $e->getMessage()));
- exit();
- } catch (Exception $e) {
- header('Location: admin_panel.php?action=' . $action . '&error=' . urlencode($e->getMessage()));
- exit();
- }
- }
-
-
-// Получение данных для отображения
-try {
- // Статистика
- $stats = [
- 'total_products' => $db->query("SELECT COUNT(*) FROM products")->fetchColumn(),
- 'active_products' => $db->query("SELECT COUNT(*) FROM products WHERE is_available = TRUE")->fetchColumn(),
- 'total_orders' => $db->query("SELECT COUNT(*) FROM orders")->fetchColumn(),
- 'total_users' => $db->query("SELECT COUNT(*) FROM users")->fetchColumn(),
- 'revenue' => $db->query("SELECT COALESCE(SUM(final_amount), 0) FROM orders WHERE status = 'completed'")->fetchColumn()
- ];
-
- // Получаем все категории
- $allCategories = $db->query("SELECT * FROM categories WHERE is_active = TRUE ORDER BY name")->fetchAll();
-
- // Получаем родительские категории
- $parentCategories = $db->query("SELECT * FROM categories WHERE parent_id IS NULL AND is_active = TRUE ORDER BY name")->fetchAll();
-
- switch ($action) {
- case 'products':
- $showAll = isset($_GET['show_all']) && $_GET['show_all'] == '1';
- $sql = $showAll
- ? "SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.category_id ORDER BY p.created_at DESC"
- : "SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.category_id WHERE p.is_available = TRUE ORDER BY p.created_at DESC";
- $data = $db->query($sql)->fetchAll();
- break;
-
- case 'categories':
- $data = $db->query("
- SELECT c1.*, c2.name as parent_name,
- (SELECT COUNT(*) FROM products p WHERE p.category_id = c1.category_id) as product_count
- FROM categories c1
- LEFT JOIN categories c2 ON c1.parent_id = c2.category_id
- ORDER BY c1.sort_order, c1.name
- ")->fetchAll();
- break;
-
- case 'orders':
- $data = $db->query("
- SELECT o.*, u.email as user_email
- FROM orders o
- LEFT JOIN users u ON o.user_id = u.user_id
- ORDER BY o.created_at DESC
- LIMIT 50
- ")->fetchAll();
- break;
-
- case 'users':
- $data = $db->query("SELECT * FROM users ORDER BY created_at DESC LIMIT 50")->fetchAll();
- break;
-
- case 'add_product':
- case 'edit_product':
- if ($action === 'edit_product' && isset($_GET['id'])) {
- $productId = (int)$_GET['id'];
- $stmt = $db->prepare("SELECT * FROM products WHERE product_id = ?");
- $stmt->execute([$productId]);
- $edit_data = $stmt->fetch();
- }
- break;
-
- case 'add_category':
- case 'edit_category':
- if ($action === 'edit_category' && isset($_GET['id'])) {
- $categoryId = (int)$_GET['id'];
- $stmt = $db->prepare("SELECT * FROM categories WHERE category_id = ?");
- $stmt->execute([$categoryId]);
- $edit_data = $stmt->fetch();
- }
- break;
-
- }
-
-} catch (PDOException $e) {
- $error = "Ошибка базы данных: " . $e->getMessage();
-}
-?>
-
-
-
-
-
- AETERNA - Админ-панель
-
-
-
-
-
-
-
-
-
-
-
- = htmlspecialchars(urldecode($message)) ?>
-
-
-
-
-
- = htmlspecialchars(urldecode($error)) ?>
-
-
-
-
-
-
Статистика
-
-
-
= $stats['total_products'] ?>
-
Всего товаров
-
-
-
= $stats['active_products'] ?>
-
Активных товаров
-
-
-
= $stats['total_orders'] ?>
-
Заказов
-
-
-
= $stats['total_users'] ?>
-
Пользователей
-
-
-
-
-
-
-
-
-
Управление товарами
-
-
-
-
-
-
- ID
- Название
- Категория
- Цена
- На складе
- Статус
- Действия
-
-
-
-
-
- = $product['product_id'] ?>
- = htmlspecialchars($product['name']) ?>
- = htmlspecialchars($product['category_name'] ?? 'Без категории') ?>
- = number_format($product['price'], 0, '', ' ') ?> ₽
- = $product['stock_quantity'] ?>
-
- 0): ?>
- ✓ Доступен
-
- ✗ Недоступен
-
- ⚠ Нет на складе
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ID
- Название
- Slug
- Родительская
- Товаров
- Действия
-
-
-
-
-
- = $category['category_id'] ?>
- = htmlspecialchars($category['name']) ?>
- = htmlspecialchars($category['slug']) ?>
- = htmlspecialchars($category['parent_name'] ?? '—') ?>
- = $category['product_count'] ?>
-
-
-
- Редактировать
-
-
-
- 0 ? 'disabled' : '' ?>>
- Удалить
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Заказы
-
-
-
- № заказа
- Клиент
- Сумма
- Статус
- Дата
- Действия
-
-
-
-
-
- = htmlspecialchars($order['order_number']) ?>
- = htmlspecialchars($order['customer_name']) ?>
- = number_format($order['final_amount'], 0, '', ' ') ?> ₽
- = htmlspecialchars($order['status']) ?>
- = date('d.m.Y H:i', strtotime($order['created_at'])) ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
Пользователи
-
-
-
- ID
- Email
- ФИО
- Дата регистрации
- Статус
-
-
-
-
-
- = $user['user_id'] ?>
- = htmlspecialchars($user['email']) ?>
- = htmlspecialchars($user['full_name']) ?>
- = date('d.m.Y', strtotime($user['created_at'])) ?>
-
-
- ✓ Активен
-
- ✗ Неактивен
-
-
-
-
-
-
-
-
-
-
-
+alert('Требуется авторизация администратора'); window.location.href = '../login.php';";
+ exit();
+}
+
+$db = Database::getInstance()->getConnection();
+
+$action = $_GET['action'] ?? 'dashboard';
+$message = $_GET['message'] ?? '';
+$error = $_GET['error'] ?? '';
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $post_action = $_POST['action'] ?? '';
+
+ try {
+ if ($post_action === 'add_category') {
+ $name = trim($_POST['name'] ?? '');
+ $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
+ $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : NULL;
+ $description = trim($_POST['description'] ?? '');
+ $sort_order = (int)($_POST['sort_order'] ?? 0);
+ $is_active = isset($_POST['is_active']) ? 1 : 0;
+
+ if (empty($name)) {
+ throw new Exception('Название категории обязательно');
+ }
+
+ $stmt = $db->prepare("
+ INSERT INTO categories (name, slug, parent_id, description, sort_order, is_active)
+ VALUES (?, ?, ?, ?, ?, ?)
+ ");
+
+ $result = $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active]);
+
+ if ($result) {
+ header('Location: index.php?action=categories&message=Категория+успешно+добавлена');
+ exit();
+ }
+ }
+
+ if ($post_action === 'edit_category' && isset($_POST['category_id'])) {
+ $category_id = (int)$_POST['category_id'];
+ $name = trim($_POST['name'] ?? '');
+ $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : NULL;
+ $description = trim($_POST['description'] ?? '');
+ $sort_order = (int)($_POST['sort_order'] ?? 0);
+ $is_active = isset($_POST['is_active']) ? 1 : 0;
+
+ if (empty($name)) {
+ throw new Exception('Название категории обязательно');
+ }
+
+ $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
+
+ $stmt = $db->prepare("
+ UPDATE categories SET
+ name = ?,
+ slug = ?,
+ parent_id = ?,
+ description = ?,
+ sort_order = ?,
+ is_active = ?,
+ updated_at = CURRENT_TIMESTAMP
+ WHERE category_id = ?
+ ");
+
+ $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active, $category_id]);
+
+ header('Location: index.php?action=categories&message=Категория+обновлена');
+ exit();
+ }
+
+ if ($post_action === 'add_product') {
+ $name = trim($_POST['name'] ?? '');
+ $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
+ $category_id = (int)($_POST['category_id'] ?? 0);
+ $description = trim($_POST['description'] ?? '');
+ $price = (float)($_POST['price'] ?? 0);
+ $old_price = !empty($_POST['old_price']) ? (float)$_POST['old_price'] : NULL;
+ $sku = trim($_POST['sku'] ?? '');
+ $stock_quantity = (int)($_POST['stock_quantity'] ?? 0);
+ $is_available = isset($_POST['is_available']) ? 1 : 0;
+ $is_featured = isset($_POST['is_featured']) ? 1 : 0;
+ $image_url = trim($_POST['image_url'] ?? '');
+ $color = trim($_POST['color'] ?? '');
+ $material = trim($_POST['material'] ?? '');
+ $card_size = trim($_POST['card_size'] ?? 'small');
+
+ if ($category_id <= 0) {
+ $_SESSION['error'] = 'Выберите корректную категорию';
+ header('Location: index.php?action=add_product');
+ exit();
+ }
+
+ $check_category = $db->prepare("SELECT COUNT(*) FROM categories WHERE category_id = ?");
+ $check_category->execute([$category_id]);
+ if ($check_category->fetchColumn() == 0) {
+ $_SESSION['error'] = 'Выбранная категория не существует';
+ header('Location: index.php?action=add_product');
+ exit();
+ }
+
+ if (empty($name)) throw new Exception('Название товара обязательно');
+ if ($price <= 0) throw new Exception('Цена должна быть больше 0');
+
+ if (empty($sku)) {
+ $sku = 'PROD-' . strtoupper(substr(preg_replace('/[^a-z0-9]/i', '', $name), 0, 6)) . '-' . rand(100, 999);
+ }
+
+ $stmt = $db->prepare("
+ INSERT INTO products (
+ category_id, name, slug, description, price, old_price,
+ sku, stock_quantity, is_available, is_featured, image_url,
+ color, material, card_size
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ");
+
+ $result = $stmt->execute([
+ $category_id, $name, $slug, $description, $price, $old_price,
+ $sku, $stock_quantity, $is_available, $is_featured, $image_url,
+ $color, $material, $card_size
+ ]);
+
+ if ($result) {
+ $_SESSION['message'] = 'Товар успешно добавлен';
+ header('Location: index.php?action=products');
+ exit();
+ }
+ }
+
+ if ($post_action === 'edit_product' && isset($_POST['product_id'])) {
+ $product_id = (int)$_POST['product_id'];
+ $name = trim($_POST['name'] ?? '');
+ $category_id = (int)($_POST['category_id'] ?? 1);
+ $description = trim($_POST['description'] ?? '');
+ $price = (float)($_POST['price'] ?? 0);
+ $old_price = !empty($_POST['old_price']) ? (float)$_POST['old_price'] : NULL;
+ $stock_quantity = (int)($_POST['stock_quantity'] ?? 0);
+ $is_available = isset($_POST['is_available']) ? 1 : 0;
+ $image_url = trim($_POST['image_url'] ?? '');
+ $color = trim($_POST['color'] ?? '');
+ $material = trim($_POST['material'] ?? '');
+
+ if ($category_id <= 0) {
+ $firstCat = $db->query("SELECT category_id FROM categories LIMIT 1")->fetchColumn();
+ $category_id = $firstCat ?: 1;
+ }
+
+ $stmt = $db->prepare("
+ UPDATE products SET
+ name = ?,
+ category_id = ?,
+ description = ?,
+ price = ?,
+ old_price = ?,
+ stock_quantity = ?,
+ is_available = ?,
+ image_url = ?,
+ color = ?,
+ material = ?,
+ updated_at = CURRENT_TIMESTAMP
+ WHERE product_id = ?
+ ");
+
+ $stmt->execute([
+ $name, $category_id, $description, $price, $old_price,
+ $stock_quantity, $is_available, $image_url, $color, $material, $product_id
+ ]);
+
+ header('Location: index.php?action=products&message=Товар+обновлен');
+ exit();
+ }
+
+ if ($post_action === 'delete_category' && isset($_POST['category_id'])) {
+ $categoryId = intval($_POST['category_id']);
+
+ $checkProducts = $db->prepare("SELECT COUNT(*) FROM products WHERE category_id = ?");
+ $checkProducts->execute([$categoryId]);
+ $productCount = $checkProducts->fetchColumn();
+
+ $checkChildren = $db->prepare("SELECT COUNT(*) FROM categories WHERE parent_id = ?");
+ $checkChildren->execute([$categoryId]);
+ $childCount = $checkChildren->fetchColumn();
+
+ if ($productCount > 0) {
+ $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?");
+ $stmt->execute([$categoryId]);
+ header('Location: index.php?action=categories&message=Категория+скрыта+(содержит+товары)');
+ exit();
+ } elseif ($childCount > 0) {
+ $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?");
+ $stmt->execute([$categoryId]);
+ header('Location: index.php?action=categories&message=Категория+скрыта+(имеет+дочерние+категории)');
+ exit();
+ } else {
+ $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
+ $stmt->execute([$categoryId]);
+ header('Location: index.php?action=categories&message=Категория+удалена');
+ exit();
+ }
+ }
+ } catch (PDOException $e) {
+ header('Location: index.php?action=' . $action . '&error=' . urlencode('Ошибка БД: ' . $e->getMessage()));
+ exit();
+ } catch (Exception $e) {
+ header('Location: index.php?action=' . $action . '&error=' . urlencode($e->getMessage()));
+ exit();
+ }
+ }
+
+try {
+ $stats = [
+ 'total_products' => $db->query("SELECT COUNT(*) FROM products")->fetchColumn(),
+ 'active_products' => $db->query("SELECT COUNT(*) FROM products WHERE is_available = TRUE")->fetchColumn(),
+ 'total_orders' => $db->query("SELECT COUNT(*) FROM orders")->fetchColumn(),
+ 'total_users' => $db->query("SELECT COUNT(*) FROM users")->fetchColumn(),
+ 'revenue' => $db->query("SELECT COALESCE(SUM(final_amount), 0) FROM orders WHERE status = 'completed'")->fetchColumn()
+ ];
+
+ $allCategories = $db->query("SELECT * FROM categories WHERE is_active = TRUE ORDER BY name")->fetchAll();
+
+ $parentCategories = $db->query("SELECT * FROM categories WHERE parent_id IS NULL AND is_active = TRUE ORDER BY name")->fetchAll();
+
+ switch ($action) {
+ case 'products':
+ $showAll = isset($_GET['show_all']) && $_GET['show_all'] == '1';
+ $sql = $showAll
+ ? "SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.category_id ORDER BY p.created_at DESC"
+ : "SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.category_id WHERE p.is_available = TRUE ORDER BY p.created_at DESC";
+ $data = $db->query($sql)->fetchAll();
+ break;
+
+ case 'categories':
+ $data = $db->query("
+ SELECT c1.*, c2.name as parent_name,
+ (SELECT COUNT(*) FROM products p WHERE p.category_id = c1.category_id) as product_count
+ FROM categories c1
+ LEFT JOIN categories c2 ON c1.parent_id = c2.category_id
+ ORDER BY c1.sort_order, c1.name
+ ")->fetchAll();
+ break;
+
+ case 'orders':
+ $data = $db->query("
+ SELECT o.*, u.email as user_email
+ FROM orders o
+ LEFT JOIN users u ON o.user_id = u.user_id
+ ORDER BY o.created_at DESC
+ LIMIT 50
+ ")->fetchAll();
+ break;
+
+ case 'users':
+ $data = $db->query("SELECT * FROM users ORDER BY created_at DESC LIMIT 50")->fetchAll();
+ break;
+
+ case 'add_product':
+ case 'edit_product':
+ if ($action === 'edit_product' && isset($_GET['id'])) {
+ $productId = (int)$_GET['id'];
+ $stmt = $db->prepare("SELECT * FROM products WHERE product_id = ?");
+ $stmt->execute([$productId]);
+ $edit_data = $stmt->fetch();
+ }
+ break;
+
+ case 'add_category':
+ case 'edit_category':
+ if ($action === 'edit_category' && isset($_GET['id'])) {
+ $categoryId = (int)$_GET['id'];
+ $stmt = $db->prepare("SELECT * FROM categories WHERE category_id = ?");
+ $stmt->execute([$categoryId]);
+ $edit_data = $stmt->fetch();
+ }
+ break;
+
+ case 'order_details':
+ if (isset($_GET['id'])) {
+ $orderId = (int)$_GET['id'];
+
+ // Получаем информацию о заказе
+ $stmt = $db->prepare("
+ SELECT o.*, u.email as user_email, u.full_name as user_full_name
+ FROM orders o
+ LEFT JOIN users u ON o.user_id = u.user_id
+ WHERE o.order_id = ?
+ ");
+ $stmt->execute([$orderId]);
+ $order = $stmt->fetch();
+
+ // Получаем товары в заказе
+ if ($order) {
+ $stmt = $db->prepare("
+ SELECT oi.*, p.image_url
+ FROM order_items oi
+ LEFT JOIN products p ON oi.product_id = p.product_id
+ WHERE oi.order_id = ?
+ ");
+ $stmt->execute([$orderId]);
+ $order_items = $stmt->fetchAll();
+ }
+ }
+ break;
+
+ }
+
+} catch (PDOException $e) {
+ $error = "Ошибка базы данных: " . $e->getMessage();
+}
+?>
+
+
+
+
+
+
+ AETERNA - Админ-панель
+
+
+
+
+
+
+
+
+
+
+
+
+ = htmlspecialchars(urldecode($message)) ?>
+
+
+
+
+
+ = htmlspecialchars(urldecode($error)) ?>
+
+
+
+
+
+
Статистика
+
+
+
= $stats['total_products'] ?>
+
Всего товаров
+
+
+
= $stats['active_products'] ?>
+
Активных товаров
+
+
+
= $stats['total_orders'] ?>
+
Заказов
+
+
+
= $stats['total_users'] ?>
+
Пользователей
+
+
+
+
+
+
+
+
+
Управление товарами
+
+
+
+
+
+
+ ID
+ Название
+ Категория
+ Цена
+ На складе
+ Статус
+ Действия
+
+
+
+
+
+ = $product['product_id'] ?>
+ = htmlspecialchars($product['name']) ?>
+ = htmlspecialchars($product['category_name'] ?? 'Без категории') ?>
+ = number_format($product['price'], 0, '', ' ') ?> ₽
+ = $product['stock_quantity'] ?>
+
+ 0): ?>
+ ✓ Доступен
+
+ ✗ Недоступен
+
+ ⚠ Нет на складе
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ Название
+ Slug
+ Родительская
+ Товаров
+ Действия
+
+
+
+
+
+ = $category['category_id'] ?>
+ = htmlspecialchars($category['name']) ?>
+ = htmlspecialchars($category['slug']) ?>
+ = htmlspecialchars($category['parent_name'] ?? '—') ?>
+ = $category['product_count'] ?>
+
+
+ Редактировать
+
+
+ Удалить
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Заказы
+
+
+
+ № заказа
+ Клиент
+ Сумма
+ Статус
+ Дата
+ Действия
+
+
+
+
+
+ = htmlspecialchars($order['order_number']) ?>
+ = htmlspecialchars($order['customer_name']) ?>
+ = number_format($order['final_amount'], 0, '', ' ') ?> ₽
+ = htmlspecialchars($order['status']) ?>
+ = date('d.m.Y H:i', strtotime($order['created_at'])) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
Пользователи
+
+
+
+ ID
+ Email
+ ФИО
+ Дата регистрации
+ Статус
+
+
+
+
+
+ = $user['user_id'] ?>
+ = htmlspecialchars($user['email']) ?>
+ = htmlspecialchars($user['full_name']) ?>
+ = date('d.m.Y', strtotime($user['created_at'])) ?>
+
+
+ ✓ Активен
+
+ ✗ Неактивен
+
+
+
+
+
+
+
+
+
+
+
+
+
Детали заказа #= htmlspecialchars($order['order_number']) ?>
+
+
+
+
+
Информация о заказе
+
+
+ Номер заказа:
+ = htmlspecialchars($order['order_number']) ?>
+
+
+ Дата создания:
+ = date('d.m.Y H:i', strtotime($order['created_at'])) ?>
+
+
+ Статус:
+
+
+ = htmlspecialchars($order['status']) ?>
+
+
+
+
+ Способ оплаты:
+ = $order['payment_method'] == 'card' ? 'Банковская карта' : 'Наличные' ?>
+
+
+ Способ доставки:
+ = $order['delivery_method'] == 'courier' ? 'Курьерская доставка' : 'Самовывоз' ?>
+
+
+
+
+
+
+
Информация о клиенте
+
+
+ ФИО:
+ = htmlspecialchars($order['customer_name']) ?>
+
+
+ Email:
+ = htmlspecialchars($order['customer_email']) ?>
+
+
+ Телефон:
+ = htmlspecialchars($order['customer_phone']) ?>
+
+
+ Регион:
+ = htmlspecialchars($order['delivery_region'] ?? '—') ?>
+
+
+ Адрес доставки:
+ = htmlspecialchars($order['delivery_address']) ?>
+
+
+
+ Индекс:
+ = htmlspecialchars($order['postal_code']) ?>
+
+
+
+
+
+
+
+
+
Товары в заказе
+
+
+
+ Изображение
+ Товар
+ Цена
+ Количество
+ Сумма
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = htmlspecialchars($item['product_name']) ?>
+ = number_format($item['product_price'], 0, '', ' ') ?> ₽
+ = $item['quantity'] ?> шт.
+ = number_format($item['total_price'], 0, '', ' ') ?> ₽
+
+
+
+
+
+
+
+
+
Итого
+
+
+ Товары:
+ = number_format($order['subtotal'], 0, '', ' ') ?> ₽
+
+ 0): ?>
+
+ Скидка:
+ -= number_format($order['discount_amount'], 0, '', ' ') ?> ₽
+
+
+
+ Доставка:
+ = number_format($order['delivery_price'], 0, '', ' ') ?> ₽
+
+
+ Итого к оплате:
+ = number_format($order['final_amount'], 0, '', ' ') ?> ₽
+
+
+
+
+
+
+
Примечания
+
= htmlspecialchars($order['notes']) ?>
+
+
+
+
+
+ Заказ не найден
+
+
Вернуться к списку заказов
+
+
+
+
+
+
\ No newline at end of file
diff --git a/add_to_cart.php b/public/api/add_to_cart.php
similarity index 84%
rename from add_to_cart.php
rename to public/api/add_to_cart.php
index 4ff29d8..a452dcc 100644
--- a/add_to_cart.php
+++ b/public/api/add_to_cart.php
@@ -1,116 +1,112 @@
- false, 'message' => 'Требуется авторизация']);
- exit();
-}
-
-if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['product_id'])) {
- $product_id = intval($_POST['product_id']);
- $quantity = intval($_POST['quantity'] ?? 1);
- $user_id = $_SESSION['user_id'] ?? 0;
-
- if ($user_id == 0) {
- echo json_encode(['success' => false, 'message' => 'Пользователь не найден']);
- exit();
- }
-
- $db = Database::getInstance()->getConnection();
-
- try {
- // Проверяем наличие товара на складе
- $checkStock = $db->prepare("
- SELECT stock_quantity, name, price
- FROM products
- WHERE product_id = ? AND is_available = TRUE
- ");
- $checkStock->execute([$product_id]);
- $product = $checkStock->fetch();
-
- if (!$product) {
- echo json_encode(['success' => false, 'message' => 'Товар не найден']);
- exit();
- }
-
- if ($product['stock_quantity'] < $quantity) {
- echo json_encode(['success' => false, 'message' => 'Недостаточно товара на складе']);
- exit();
- }
-
- // Проверяем, есть ли товар уже в корзине пользователя
- $checkCart = $db->prepare("
- SELECT cart_id, quantity
- FROM cart
- WHERE user_id = ? AND product_id = ?
- ");
- $checkCart->execute([$user_id, $product_id]);
- $cartItem = $checkCart->fetch();
-
- if ($cartItem) {
- // Обновляем количество
- $newQuantity = $cartItem['quantity'] + $quantity;
-
- // Проверяем общее количество
- if ($newQuantity > $product['stock_quantity']) {
- echo json_encode(['success' => false, 'message' => 'Превышено доступное количество']);
- exit();
- }
-
- $updateStmt = $db->prepare("
- UPDATE cart
- SET quantity = ?, updated_at = CURRENT_TIMESTAMP
- WHERE cart_id = ?
- ");
- $updateStmt->execute([$newQuantity, $cartItem['cart_id']]);
- } else {
- // Добавляем новый товар
- $insertStmt = $db->prepare("
- INSERT INTO cart (user_id, product_id, quantity)
- VALUES (?, ?, ?)
- ");
- $insertStmt->execute([$user_id, $product_id, $quantity]);
- }
-
- // Обновляем сессию
- if (!isset($_SESSION['cart'])) {
- $_SESSION['cart'] = [];
- }
-
- if (isset($_SESSION['cart'][$product_id])) {
- $_SESSION['cart'][$product_id]['quantity'] += $quantity;
- } else {
- $_SESSION['cart'][$product_id] = [
- 'quantity' => $quantity,
- 'name' => $product['name'],
- 'price' => $product['price'],
- 'added_at' => time()
- ];
- }
-
- // Получаем общее количество товаров в корзине
- $cartCountStmt = $db->prepare("
- SELECT SUM(quantity) as total
- FROM cart
- WHERE user_id = ?
- ");
- $cartCountStmt->execute([$user_id]);
- $cart_count = $cartCountStmt->fetchColumn() ?: 0;
-
- echo json_encode([
- 'success' => true,
- 'cart_count' => $cart_count,
- 'message' => 'Товар добавлен в корзину'
- ]);
-
- } catch (PDOException $e) {
- echo json_encode([
- 'success' => false,
- 'message' => 'Ошибка базы данных: ' . $e->getMessage()
- ]);
- }
-} else {
- echo json_encode(['success' => false, 'message' => 'Неверный запрос']);
-}
+ false, 'message' => 'Требуется авторизация']);
+ exit();
+}
+
+if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['product_id'])) {
+ $product_id = intval($_POST['product_id']);
+ $quantity = intval($_POST['quantity'] ?? 1);
+ $user_id = $_SESSION['user_id'] ?? 0;
+
+ if ($user_id == 0) {
+ echo json_encode(['success' => false, 'message' => 'Пользователь не найден']);
+ exit();
+ }
+
+ $db = Database::getInstance()->getConnection();
+
+ try {
+
+ $checkStock = $db->prepare("
+ SELECT stock_quantity, name, price
+ FROM products
+ WHERE product_id = ? AND is_available = TRUE
+ ");
+ $checkStock->execute([$product_id]);
+ $product = $checkStock->fetch();
+
+ if (!$product) {
+ echo json_encode(['success' => false, 'message' => 'Товар не найден']);
+ exit();
+ }
+
+ if ($product['stock_quantity'] < $quantity) {
+ echo json_encode(['success' => false, 'message' => 'Недостаточно товара на складе']);
+ exit();
+ }
+
+ $checkCart = $db->prepare("
+ SELECT cart_id, quantity
+ FROM cart
+ WHERE user_id = ? AND product_id = ?
+ ");
+ $checkCart->execute([$user_id, $product_id]);
+ $cartItem = $checkCart->fetch();
+
+ if ($cartItem) {
+
+ $newQuantity = $cartItem['quantity'] + $quantity;
+
+ if ($newQuantity > $product['stock_quantity']) {
+ echo json_encode(['success' => false, 'message' => 'Превышено доступное количество']);
+ exit();
+ }
+
+ $updateStmt = $db->prepare("
+ UPDATE cart
+ SET quantity = ?, updated_at = CURRENT_TIMESTAMP
+ WHERE cart_id = ?
+ ");
+ $updateStmt->execute([$newQuantity, $cartItem['cart_id']]);
+ } else {
+
+ $insertStmt = $db->prepare("
+ INSERT INTO cart (user_id, product_id, quantity)
+ VALUES (?, ?, ?)
+ ");
+ $insertStmt->execute([$user_id, $product_id, $quantity]);
+ }
+
+ if (!isset($_SESSION['cart'])) {
+ $_SESSION['cart'] = [];
+ }
+
+ if (isset($_SESSION['cart'][$product_id])) {
+ $_SESSION['cart'][$product_id]['quantity'] += $quantity;
+ } else {
+ $_SESSION['cart'][$product_id] = [
+ 'quantity' => $quantity,
+ 'name' => $product['name'],
+ 'price' => $product['price'],
+ 'added_at' => time()
+ ];
+ }
+
+ $cartCountStmt = $db->prepare("
+ SELECT SUM(quantity) as total
+ FROM cart
+ WHERE user_id = ?
+ ");
+ $cartCountStmt->execute([$user_id]);
+ $cart_count = $cartCountStmt->fetchColumn() ?: 0;
+
+ echo json_encode([
+ 'success' => true,
+ 'cart_count' => $cart_count,
+ 'message' => 'Товар добавлен в корзину'
+ ]);
+
+ } catch (PDOException $e) {
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Ошибка базы данных: ' . $e->getMessage()
+ ]);
+ }
+} else {
+ echo json_encode(['success' => false, 'message' => 'Неверный запрос']);
+}
?>
\ No newline at end of file
diff --git a/login_handler.php b/public/api/auth.php
similarity index 83%
rename from login_handler.php
rename to public/api/auth.php
index 3f63de6..5285940 100644
--- a/login_handler.php
+++ b/public/api/auth.php
@@ -1,66 +1,69 @@
- false, 'message' => 'Заполните все поля']);
- exit();
- }
-
- $db = Database::getInstance()->getConnection();
-
- try {
- // Проверяем пользователя в базе данных
- $stmt = $db->prepare("
- SELECT user_id, email, password_hash, full_name, phone, city, is_admin, is_active
- FROM users
- WHERE email = ?
- ");
- $stmt->execute([$email]);
- $user = $stmt->fetch();
-
- if (!$user) {
- echo json_encode(['success' => false, 'message' => 'Пользователь не найден']);
- exit();
- }
-
- if (!$user['is_active']) {
- echo json_encode(['success' => false, 'message' => 'Аккаунт заблокирован']);
- exit();
- }
-
- // Проверяем пароль
- if (!password_verify($password, $user['password_hash'])) {
- echo json_encode(['success' => false, 'message' => 'Неверный пароль']);
- exit();
- }
-
- // Сохраняем в сессию
- $_SESSION['user_id'] = $user['user_id'];
- $_SESSION['user_email'] = $user['email'];
- $_SESSION['full_name'] = $user['full_name'];
- $_SESSION['user_phone'] = $user['phone'] ?? '';
- $_SESSION['user_city'] = $user['city'] ?? '';
- $_SESSION['isLoggedIn'] = true;
- $_SESSION['isAdmin'] = (bool)$user['is_admin'];
- $_SESSION['login_time'] = time();
-
- // Обновляем время последнего входа
- $updateStmt = $db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE user_id = ?");
- $updateStmt->execute([$user['user_id']]);
-
- echo json_encode(['success' => true, 'redirect' => 'catalog.php']);
-
- } catch (PDOException $e) {
- echo json_encode(['success' => false, 'message' => 'Ошибка базы данных']);
- }
-
-} else {
- echo json_encode(['success' => false, 'message' => 'Неверный запрос']);
-}
+ false, 'message' => 'Заполните все поля']);
+ exit();
+ }
+
+ $db = Database::getInstance()->getConnection();
+
+ try {
+
+ $stmt = $db->prepare("
+ SELECT user_id, email, password_hash, full_name, phone, city, is_admin, is_active
+ FROM users
+ WHERE email = ?
+ ");
+ $stmt->execute([$email]);
+ $user = $stmt->fetch(PDO::FETCH_ASSOC);
+
+ if (!$user) {
+ echo json_encode(['success' => false, 'message' => 'Пользователь не найден']);
+ exit();
+ }
+
+ if (!$user['is_active']) {
+ echo json_encode(['success' => false, 'message' => 'Аккаунт заблокирован']);
+ exit();
+ }
+
+ if (empty($user['password_hash'])) {
+ echo json_encode(['success' => false, 'message' => 'Ошибка: пароль не найден в базе данных']);
+ exit();
+ }
+
+ if (!password_verify($password, $user['password_hash'])) {
+ echo json_encode(['success' => false, 'message' => 'Неверный пароль']);
+ exit();
+ }
+
+ $_SESSION['user_id'] = $user['user_id'];
+ $_SESSION['user_email'] = $user['email'];
+ $_SESSION['full_name'] = $user['full_name'];
+ $_SESSION['user_phone'] = $user['phone'] ?? '';
+ $_SESSION['user_city'] = $user['city'] ?? '';
+ $_SESSION['isLoggedIn'] = true;
+ $_SESSION['isAdmin'] = (bool)$user['is_admin'];
+ $_SESSION['login_time'] = time();
+
+ $updateStmt = $db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE user_id = ?");
+ $updateStmt->execute([$user['user_id']]);
+
+ echo json_encode(['success' => true, 'redirect' => 'catalog.php']);
+
+ } catch (PDOException $e) {
+ echo json_encode(['success' => false, 'message' => 'Ошибка базы данных']);
+ }
+
+} else {
+ echo json_encode(['success' => false, 'message' => 'Неверный запрос']);
+}
?>
\ No newline at end of file
diff --git a/public/api/cart.php b/public/api/cart.php
new file mode 100644
index 0000000..cd68d7b
--- /dev/null
+++ b/public/api/cart.php
@@ -0,0 +1,127 @@
+ false, 'message' => 'Требуется авторизация']);
+ exit();
+}
+
+$userId = $_SESSION['user_id'] ?? 0;
+$action = $_GET['action'] ?? $_POST['action'] ?? '';
+
+$db = Database::getInstance()->getConnection();
+
+try {
+ switch ($action) {
+ case 'add':
+ $productId = (int)($_POST['product_id'] ?? 0);
+ $quantity = (int)($_POST['quantity'] ?? 1);
+
+ if ($productId <= 0) {
+ echo json_encode(['success' => false, 'message' => 'Неверный ID товара']);
+ exit();
+ }
+
+ $checkProduct = $db->prepare("SELECT product_id, stock_quantity FROM products WHERE product_id = ? AND is_available = TRUE");
+ $checkProduct->execute([$productId]);
+ $product = $checkProduct->fetch();
+
+ if (!$product) {
+ echo json_encode(['success' => false, 'message' => 'Товар не найден']);
+ exit();
+ }
+
+ $checkCart = $db->prepare("SELECT cart_id, quantity FROM cart WHERE user_id = ? AND product_id = ?");
+ $checkCart->execute([$userId, $productId]);
+ $cartItem = $checkCart->fetch();
+
+ if ($cartItem) {
+
+ $newQuantity = $cartItem['quantity'] + $quantity;
+ $stmt = $db->prepare("UPDATE cart SET quantity = ?, updated_at = CURRENT_TIMESTAMP WHERE cart_id = ?");
+ $stmt->execute([$newQuantity, $cartItem['cart_id']]);
+ } else {
+
+ $stmt = $db->prepare("INSERT INTO cart (user_id, product_id, quantity) VALUES (?, ?, ?)");
+ $stmt->execute([$userId, $productId, $quantity]);
+ }
+
+ echo json_encode(['success' => true, 'message' => 'Товар добавлен в корзину']);
+ break;
+
+ case 'update':
+ $productId = (int)($_POST['product_id'] ?? 0);
+ $quantity = (int)($_POST['quantity'] ?? 1);
+
+ if ($quantity <= 0) {
+
+ $stmt = $db->prepare("DELETE FROM cart WHERE user_id = ? AND product_id = ?");
+ $stmt->execute([$userId, $productId]);
+ } else {
+ $stmt = $db->prepare("UPDATE cart SET quantity = ?, updated_at = CURRENT_TIMESTAMP WHERE user_id = ? AND product_id = ?");
+ $stmt->execute([$quantity, $userId, $productId]);
+ }
+
+ echo json_encode(['success' => true, 'message' => 'Корзина обновлена']);
+ break;
+
+ case 'remove':
+ $productId = (int)($_POST['product_id'] ?? 0);
+
+ $stmt = $db->prepare("DELETE FROM cart WHERE user_id = ? AND product_id = ?");
+ $stmt->execute([$userId, $productId]);
+
+ echo json_encode(['success' => true, 'message' => 'Товар удален из корзины']);
+ break;
+
+ case 'get':
+ $stmt = $db->prepare("
+ SELECT c.cart_id, c.product_id, c.quantity, p.name, p.price, p.image_url, p.stock_quantity
+ FROM cart c
+ JOIN products p ON c.product_id = p.product_id
+ WHERE c.user_id = ? AND p.is_available = TRUE
+ ORDER BY c.created_at DESC
+ ");
+ $stmt->execute([$userId]);
+ $items = $stmt->fetchAll();
+
+ $total = 0;
+ foreach ($items as &$item) {
+ $item['subtotal'] = $item['price'] * $item['quantity'];
+ $total += $item['subtotal'];
+ }
+
+ echo json_encode([
+ 'success' => true,
+ 'items' => $items,
+ 'total' => $total,
+ 'count' => array_sum(array_column($items, 'quantity'))
+ ]);
+ break;
+
+ case 'count':
+ $stmt = $db->prepare("SELECT COALESCE(SUM(quantity), 0) FROM cart WHERE user_id = ?");
+ $stmt->execute([$userId]);
+ $count = $stmt->fetchColumn();
+
+ echo json_encode(['success' => true, 'count' => (int)$count]);
+ break;
+
+ case 'clear':
+ $stmt = $db->prepare("DELETE FROM cart WHERE user_id = ?");
+ $stmt->execute([$userId]);
+
+ echo json_encode(['success' => true, 'message' => 'Корзина очищена']);
+ break;
+
+ default:
+ echo json_encode(['success' => false, 'message' => 'Неизвестное действие']);
+ }
+
+} catch (PDOException $e) {
+ echo json_encode(['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()]);
+}
diff --git a/get_cart.php b/public/api/get_cart.php
similarity index 88%
rename from get_cart.php
rename to public/api/get_cart.php
index dbef740..2d43ea4 100644
--- a/get_cart.php
+++ b/public/api/get_cart.php
@@ -1,62 +1,61 @@
- false, 'message' => 'Требуется авторизация']);
- exit();
-}
-
-$user_id = $_SESSION['user_id'] ?? 0;
-
-if ($user_id == 0) {
- echo json_encode(['success' => false, 'message' => 'Пользователь не найден']);
- exit();
-}
-
-$db = Database::getInstance()->getConnection();
-
-try {
- // Получаем корзину из БД
- $stmt = $db->prepare("
- SELECT
- c.cart_id,
- c.product_id,
- c.quantity,
- p.name,
- p.price,
- p.image_url,
- p.stock_quantity
- FROM cart c
- JOIN products p ON c.product_id = p.product_id
- WHERE c.user_id = ? AND p.is_available = TRUE
- ORDER BY c.created_at DESC
- ");
- $stmt->execute([$user_id]);
- $cart_items = $stmt->fetchAll();
-
- // Обновляем сессию
- $_SESSION['cart'] = [];
- foreach ($cart_items as $item) {
- $_SESSION['cart'][$item['product_id']] = [
- 'quantity' => $item['quantity'],
- 'name' => $item['name'],
- 'price' => $item['price'],
- 'added_at' => time()
- ];
- }
-
- echo json_encode([
- 'success' => true,
- 'cart_items' => $cart_items,
- 'total_items' => count($cart_items)
- ]);
-
-} catch (PDOException $e) {
- echo json_encode([
- 'success' => false,
- 'message' => 'Ошибка базы данных: ' . $e->getMessage()
- ]);
-}
+ false, 'message' => 'Требуется авторизация']);
+ exit();
+}
+
+$user_id = $_SESSION['user_id'] ?? 0;
+
+if ($user_id == 0) {
+ echo json_encode(['success' => false, 'message' => 'Пользователь не найден']);
+ exit();
+}
+
+$db = Database::getInstance()->getConnection();
+
+try {
+
+ $stmt = $db->prepare("
+ SELECT
+ c.cart_id,
+ c.product_id,
+ c.quantity,
+ p.name,
+ p.price,
+ p.image_url,
+ p.stock_quantity
+ FROM cart c
+ JOIN products p ON c.product_id = p.product_id
+ WHERE c.user_id = ? AND p.is_available = TRUE
+ ORDER BY c.created_at DESC
+ ");
+ $stmt->execute([$user_id]);
+ $cart_items = $stmt->fetchAll();
+
+ $_SESSION['cart'] = [];
+ foreach ($cart_items as $item) {
+ $_SESSION['cart'][$item['product_id']] = [
+ 'quantity' => $item['quantity'],
+ 'name' => $item['name'],
+ 'price' => $item['price'],
+ 'added_at' => time()
+ ];
+ }
+
+ echo json_encode([
+ 'success' => true,
+ 'cart_items' => $cart_items,
+ 'total_items' => count($cart_items)
+ ]);
+
+} catch (PDOException $e) {
+ echo json_encode([
+ 'success' => false,
+ 'message' => 'Ошибка базы данных: ' . $e->getMessage()
+ ]);
+}
?>
\ No newline at end of file
diff --git a/get_cart_count.php b/public/api/get_cart_count.php
similarity index 88%
rename from get_cart_count.php
rename to public/api/get_cart_count.php
index 39cdc01..f1d59c2 100644
--- a/get_cart_count.php
+++ b/public/api/get_cart_count.php
@@ -1,22 +1,22 @@
- false, 'cart_count' => 0]);
- exit();
-}
-
-$user_id = $_SESSION['user_id'] ?? 0;
-$db = Database::getInstance()->getConnection();
-
-try {
- $stmt = $db->prepare("SELECT SUM(quantity) as total FROM cart WHERE user_id = ?");
- $stmt->execute([$user_id]);
- $cart_count = $stmt->fetchColumn() ?: 0;
-
- echo json_encode(['success' => true, 'cart_count' => $cart_count]);
-} catch (PDOException $e) {
- echo json_encode(['success' => false, 'cart_count' => 0]);
+ false, 'cart_count' => 0]);
+ exit();
+}
+
+$user_id = $_SESSION['user_id'] ?? 0;
+$db = Database::getInstance()->getConnection();
+
+try {
+ $stmt = $db->prepare("SELECT SUM(quantity) as total FROM cart WHERE user_id = ?");
+ $stmt->execute([$user_id]);
+ $cart_count = $stmt->fetchColumn() ?: 0;
+
+ echo json_encode(['success' => true, 'cart_count' => $cart_count]);
+} catch (PDOException $e) {
+ echo json_encode(['success' => false, 'cart_count' => 0]);
}
\ No newline at end of file
diff --git a/get_product.php b/public/api/get_product.php
similarity index 85%
rename from get_product.php
rename to public/api/get_product.php
index e801cd7..0cd3ca6 100644
--- a/get_product.php
+++ b/public/api/get_product.php
@@ -1,33 +1,32 @@
- false, 'message' => 'Доступ запрещен']);
- exit();
-}
-
-if (!isset($_GET['id'])) {
- echo json_encode(['success' => false, 'message' => 'ID не указан']);
- exit();
-}
-
-try {
- $db = Database::getInstance()->getConnection();
- $product_id = $_GET['id'];
-
- $stmt = $db->prepare("SELECT * FROM products WHERE product_id = ?");
- $stmt->execute([$product_id]);
- $product = $stmt->fetch();
-
- if ($product) {
- echo json_encode($product);
- } else {
- echo json_encode(['success' => false, 'message' => 'Товар не найден']);
- }
-
-} catch (PDOException $e) {
- echo json_encode(['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()]);
-}
+ false, 'message' => 'Доступ запрещен']);
+ exit();
+}
+
+if (!isset($_GET['id'])) {
+ echo json_encode(['success' => false, 'message' => 'ID не указан']);
+ exit();
+}
+
+try {
+ $db = Database::getInstance()->getConnection();
+ $product_id = $_GET['id'];
+
+ $stmt = $db->prepare("SELECT * FROM products WHERE product_id = ?");
+ $stmt->execute([$product_id]);
+ $product = $stmt->fetch();
+
+ if ($product) {
+ echo json_encode($product);
+ } else {
+ echo json_encode(['success' => false, 'message' => 'Товар не найден']);
+ }
+
+} catch (PDOException $e) {
+ echo json_encode(['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()]);
+}
?>
\ No newline at end of file
diff --git a/process_order.php b/public/api/process_order.php
similarity index 65%
rename from process_order.php
rename to public/api/process_order.php
index e7f5805..1e59cb3 100644
--- a/process_order.php
+++ b/public/api/process_order.php
@@ -1,134 +1,136 @@
-getConnection();
-
- try {
- $db->beginTransaction();
-
- // Получаем данные из формы
- $customer_name = $_POST['full_name'] ?? '';
- $customer_email = $_POST['email'] ?? '';
- $customer_phone = $_POST['phone'] ?? '';
- $delivery_address = $_POST['address'] ?? '';
- $region = $_POST['region'] ?? '';
- $payment_method = $_POST['payment'] ?? 'card';
- $delivery_method = $_POST['delivery'] ?? 'courier';
- $notes = $_POST['notes'] ?? '';
- $discount_amount = floatval($_POST['discount'] ?? 0);
- $delivery_cost = floatval($_POST['delivery_price'] ?? 2000);
-
- // Генерируем номер заказа
- $order_number = 'ORD-' . date('Ymd-His') . '-' . rand(1000, 9999);
-
- // Получаем корзину пользователя
- $cartStmt = $db->prepare("
- SELECT
- c.product_id,
- c.quantity,
- p.name,
- p.price,
- p.stock_quantity
- FROM cart c
- JOIN products p ON c.product_id = p.product_id
- WHERE c.user_id = ?
- ");
- $cartStmt->execute([$user_id]);
- $cart_items = $cartStmt->fetchAll();
-
- if (empty($cart_items)) {
- throw new Exception('Корзина пуста');
- }
-
- // Рассчитываем итоги
- $total_amount = 0;
- foreach ($cart_items as $item) {
- $total_amount += $item['price'] * $item['quantity'];
- }
-
- $final_amount = $total_amount - $discount_amount + $delivery_cost;
-
- // Создаем заказ
- $orderStmt = $db->prepare("
- INSERT INTO orders (
- user_id, order_number, total_amount, discount_amount,
- delivery_cost, final_amount, status, payment_method,
- delivery_method, delivery_address, customer_name,
- customer_email, customer_phone, notes
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- RETURNING order_id
- ");
-
- $orderStmt->execute([
- $user_id, $order_number, $total_amount, $discount_amount,
- $delivery_cost, $final_amount, 'pending', $payment_method,
- $delivery_method, $delivery_address, $customer_name,
- $customer_email, $customer_phone, $notes
- ]);
-
- $order_id = $orderStmt->fetchColumn();
-
- // Добавляем товары в заказ и обновляем остатки
- foreach ($cart_items as $item) {
- // Добавляем в order_items
- $itemStmt = $db->prepare("
- INSERT INTO order_items (
- order_id, product_id, product_name,
- quantity, unit_price, total_price
- ) VALUES (?, ?, ?, ?, ?, ?)
- ");
-
- $item_total = $item['price'] * $item['quantity'];
- $itemStmt->execute([
- $order_id, $item['product_id'], $item['name'],
- $item['quantity'], $item['price'], $item_total
- ]);
-
- // Обновляем остатки на складе
- $updateStmt = $db->prepare("
- UPDATE products
- SET stock_quantity = stock_quantity - ?,
- updated_at = CURRENT_TIMESTAMP
- WHERE product_id = ?
- ");
- $updateStmt->execute([$item['quantity'], $item['product_id']]);
- }
-
- // Очищаем корзину
- $clearCartStmt = $db->prepare("DELETE FROM cart WHERE user_id = ?");
- $clearCartStmt->execute([$user_id]);
-
- // Очищаем сессию
- unset($_SESSION['cart']);
-
- $db->commit();
-
- // Перенаправляем на страницу успеха
- header('Location: order_success.php?id=' . $order_id);
- exit();
-
- } catch (Exception $e) {
- $db->rollBack();
- header('Location: оформление_заказа.php?error=' . urlencode($e->getMessage()));
- exit();
- }
-} else {
- header('Location: оформление_заказа.php');
- exit();
-}
+ false, 'message' => 'Требуется авторизация']);
+ exit();
+}
+
+$user_id = $_SESSION['user_id'] ?? 0;
+
+if ($user_id == 0) {
+ echo json_encode(['success' => false, 'message' => 'Пользователь не найден']);
+ exit();
+}
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $db = Database::getInstance()->getConnection();
+
+ try {
+ $db->beginTransaction();
+
+ $customer_name = $_POST['full_name'] ?? '';
+ $customer_email = $_POST['email'] ?? '';
+ $customer_phone = $_POST['phone'] ?? '';
+ $delivery_address = $_POST['address'] ?? '';
+ $region = $_POST['region'] ?? '';
+ $postal_code = $_POST['postal_code'] ?? '';
+ $payment_method = $_POST['payment'] ?? 'card';
+ $delivery_method = $_POST['delivery'] ?? 'courier';
+ $promo_code = $_POST['promo_code'] ?? '';
+ $notes = $_POST['notes'] ?? '';
+ $discount_amount = floatval($_POST['discount'] ?? 0);
+ $delivery_cost = floatval($_POST['delivery_price'] ?? 2000);
+
+ $order_number = 'ORD-' . date('Ymd-His') . '-' . rand(1000, 9999);
+
+ $cartStmt = $db->prepare("
+ SELECT
+ c.product_id,
+ c.quantity,
+ p.name,
+ p.price,
+ p.stock_quantity
+ FROM cart c
+ JOIN products p ON c.product_id = p.product_id
+ WHERE c.user_id = ?
+ ");
+ $cartStmt->execute([$user_id]);
+ $cart_items = $cartStmt->fetchAll();
+
+ if (empty($cart_items)) {
+ throw new Exception('Корзина пуста');
+ }
+
+ $total_amount = 0;
+ foreach ($cart_items as $item) {
+ $total_amount += $item['price'] * $item['quantity'];
+ }
+
+ $final_amount = $total_amount - $discount_amount + $delivery_cost;
+
+ $orderStmt = $db->prepare("
+ INSERT INTO orders (
+ user_id, order_number, subtotal, discount_amount,
+ delivery_price, final_amount, status, payment_method,
+ delivery_method, delivery_address, delivery_region,
+ postal_code, promo_code, customer_name, customer_email,
+ customer_phone, notes
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ RETURNING order_id
+ ");
+
+ $orderStmt->execute([
+ $user_id, $order_number, $total_amount, $discount_amount,
+ $delivery_cost, $final_amount, 'pending', $payment_method,
+ $delivery_method, $delivery_address, $region, $postal_code,
+ $promo_code, $customer_name, $customer_email, $customer_phone, $notes
+ ]);
+
+ $order_id = $orderStmt->fetchColumn();
+
+ foreach ($cart_items as $item) {
+
+ $itemStmt = $db->prepare("
+ INSERT INTO order_items (
+ order_id, product_id, product_name,
+ quantity, product_price, total_price
+ ) VALUES (?, ?, ?, ?, ?, ?)
+ ");
+
+ $item_total = $item['price'] * $item['quantity'];
+ $itemStmt->execute([
+ $order_id, $item['product_id'], $item['name'],
+ $item['quantity'], $item['price'], $item_total
+ ]);
+
+ $updateStmt = $db->prepare("
+ UPDATE products
+ SET stock_quantity = stock_quantity - ?,
+ updated_at = CURRENT_TIMESTAMP
+ WHERE product_id = ?
+ ");
+ $updateStmt->execute([$item['quantity'], $item['product_id']]);
+ }
+
+ $clearCartStmt = $db->prepare("DELETE FROM cart WHERE user_id = ?");
+ $clearCartStmt->execute([$user_id]);
+
+ unset($_SESSION['cart']);
+
+ $db->commit();
+
+ echo json_encode([
+ 'success' => true,
+ 'order_id' => $order_id,
+ 'order_number' => $order_number,
+ 'message' => 'Заказ успешно оформлен'
+ ]);
+ exit();
+
+ } catch (Exception $e) {
+ $db->rollBack();
+ echo json_encode([
+ 'success' => false,
+ 'message' => $e->getMessage()
+ ]);
+ exit();
+ }
+} else {
+ echo json_encode(['success' => false, 'message' => 'Неверный метод запроса']);
+ exit();
+}
?>
\ No newline at end of file
diff --git a/register_handler.php b/public/api/register_handler.php
similarity index 52%
rename from register_handler.php
rename to public/api/register_handler.php
index 76ce3b9..d1cfb81 100644
--- a/register_handler.php
+++ b/public/api/register_handler.php
@@ -1,182 +1,162 @@
- $full_name,
- 'city' => $city,
- 'email' => $email,
- 'phone' => $phone
- ];
- header('Location: профиль.php');
- exit();
- }
-
- // Подключаемся к базе данных
- $db = Database::getInstance()->getConnection();
-
- try {
- // Проверяем, существует ли пользователь с таким email
- $checkStmt = $db->prepare("SELECT user_id FROM users WHERE email = ?");
- $checkStmt->execute([$email]);
-
- if ($checkStmt->fetch()) {
- $_SESSION['registration_errors'] = ['Пользователь с таким email уже существует'];
- $_SESSION['old_data'] = [
- 'fio' => $full_name,
- 'city' => $city,
- 'email' => $email,
- 'phone' => $phone
- ];
- header('Location: профиль.php');
- exit();
- }
-
- // Хэшируем пароль
- $password_hash = password_hash($password, PASSWORD_DEFAULT);
-
- // Определяем, является ли пользователь администратором
- $is_admin = false;
- $admin_emails = ['admin@aeterna.ru', 'administrator@aeterna.ru', 'aeterna@mail.ru'];
- if (in_array(strtolower($email), $admin_emails)) {
- $is_admin = true;
- }
-
- // РАЗНЫЕ ВАРИАНТЫ ДЛЯ ТЕСТИРОВАНИЯ - РАСКОММЕНТИРУЙТЕ НУЖНЫЙ
-
- // Вариант 1: С явным преобразованием boolean (самый надежный)
- $stmt = $db->prepare("
- INSERT INTO users (email, password_hash, full_name, phone, city, is_admin)
- VALUES (?, ?, ?, ?, ?, ?)
- RETURNING user_id
- ");
-
- // Преобразуем boolean в integer для PostgreSQL
- $stmt->execute([
- $email,
- $password_hash,
- $full_name,
- $phone,
- $city,
- $is_admin ? 1 : 0 // Преобразуем в integer (1 или 0)
- ]);
-
- // Вариант 2: С использованием CAST в SQL (альтернатива)
- /*
- $stmt = $db->prepare("
- INSERT INTO users (email, password_hash, full_name, phone, city, is_admin)
- VALUES (?, ?, ?, ?, ?, CAST(? AS boolean))
- RETURNING user_id
- ");
-
- $stmt->execute([
- $email,
- $password_hash,
- $full_name,
- $phone,
- $city,
- $is_admin ? 'true' : 'false' // Строковые значения true/false
- ]);
- */
-
- $user_id = $stmt->fetchColumn();
-
- if ($user_id) {
- // Автоматически авторизуем пользователя
- $_SESSION['user_id'] = $user_id;
- $_SESSION['user_email'] = $email;
- $_SESSION['full_name'] = $full_name;
- $_SESSION['user_phone'] = $phone;
- $_SESSION['user_city'] = $city;
- $_SESSION['isLoggedIn'] = true;
- $_SESSION['isAdmin'] = $is_admin;
- $_SESSION['login_time'] = time();
-
- // Обновляем время последнего входа
- $updateStmt = $db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE user_id = ?");
- $updateStmt->execute([$user_id]);
-
- // Перенаправляем на главную или каталог
- $_SESSION['registration_success'] = 'Регистрация прошла успешно! ' .
- ($is_admin ? 'Вы зарегистрированы как администратор.' : 'Добро пожаловать в AETERNA!');
-
- header('Location: catalog.php');
- exit();
- } else {
- throw new Exception('Ошибка при создании пользователя');
- }
-
- } catch (PDOException $e) {
- // Логируем полную ошибку для отладки
- error_log("Registration DB Error: " . $e->getMessage());
- error_log("SQL State: " . $e->getCode());
- error_log("Email: " . $email);
-
- $_SESSION['registration_errors'] = ['Ошибка базы данных: ' . $e->getMessage()];
- $_SESSION['old_data'] = [
- 'fio' => $full_name,
- 'city' => $city,
- 'email' => $email,
- 'phone' => $phone
- ];
- header('Location: профиль.php');
- exit();
- } catch (Exception $e) {
- error_log("Registration Error: " . $e->getMessage());
-
- $_SESSION['registration_errors'] = [$e->getMessage()];
- header('Location: профиль.php');
- exit();
- }
-
-} else {
- // Если запрос не POST, перенаправляем на форму регистрации
- header('Location: профиль.php');
- exit();
-}
+ $full_name,
+ 'city' => $city,
+ 'email' => $email,
+ 'phone' => $phone
+ ];
+ header('Location: ../register.php');
+ exit();
+ }
+
+ $db = Database::getInstance()->getConnection();
+
+ try {
+
+ $checkStmt = $db->prepare("SELECT user_id FROM users WHERE email = ?");
+ $checkStmt->execute([$email]);
+
+ if ($checkStmt->fetch()) {
+ $_SESSION['registration_errors'] = ['Пользователь с таким email уже существует'];
+ $_SESSION['old_data'] = [
+ 'fio' => $full_name,
+ 'city' => $city,
+ 'email' => $email,
+ 'phone' => $phone
+ ];
+ header('Location: ../register.php');
+ exit();
+ }
+
+ $password_hash = password_hash($password, PASSWORD_DEFAULT);
+
+ $is_admin = false;
+ $admin_emails = ['admin@aeterna.ru', 'administrator@aeterna.ru', 'aeterna@mail.ru'];
+ if (in_array(strtolower($email), $admin_emails)) {
+ $is_admin = true;
+ }
+
+ $stmt = $db->prepare("
+ INSERT INTO users (email, password_hash, full_name, phone, city, is_admin, is_active)
+ VALUES (?, ?, ?, ?, ?, CAST(? AS boolean), TRUE)
+ RETURNING user_id
+ ");
+
+ $stmt->execute([
+ $email,
+ $password_hash,
+ $full_name,
+ $phone,
+ $city,
+ $is_admin ? 'true' : 'false'
+ ]);
+
+ $user_id = $stmt->fetchColumn();
+
+ if (!$user_id) {
+ throw new Exception('Ошибка при создании пользователя: user_id не получен');
+ }
+
+ $verifyStmt = $db->prepare("SELECT user_id, email, password_hash FROM users WHERE user_id = ?");
+ $verifyStmt->execute([$user_id]);
+ $verifyUser = $verifyStmt->fetch(PDO::FETCH_ASSOC);
+
+ if (!$verifyUser) {
+ throw new Exception('Ошибка: пользователь не найден после создания');
+ }
+
+ if (empty($verifyUser['password_hash'])) {
+ throw new Exception('Ошибка: пароль не сохранен');
+ }
+
+ $_SESSION['user_id'] = $user_id;
+ $_SESSION['user_email'] = $email;
+ $_SESSION['full_name'] = $full_name;
+ $_SESSION['user_phone'] = $phone;
+ $_SESSION['user_city'] = $city;
+ $_SESSION['isLoggedIn'] = true;
+ $_SESSION['isAdmin'] = (bool)$is_admin;
+ $_SESSION['login_time'] = time();
+
+ $updateStmt = $db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE user_id = ?");
+ $updateStmt->execute([$user_id]);
+
+ $_SESSION['registration_success'] = 'Регистрация прошла успешно! ' .
+ ($is_admin ? 'Вы зарегистрированы как администратор.' : 'Добро пожаловать в AETERNA!');
+
+ header('Location: ../catalog.php');
+ exit();
+
+ } catch (PDOException $e) {
+
+ error_log("Registration DB Error: " . $e->getMessage());
+ error_log("SQL State: " . $e->getCode());
+ error_log("Email: " . $email);
+
+ $_SESSION['registration_errors'] = ['Ошибка базы данных: ' . $e->getMessage()];
+ $_SESSION['old_data'] = [
+ 'fio' => $full_name,
+ 'city' => $city,
+ 'email' => $email,
+ 'phone' => $phone
+ ];
+ header('Location: ../register.php');
+ exit();
+ } catch (Exception $e) {
+ error_log("Registration Error: " . $e->getMessage());
+
+ $_SESSION['registration_errors'] = [$e->getMessage()];
+ header('Location: ../register.php');
+ exit();
+ }
+
+} else {
+
+ header('Location: register.php');
+ exit();
+}
?>
\ No newline at end of file
diff --git a/update_cart.php b/public/api/update_cart.php
similarity index 92%
rename from update_cart.php
rename to public/api/update_cart.php
index 8ab9f4a..0063946 100644
--- a/update_cart.php
+++ b/public/api/update_cart.php
@@ -1,6 +1,6 @@
false, 'message' => 'Требуется авторизация']);
diff --git a/img2/1 — копия.jpg b/public/assets/img/1 — копия.jpg
similarity index 100%
rename from img2/1 — копия.jpg
rename to public/assets/img/1 — копия.jpg
diff --git a/img/1.jpg b/public/assets/img/1.jpg
similarity index 100%
rename from img/1.jpg
rename to public/assets/img/1.jpg
diff --git a/img2/100.jpg b/public/assets/img/100.jpg
similarity index 100%
rename from img2/100.jpg
rename to public/assets/img/100.jpg
diff --git a/img2/11.jpg b/public/assets/img/11.jpg
similarity index 100%
rename from img2/11.jpg
rename to public/assets/img/11.jpg
diff --git a/img2/111.jpg b/public/assets/img/111.jpg
similarity index 100%
rename from img2/111.jpg
rename to public/assets/img/111.jpg
diff --git a/img2/11_1.png b/public/assets/img/11_1.png
similarity index 100%
rename from img2/11_1.png
rename to public/assets/img/11_1.png
diff --git a/img/1_1.jpg b/public/assets/img/1_1.jpg
similarity index 100%
rename from img/1_1.jpg
rename to public/assets/img/1_1.jpg
diff --git a/img2/1_2.jpg b/public/assets/img/1_2.jpg
similarity index 100%
rename from img2/1_2.jpg
rename to public/assets/img/1_2.jpg
diff --git a/img2/1_2.png b/public/assets/img/1_2.png
similarity index 100%
rename from img2/1_2.png
rename to public/assets/img/1_2.png
diff --git a/img/2.jpg b/public/assets/img/2.jpg
similarity index 100%
rename from img/2.jpg
rename to public/assets/img/2.jpg
diff --git a/img2/22.jpg b/public/assets/img/22.jpg
similarity index 100%
rename from img2/22.jpg
rename to public/assets/img/22.jpg
diff --git a/img2/25.jpg b/public/assets/img/25.jpg
similarity index 100%
rename from img2/25.jpg
rename to public/assets/img/25.jpg
diff --git a/img2/2_2.jpg b/public/assets/img/2_2.jpg
similarity index 100%
rename from img2/2_2.jpg
rename to public/assets/img/2_2.jpg
diff --git a/img2/2_2.png b/public/assets/img/2_2.png
similarity index 100%
rename from img2/2_2.png
rename to public/assets/img/2_2.png
diff --git a/img2/3.jpg b/public/assets/img/3.jpg
similarity index 100%
rename from img2/3.jpg
rename to public/assets/img/3.jpg
diff --git a/img/3_3.jpg b/public/assets/img/3_3.jpg
similarity index 100%
rename from img/3_3.jpg
rename to public/assets/img/3_3.jpg
diff --git a/img2/3_3.png b/public/assets/img/3_3.png
similarity index 100%
rename from img2/3_3.png
rename to public/assets/img/3_3.png
diff --git a/img2/4.jpg b/public/assets/img/4.jpg
similarity index 100%
rename from img2/4.jpg
rename to public/assets/img/4.jpg
diff --git a/img2/44.jpg b/public/assets/img/44.jpg
similarity index 100%
rename from img2/44.jpg
rename to public/assets/img/44.jpg
diff --git a/img2/444 b/public/assets/img/444
similarity index 100%
rename from img2/444
rename to public/assets/img/444
diff --git a/img2/444 (1).png b/public/assets/img/444 (1).png
similarity index 100%
rename from img2/444 (1).png
rename to public/assets/img/444 (1).png
diff --git a/img2/444.jpg b/public/assets/img/444.jpg
similarity index 100%
rename from img2/444.jpg
rename to public/assets/img/444.jpg
diff --git a/img2/444.png b/public/assets/img/444.png
similarity index 100%
rename from img2/444.png
rename to public/assets/img/444.png
diff --git a/img2/4_1.jpg b/public/assets/img/4_1.jpg
similarity index 100%
rename from img2/4_1.jpg
rename to public/assets/img/4_1.jpg
diff --git a/img/5.jpg b/public/assets/img/5.jpg
similarity index 100%
rename from img/5.jpg
rename to public/assets/img/5.jpg
diff --git a/img/5_5.jpg b/public/assets/img/5_5.jpg
similarity index 100%
rename from img/5_5.jpg
rename to public/assets/img/5_5.jpg
diff --git a/img2/5_5.png b/public/assets/img/5_5.png
similarity index 100%
rename from img2/5_5.png
rename to public/assets/img/5_5.png
diff --git a/img2/6.jpg b/public/assets/img/6.jpg
similarity index 100%
rename from img2/6.jpg
rename to public/assets/img/6.jpg
diff --git a/img/6_6.jpg b/public/assets/img/6_6.jpg
similarity index 100%
rename from img/6_6.jpg
rename to public/assets/img/6_6.jpg
diff --git a/img2/6_6.png b/public/assets/img/6_6.png
similarity index 100%
rename from img2/6_6.png
rename to public/assets/img/6_6.png
diff --git a/img2/7.jpg b/public/assets/img/7.jpg
similarity index 100%
rename from img2/7.jpg
rename to public/assets/img/7.jpg
diff --git a/img2/77.jpg b/public/assets/img/77.jpg
similarity index 100%
rename from img2/77.jpg
rename to public/assets/img/77.jpg
diff --git a/img2/777 (1).png b/public/assets/img/777 (1).png
similarity index 100%
rename from img2/777 (1).png
rename to public/assets/img/777 (1).png
diff --git a/img2/777.jpg b/public/assets/img/777.jpg
similarity index 100%
rename from img2/777.jpg
rename to public/assets/img/777.jpg
diff --git a/img2/777.png b/public/assets/img/777.png
similarity index 100%
rename from img2/777.png
rename to public/assets/img/777.png
diff --git a/img/7_7.jpg b/public/assets/img/7_7.jpg
similarity index 100%
rename from img/7_7.jpg
rename to public/assets/img/7_7.jpg
diff --git a/img2/7_7.png b/public/assets/img/7_7.png
similarity index 100%
rename from img2/7_7.png
rename to public/assets/img/7_7.png
diff --git a/img2/8.jpg b/public/assets/img/8.jpg
similarity index 100%
rename from img2/8.jpg
rename to public/assets/img/8.jpg
diff --git a/img2/88.jpg b/public/assets/img/88.jpg
similarity index 100%
rename from img2/88.jpg
rename to public/assets/img/88.jpg
diff --git a/img2/888 (1).png b/public/assets/img/888 (1).png
similarity index 100%
rename from img2/888 (1).png
rename to public/assets/img/888 (1).png
diff --git a/img2/888.jpg b/public/assets/img/888.jpg
similarity index 100%
rename from img2/888.jpg
rename to public/assets/img/888.jpg
diff --git a/img2/888.png b/public/assets/img/888.png
similarity index 100%
rename from img2/888.png
rename to public/assets/img/888.png
diff --git a/img2/8_8.png b/public/assets/img/8_8.png
similarity index 100%
rename from img2/8_8.png
rename to public/assets/img/8_8.png
diff --git a/img2/9.jpg b/public/assets/img/9.jpg
similarity index 100%
rename from img2/9.jpg
rename to public/assets/img/9.jpg
diff --git a/img2/99.jpg b/public/assets/img/99.jpg
similarity index 100%
rename from img2/99.jpg
rename to public/assets/img/99.jpg
diff --git a/img2/99.png b/public/assets/img/99.png
similarity index 100%
rename from img2/99.png
rename to public/assets/img/99.png
diff --git a/img2/99_1.jpg b/public/assets/img/99_1.jpg
similarity index 100%
rename from img2/99_1.jpg
rename to public/assets/img/99_1.jpg
diff --git a/img2/99_2.jpg b/public/assets/img/99_2.jpg
similarity index 100%
rename from img2/99_2.jpg
rename to public/assets/img/99_2.jpg
diff --git a/img2/99_3.png b/public/assets/img/99_3.png
similarity index 100%
rename from img2/99_3.png
rename to public/assets/img/99_3.png
diff --git a/img/9_9.jpg b/public/assets/img/9_9.jpg
similarity index 100%
rename from img/9_9.jpg
rename to public/assets/img/9_9.jpg
diff --git a/img2/9_9.png b/public/assets/img/9_9.png
similarity index 100%
rename from img2/9_9.png
rename to public/assets/img/9_9.png
diff --git a/img2/black.png b/public/assets/img/black.png
similarity index 100%
rename from img2/black.png
rename to public/assets/img/black.png
diff --git a/img2/black1.png b/public/assets/img/black1.png
similarity index 100%
rename from img2/black1.png
rename to public/assets/img/black1.png
diff --git a/img2/black2.png b/public/assets/img/black2.png
similarity index 100%
rename from img2/black2.png
rename to public/assets/img/black2.png
diff --git a/img2/brown.png b/public/assets/img/brown.png
similarity index 100%
rename from img2/brown.png
rename to public/assets/img/brown.png
diff --git a/img2/brown1.png b/public/assets/img/brown1.png
similarity index 100%
rename from img2/brown1.png
rename to public/assets/img/brown1.png
diff --git a/img2/brown2.png b/public/assets/img/brown2.png
similarity index 100%
rename from img2/brown2.png
rename to public/assets/img/brown2.png
diff --git a/img/chair.PNG b/public/assets/img/chair.PNG
similarity index 100%
rename from img/chair.PNG
rename to public/assets/img/chair.PNG
diff --git a/img2/gray.png b/public/assets/img/gray.png
similarity index 100%
rename from img2/gray.png
rename to public/assets/img/gray.png
diff --git a/img2/gray1.png b/public/assets/img/gray1.png
similarity index 100%
rename from img2/gray1.png
rename to public/assets/img/gray1.png
diff --git a/img2/gray2.png b/public/assets/img/gray2.png
similarity index 100%
rename from img2/gray2.png
rename to public/assets/img/gray2.png
diff --git a/img/диван.jpg b/public/assets/img/диван.jpg
similarity index 100%
rename from img/диван.jpg
rename to public/assets/img/диван.jpg
diff --git a/img/диван_1.jpg b/public/assets/img/диван_1.jpg
similarity index 100%
rename from img/диван_1.jpg
rename to public/assets/img/диван_1.jpg
diff --git a/img/кресло.jpg b/public/assets/img/кресло.jpg
similarity index 100%
rename from img/кресло.jpg
rename to public/assets/img/кресло.jpg
diff --git a/img/кресло_1.jpg b/public/assets/img/кресло_1.jpg
similarity index 100%
rename from img/кресло_1.jpg
rename to public/assets/img/кресло_1.jpg
diff --git a/img/слайдер_1.jpg b/public/assets/img/слайдер_1.jpg
similarity index 100%
rename from img/слайдер_1.jpg
rename to public/assets/img/слайдер_1.jpg
diff --git a/img/слайдер_2.jpg b/public/assets/img/слайдер_2.jpg
similarity index 100%
rename from img/слайдер_2.jpg
rename to public/assets/img/слайдер_2.jpg
diff --git a/img/слайдер_3.jpg b/public/assets/img/слайдер_3.jpg
similarity index 100%
rename from img/слайдер_3.jpg
rename to public/assets/img/слайдер_3.jpg
diff --git a/img/слайдер_4.jpg b/public/assets/img/слайдер_4.jpg
similarity index 100%
rename from img/слайдер_4.jpg
rename to public/assets/img/слайдер_4.jpg
diff --git a/img/слайдер_5.jpg b/public/assets/img/слайдер_5.jpg
similarity index 100%
rename from img/слайдер_5.jpg
rename to public/assets/img/слайдер_5.jpg
diff --git a/img/слайдер_6.jpg b/public/assets/img/слайдер_6.jpg
similarity index 100%
rename from img/слайдер_6.jpg
rename to public/assets/img/слайдер_6.jpg
diff --git a/img/спальня.jpg b/public/assets/img/спальня.jpg
similarity index 100%
rename from img/спальня.jpg
rename to public/assets/img/спальня.jpg
diff --git a/img/стили_оформления.css b/public/assets/img/стили_оформления.css
similarity index 87%
rename from img/стили_оформления.css
rename to public/assets/img/стили_оформления.css
index 581822b..2e1d09c 100644
--- a/img/стили_оформления.css
+++ b/public/assets/img/стили_оформления.css
@@ -1,62 +1,61 @@
-
-.error-message {
- color: #ff0000;
- font-size: 12px;
- margin-top: 5px;
- display: none;
-}
-
-.form__input.error {
- border-color: #ff0000;
-}
-
-.form__group {
- position: relative;
- margin-bottom: 15px;
-}
-
-/* Стили для сообщений внизу страницы */
-.page-messages {
- position: fixed;
- bottom: 20px;
- left: 50%;
- transform: translateX(-50%);
- z-index: 1000;
- width: 90%;
- max-width: 500px;
-}
-
-.message {
- padding: 15px;
- margin: 10px 0;
- border-radius: 5px;
- text-align: center;
- font-weight: bold;
- display: none;
-}
-
-.message.error {
- background-color: #ffebee;
- color: #c62828;
- border: 1px solid #ffcdd2;
-}
-
-.message.success {
- background-color: #e8f5e9;
- color: #453227;
- border: 1px solid #c8e6c9;
-}
-
-.message.warning {
- background-color: #fff3e0;
- color: #ef6c00;
- border: 1px solid #ffe0b2;
-}
-
-.privacy-error {
- color: #ff0000;
- font-size: 12px;
- margin-top: 5px;
- display: none;
- text-align: center;
-}
+
+.error-message {
+ color: #ff0000;
+ font-size: 12px;
+ margin-top: 5px;
+ display: none;
+}
+
+.form__input.error {
+ border-color: #ff0000;
+}
+
+.form__group {
+ position: relative;
+ margin-bottom: 15px;
+}
+
+.page-messages {
+ position: fixed;
+ bottom: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 1000;
+ width: 90%;
+ max-width: 500px;
+}
+
+.message {
+ padding: 15px;
+ margin: 10px 0;
+ border-radius: 5px;
+ text-align: center;
+ font-weight: bold;
+ display: none;
+}
+
+.message.error {
+ background-color: #ffebee;
+ color: #c62828;
+ border: 1px solid #ffcdd2;
+}
+
+.message.success {
+ background-color: #e8f5e9;
+ color: #453227;
+ border: 1px solid #c8e6c9;
+}
+
+.message.warning {
+ background-color: #fff3e0;
+ color: #ef6c00;
+ border: 1px solid #ffe0b2;
+}
+
+.privacy-error {
+ color: #ff0000;
+ font-size: 12px;
+ margin-top: 5px;
+ display: none;
+ text-align: center;
+}
diff --git a/оформление_заказа_jquery.js b/public/assets/js/checkout.js
similarity index 73%
rename from оформление_заказа_jquery.js
rename to public/assets/js/checkout.js
index 74e8305..24f4590 100644
--- a/оформление_заказа_jquery.js
+++ b/public/assets/js/checkout.js
@@ -1,346 +1,340 @@
-// script.js
-
-$(document).ready(function() {
- // Инициализация корзины
- let cart = {
- items: [
- { id: 1, name: 'Кресло OPPORTUNITY', price: 16999, quantity: 1 },
- { id: 2, name: 'Кресло GOLDEN', price: 19999, quantity: 1 },
- { id: 3, name: 'Светильник POLET', price: 7999, quantity: 1 }
- ],
- delivery: 2000,
- discount: 0
- };
-
- // Функция обновления общей суммы
- function updateTotal() {
- let productsTotal = 0;
- let totalCount = 0;
-
- // Пересчитываем товары
- $('.products__item').each(function() {
- const $item = $(this);
- const price = parseInt($item.data('price'));
- const quantity = parseInt($item.find('.products__qty-value').text());
-
- productsTotal += price * quantity;
- totalCount += quantity;
- });
-
- // Обновляем отображение
- $('.products-total').text(productsTotal + ' ₽');
- $('.summary-count').text(totalCount);
- $('.total-count').text(totalCount + ' шт.');
- $('.cart-count').text(totalCount);
-
- // Обновляем итоговую сумму
- const finalTotal = productsTotal + cart.delivery - cart.discount;
- $('.final-total').text(finalTotal + ' ₽');
- }
-
- // Функция валидации email
- function validateEmail(email) {
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- return emailRegex.test(email);
- }
-
- // Функция валидации имени (ФИО)
- function validateFullName(name) {
- // Проверяем, что имя содержит только буквы, пробелы, дефисы и апострофы
- const nameRegex = /^[a-zA-Zа-яА-ЯёЁ\s\-']+$/;
-
- // Проверяем, что имя состоит минимум из 2 слов
- const words = name.trim().split(/\s+/);
-
- return nameRegex.test(name) && words.length >= 2;
- }
-
- // Функция валидации телефона
- function validatePhone(phone) {
- // Российский формат телефона: +7XXXXXXXXXX
- const phoneRegex = /^\+7\d{10}$/;
- return phoneRegex.test(phone);
- }
-
- // Функция отображения сообщения
- function showMessage(messageId, duration = 5000) {
- // Скрываем все сообщения
- $('.message').hide();
-
- // Показываем нужное сообщение
- $(messageId).fadeIn(300);
-
- // Автоматически скрываем через указанное время
- if (duration > 0) {
- setTimeout(() => {
- $(messageId).fadeOut(300);
- }, duration);
- }
- }
-
- // Функция показа ошибки приватности
- function showPrivacyError(show) {
- if (show) {
- $('#privacy-error').show();
- } else {
- $('#privacy-error').hide();
- }
- }
-
- // Функция отображения ошибки конкретного поля
- function showFieldError(fieldId, message) {
- // Убираем старые ошибки
- $(fieldId).removeClass('error-input');
- $(fieldId + '-error').remove();
-
- if (message) {
- $(fieldId).addClass('error-input');
- $(fieldId).after('' + message + '
');
- }
- }
-
- // Валидация поля при потере фокуса с указанием конкретной ошибки
- $('#fullname').on('blur', function() {
- const value = $(this).val().trim();
- if (value) {
- if (!validateFullName(value)) {
- showFieldError('#fullname', 'ФИО должно содержать только буквы и состоять минимум из 2 слов');
- } else {
- showFieldError('#fullname', '');
- }
- } else {
- showFieldError('#fullname', '');
- }
- });
-
- $('#email').on('blur', function() {
- const value = $(this).val().trim();
- if (value) {
- if (!validateEmail(value)) {
- showFieldError('#email', 'Введите корректный email адрес (например: example@mail.ru)');
- } else {
- showFieldError('#email', '');
- }
- } else {
- showFieldError('#email', '');
- }
- });
-
- $('#phone').on('blur', function() {
- const value = $(this).val().trim();
- if (value) {
- if (!validatePhone(value)) {
- showFieldError('#phone', 'Введите номер в формате +7XXXXXXXXXX (10 цифр после +7)');
- } else {
- showFieldError('#phone', '');
- }
- } else {
- showFieldError('#phone', '');
- }
- });
-
- // Валидация обязательных полей
- $('#region').on('blur', function() {
- const value = $(this).val().trim();
- if (!value) {
- showFieldError('#region', 'Укажите регион доставки');
- } else {
- showFieldError('#region', '');
- }
- });
-
- $('#address').on('blur', function() {
- const value = $(this).val().trim();
- if (!value) {
- showFieldError('#address', 'Укажите адрес доставки (улица, дом, квартира)');
- } else {
- showFieldError('#address', '');
- }
- });
-
- // Очистка ошибки при начале ввода
- $('.form__input').on('input', function() {
- const fieldId = '#' + $(this).attr('id');
- showFieldError(fieldId, '');
- });
-
- // Обработчик увеличения количества
- $('.products__qty-btn.plus').click(function() {
- const $qtyValue = $(this).siblings('.products__qty-value');
- let quantity = parseInt($qtyValue.text());
- $qtyValue.text(quantity + 1);
- updateTotal();
- });
-
- // Обработчик уменьшения количества
- $('.products__qty-btn.minus').click(function() {
- const $qtyValue = $(this).siblings('.products__qty-value');
- let quantity = parseInt($qtyValue.text());
- if (quantity > 1) {
- $qtyValue.text(quantity - 1);
- updateTotal();
- }
- });
-
- // Обработчик удаления товара
- $('.remove-from-cart').click(function() {
- const $productItem = $(this).closest('.products__item');
- $productItem.fadeOut(300, function() {
- $(this).remove();
- updateTotal();
-
- // Показываем сообщение, если корзина пуста
- if ($('.products__item').length === 0) {
- $('.products__list').html('Корзина пуста
');
- }
- });
- });
-
- // Обработчик применения промокода
- $('.promo__btn').click(function() {
- const promoCode = $('.promo__input').val().toUpperCase();
-
- if (promoCode === 'SALE10') {
- cart.discount = Math.round(parseInt($('.products-total').text()) * 0.1);
- $('.discount-total').text(cart.discount + ' ₽');
- showMessage('#form-error', 3000);
- $('#form-error').text('Промокод применен! Скидка 10%').removeClass('error').addClass('success');
- } else if (promoCode === 'FREE') {
- cart.delivery = 0;
- $('.delivery-price').text('0 ₽');
- showMessage('#form-error', 3000);
- $('#form-error').text('Промокод применен! Бесплатная доставка').removeClass('error').addClass('success');
- } else if (promoCode) {
- showMessage('#form-error', 3000);
- $('#form-error').text('Промокод недействителен').removeClass('success').addClass('error');
- }
-
- updateTotal();
- });
-
- // Обработчик выбора доставки
- $('input[name="delivery"]').change(function() {
- if ($(this).val() === 'pickup') {
- cart.delivery = 0;
- $('.delivery-price').text('0 ₽');
- } else {
- cart.delivery = 2000;
- $('.delivery-price').text('2000 ₽');
- }
- updateTotal();
- });
-
- // Функция проверки всех полей формы
- function validateForm() {
- let isValid = true;
- let errorMessages = [];
-
- // Очищаем все старые ошибки
- $('.field-error').remove();
- $('.form__input').removeClass('error-input');
-
- // Проверка обязательных полей
- const requiredFields = [
- {
- id: '#fullname',
- value: $('#fullname').val().trim(),
- validator: validateFullName,
- required: true,
- message: 'ФИО должно содержать только буквы и состоять минимум из 2 слов'
- },
- {
- id: '#phone',
- value: $('#phone').val().trim(),
- validator: validatePhone,
- required: true,
- message: 'Введите корректный номер телефона в формате +7XXXXXXXXXX'
- },
- {
- id: '#email',
- value: $('#email').val().trim(),
- validator: validateEmail,
- required: true,
- message: 'Введите корректный email адрес'
- },
- {
- id: '#region',
- value: $('#region').val().trim(),
- validator: (val) => val.length > 0,
- required: true,
- message: 'Поле "Регион" обязательно для заполнения'
- },
- {
- id: '#address',
- value: $('#address').val().trim(),
- validator: (val) => val.length > 0,
- required: true,
- message: 'Поле "Адрес" обязательно для заполнения'
- }
- ];
-
- // Проверяем каждое поле
- requiredFields.forEach(field => {
- if (field.required && (!field.value || !field.validator(field.value))) {
- isValid = false;
- errorMessages.push(field.message);
- showFieldError(field.id, field.message);
- }
- });
-
- // Проверка согласия на обработку данных
- if (!$('#privacy-checkbox').is(':checked')) {
- isValid = false;
- showPrivacyError(true);
- errorMessages.push('Необходимо согласие на обработку персональных данных');
- } else {
- showPrivacyError(false);
- }
-
- // Показываем общее сообщение, если есть ошибки
- if (!isValid && errorMessages.length > 0) {
- showMessage('#form-error', 5000);
- $('#form-error').text('Исправьте следующие ошибки: ' + errorMessages.join('; ')).removeClass('success').addClass('error');
-
- // Прокручиваем к первой ошибке
- $('html, body').animate({
- scrollTop: $('.error-input').first().offset().top - 100
- }, 500);
- }
-
- return isValid;
- }
-
- // Обработчик оформления заказа
- $('#submit-order').click(function() {
- // Проверка валидации всех полей
- if (!validateForm()) {
- return;
- }
-
- // Симуляция отправки заказа
- $(this).prop('disabled', true).text('ОБРАБОТКА...');
-
- setTimeout(() => {
- showMessage('#order-success', 5000);
- $(this).prop('disabled', false).text('ОФОРМИТЬ ЗАКАЗ');
-
- // Здесь можно добавить редирект на страницу благодарности
- // window.location.href = 'спасибо.html';
- }, 2000);
- });
-
- // Маска для телефона
- $('#phone').on('input', function() {
- let phone = $(this).val().replace(/\D/g, '');
- if (phone.length > 0) {
- if (phone[0] !== '7' && phone[0] !== '8') {
- phone = '7' + phone;
- }
- phone = '+7' + phone.substring(1, 11);
- $(this).val(phone);
- }
- });
-
- // Инициализация при загрузке
- updateTotal();
-});
\ No newline at end of file
+$(document).ready(function() {
+ let cart = {
+ items: [
+ { id: 1, name: 'Кресло OPPORTUNITY', price: 16999, quantity: 1 },
+ { id: 2, name: 'Кресло GOLDEN', price: 19999, quantity: 1 },
+ { id: 3, name: 'Светильник POLET', price: 7999, quantity: 1 }
+ ],
+ delivery: 2000,
+ discount: 0
+ };
+
+ function updateTotal() {
+ let productsTotal = 0;
+ let totalCount = 0;
+
+ $('.products__item').each(function() {
+ const $item = $(this);
+ const price = parseInt($item.data('price'));
+ const quantity = parseInt($item.find('.products__qty-value').text());
+
+ productsTotal += price * quantity;
+ totalCount += quantity;
+ });
+
+ $('.products-total').text(productsTotal + ' ₽');
+ $('.summary-count').text(totalCount);
+ $('.total-count').text(totalCount + ' шт.');
+ if ($('.cart-count').length) {
+ $('.cart-count').text(totalCount);
+ }
+ if ($('#cartCount').length) {
+ $('#cartCount').text(totalCount);
+ }
+
+ const finalTotal = productsTotal + cart.delivery - cart.discount;
+ $('.final-total').text(finalTotal + ' ₽');
+ }
+
+ function validateEmail(email) {
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ return emailRegex.test(email);
+ }
+
+ function validateFullName(name) {
+ const nameRegex = /^[a-zA-Zа-яА-ЯёЁ\s\-']+$/;
+ const words = name.trim().split(/\s+/);
+
+ return nameRegex.test(name) && words.length >= 2;
+ }
+
+ function validatePhone(phone) {
+ const phoneRegex = /^\+7\d{10}$/;
+ return phoneRegex.test(phone);
+ }
+
+ function showMessage(messageId, duration = 5000) {
+ $('.message').hide();
+
+ $(messageId).fadeIn(300);
+
+ if (duration > 0) {
+ setTimeout(() => {
+ $(messageId).fadeOut(300);
+ }, duration);
+ }
+ }
+
+ function showPrivacyError(show) {
+ if (show) {
+ $('#privacy-error').show();
+ } else {
+ $('#privacy-error').hide();
+ }
+ }
+
+ function showFieldError(fieldId, message) {
+ $(fieldId).removeClass('error-input');
+ $(fieldId + '-error').remove();
+
+ if (message) {
+ $(fieldId).addClass('error-input');
+ $(fieldId).after('' + message + '
');
+ }
+ }
+
+ $('#fullname').on('blur', function() {
+ const value = $(this).val().trim();
+ if (value) {
+ if (!validateFullName(value)) {
+ showFieldError('#fullname', 'ФИО должно содержать только буквы и состоять минимум из 2 слов');
+ } else {
+ showFieldError('#fullname', '');
+ }
+ } else {
+ showFieldError('#fullname', '');
+ }
+ });
+
+ $('#email').on('blur', function() {
+ const value = $(this).val().trim();
+ if (value) {
+ if (!validateEmail(value)) {
+ showFieldError('#email', 'Введите корректный email адрес (например: example@mail.ru)');
+ } else {
+ showFieldError('#email', '');
+ }
+ } else {
+ showFieldError('#email', '');
+ }
+ });
+
+ $('#phone').on('blur', function() {
+ const value = $(this).val().trim();
+ if (value) {
+ if (!validatePhone(value)) {
+ showFieldError('#phone', 'Введите номер в формате +7XXXXXXXXXX (10 цифр после +7)');
+ } else {
+ showFieldError('#phone', '');
+ }
+ } else {
+ showFieldError('#phone', '');
+ }
+ });
+
+ $('#region').on('blur', function() {
+ const value = $(this).val().trim();
+ if (!value) {
+ showFieldError('#region', 'Укажите регион доставки');
+ } else {
+ showFieldError('#region', '');
+ }
+ });
+
+ $('#address').on('blur', function() {
+ const value = $(this).val().trim();
+ if (!value) {
+ showFieldError('#address', 'Укажите адрес доставки (улица, дом, квартира)');
+ } else {
+ showFieldError('#address', '');
+ }
+ });
+
+ $('.form__input').on('input', function() {
+ const fieldId = '#' + $(this).attr('id');
+ showFieldError(fieldId, '');
+ });
+
+ $('.products__qty-btn.plus').click(function() {
+ const $qtyValue = $(this).siblings('.products__qty-value');
+ let quantity = parseInt($qtyValue.text());
+ $qtyValue.text(quantity + 1);
+ updateTotal();
+ });
+
+ $('.products__qty-btn.minus').click(function() {
+ const $qtyValue = $(this).siblings('.products__qty-value');
+ let quantity = parseInt($qtyValue.text());
+ if (quantity > 1) {
+ $qtyValue.text(quantity - 1);
+ updateTotal();
+ }
+ });
+
+ $('.remove-from-cart').click(function() {
+ const $productItem = $(this).closest('.products__item');
+ $productItem.fadeOut(300, function() {
+ $(this).remove();
+ updateTotal();
+
+ if ($('.products__item').length === 0) {
+ $('.products__list').html('Корзина пуста
');
+ }
+ });
+ });
+
+ $('.promo__btn').click(function() {
+ const promoCode = $('.promo__input').val().toUpperCase();
+
+ if (promoCode === 'SALE10') {
+ cart.discount = Math.round(parseInt($('.products-total').text()) * 0.1);
+ $('.discount-total').text(cart.discount + ' ₽');
+ showMessage('#form-error', 3000);
+ $('#form-error').text('Промокод применен! Скидка 10%').removeClass('error').addClass('success');
+ } else if (promoCode === 'FREE') {
+ cart.delivery = 0;
+ $('.delivery-price').text('0 ₽');
+ showMessage('#form-error', 3000);
+ $('#form-error').text('Промокод применен! Бесплатная доставка').removeClass('error').addClass('success');
+ } else if (promoCode) {
+ showMessage('#form-error', 3000);
+ $('#form-error').text('Промокод недействителен').removeClass('success').addClass('error');
+ }
+
+ updateTotal();
+ });
+
+ $('input[name="delivery"]').change(function() {
+ if ($(this).val() === 'pickup') {
+ cart.delivery = 0;
+ $('.delivery-price').text('0 ₽');
+ } else {
+ cart.delivery = 2000;
+ $('.delivery-price').text('2000 ₽');
+ }
+ updateTotal();
+ });
+
+ function validateForm() {
+ let isValid = true;
+ let errorMessages = [];
+
+ $('.field-error').remove();
+ $('.form__input').removeClass('error-input');
+
+ const requiredFields = [
+ {
+ id: '#fullname',
+ value: $('#fullname').val().trim(),
+ validator: validateFullName,
+ required: true,
+ message: 'ФИО должно содержать только буквы и состоять минимум из 2 слов'
+ },
+ {
+ id: '#phone',
+ value: $('#phone').val().trim(),
+ validator: validatePhone,
+ required: true,
+ message: 'Введите корректный номер телефона в формате +7XXXXXXXXXX'
+ },
+ {
+ id: '#email',
+ value: $('#email').val().trim(),
+ validator: validateEmail,
+ required: true,
+ message: 'Введите корректный email адрес'
+ },
+ {
+ id: '#region',
+ value: $('#region').val().trim(),
+ validator: (val) => val.length > 0,
+ required: true,
+ message: 'Поле "Регион" обязательно для заполнения'
+ },
+ {
+ id: '#address',
+ value: $('#address').val().trim(),
+ validator: (val) => val.length > 0,
+ required: true,
+ message: 'Поле "Адрес" обязательно для заполнения'
+ }
+ ];
+
+ requiredFields.forEach(field => {
+ if (field.required && (!field.value || !field.validator(field.value))) {
+ isValid = false;
+ errorMessages.push(field.message);
+ showFieldError(field.id, field.message);
+ }
+ });
+
+ if (!$('#privacy-checkbox').is(':checked')) {
+ isValid = false;
+ showPrivacyError(true);
+ errorMessages.push('Необходимо согласие на обработку персональных данных');
+ } else {
+ showPrivacyError(false);
+ }
+
+ if (!isValid && errorMessages.length > 0) {
+ showMessage('#form-error', 5000);
+ $('#form-error').text('Исправьте следующие ошибки: ' + errorMessages.join('; ')).removeClass('success').addClass('error');
+
+ $('html, body').animate({
+ scrollTop: $('.error-input').first().offset().top - 100
+ }, 500);
+ }
+
+ return isValid;
+ }
+
+ $('#submit-order').click(function() {
+ if (!validateForm()) {
+ return;
+ }
+
+ const $btn = $(this);
+ $btn.prop('disabled', true).text('ОБРАБОТКА...');
+
+ // Собрать данные заказа
+ const orderData = {
+ full_name: $('#fullname').val().trim(),
+ phone: $('#phone').val().trim(),
+ email: $('#email').val().trim(),
+ region: $('#region').val().trim(),
+ address: $('#address').val().trim(),
+ delivery_method: $('input[name="delivery"]:checked').val(),
+ comment: $('#comment').val().trim()
+ };
+
+ // Отправить на сервер
+ $.ajax({
+ url: 'api/process_order.php',
+ method: 'POST',
+ data: orderData,
+ dataType: 'json',
+ success: function(response) {
+ if (response.success) {
+ showMessage('#order-success', 5000);
+ $('#order-success').text('Заказ успешно оформлен! Номер заказа: ' + (response.order_id || ''));
+ // Очистить корзину и форму
+ setTimeout(() => {
+ window.location.href = 'catalog.php';
+ }, 2000);
+ } else {
+ showMessage('#form-error', 5000);
+ $('#form-error').text(response.message || 'Ошибка оформления заказа').removeClass('success').addClass('error');
+ $btn.prop('disabled', false).text('ОФОРМИТЬ ЗАКАЗ');
+ }
+ },
+ error: function(xhr, status, error) {
+ showMessage('#form-error', 5000);
+ $('#form-error').text('Ошибка сервера. Попробуйте позже.').removeClass('success').addClass('error');
+ $btn.prop('disabled', false).text('ОФОРМИТЬ ЗАКАЗ');
+ }
+ });
+ });
+
+ $('#phone').on('input', function() {
+ let phone = $(this).val().replace(/\D/g, '');
+ if (phone.length > 0) {
+ if (phone[0] !== '7' && phone[0] !== '8') {
+ phone = '7' + phone;
+ }
+ phone = '+7' + phone.substring(1, 11);
+ $(this).val(phone);
+ }
+ });
+
+ updateTotal();
+});
diff --git a/профиль_jquery.js b/public/assets/js/profile.js
similarity index 78%
rename from профиль_jquery.js
rename to public/assets/js/profile.js
index 4aa7ff1..509db1a 100644
--- a/профиль_jquery.js
+++ b/public/assets/js/profile.js
@@ -1,384 +1,348 @@
-$(document).ready(function() {
- // Функции для отображения сообщений
- function showMessage(type, text) {
- const messageId = type + 'Message';
- const $message = $('#' + messageId);
- $message.text(text).fadeIn(300);
- setTimeout(() => {
- $message.fadeOut(300);
- }, 5000);
- }
-
- // Проверка, является ли email администратора
- function isAdminEmail(email) {
- const adminEmails = ['admin@aeterna.ru', 'administrator@aeterna.ru', 'aeterna@mail.ru'];
- return adminEmails.includes(email.toLowerCase());
- }
-
- // Валидация ФИО (без цифр)
- function validateFIO(fio) {
- const words = fio.trim().split(/\s+/);
- // Проверяем, что минимум 2 слова и нет цифр
- const hasNoDigits = !/\d/.test(fio);
- return words.length >= 2 && words.every(word => word.length >= 2) && hasNoDigits;
- }
-
- // Валидация города
- function validateCity(city) {
- return city.trim().length >= 2 && /^[а-яА-ЯёЁ\s-]+$/.test(city);
- }
-
- // Валидация email
- function validateEmail(email) {
- const localPart = email.split('@')[0];
-
- // Проверка на кириллические символы перед @
- if (/[а-яА-ЯёЁ]/.test(localPart)) {
- showError('email', 'В тексте перед знаком "@" не должно быть кириллических символов');
- return false;
- }
-
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- if (!emailRegex.test(email)) {
- showError('email', 'Введите корректный email адрес');
- return false;
- }
-
- hideError('email');
- return true;
- }
-
- // Валидация телефона
- function validatePhone(phone) {
- const phoneRegex = /^(\+7|8)[\s-]?\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}$/;
- return phoneRegex.test(phone.replace(/\s/g, ''));
- }
-
- // Валидация пароля
- function validatePassword(password) {
- return password.length >= 6;
- }
-
- // Показать/скрыть ошибку
- function showError(fieldId, message) {
- $('#' + fieldId).addClass('error');
- $('#' + fieldId + '-error').text(message).show();
- }
-
- function hideError(fieldId) {
- $('#' + fieldId).removeClass('error');
- $('#' + fieldId + '-error').hide();
- }
-
- // Валидация формы
- function validateForm() {
- let isValid = true;
-
- // Валидация ФИО
- const fio = $('#fio').val();
- if (!validateFIO(fio)) {
- if (/\d/.test(fio)) {
- showError('fio', 'ФИО не должно содержать цифры');
- } else {
- showError('fio', 'ФИО должно содержать минимум 2 слова (каждое от 2 символов)');
- }
- isValid = false;
- } else {
- hideError('fio');
- }
-
- // Валидация города
- const city = $('#city').val();
- if (!validateCity(city)) {
- showError('city', 'Укажите корректное название города (только русские буквы)');
- isValid = false;
- } else {
- hideError('city');
- }
-
- // Валидация email
- const email = $('#email').val();
- if (!validateEmail(email)) {
- showError('email', 'Введите корректный email адрес');
- isValid = false;
- } else {
- hideError('email');
- }
-
- // Валидация телефона
- const phone = $('#phone').val();
- if (!validatePhone(phone)) {
- showError('phone', 'Введите номер в формате: +7(912)999-12-23');
- isValid = false;
- } else {
- hideError('phone');
- }
-
- // Валидация пароля
- const password = $('#password').val();
- if (!validatePassword(password)) {
- showError('password', 'Пароль должен содержать минимум 6 символов');
- isValid = false;
- } else {
- hideError('password');
- }
-
- // Проверка совпадения паролей
- const confirmPassword = $('#confirm-password').val();
- if (password !== confirmPassword) {
- showError('confirm-password', 'Пароли не совпадают');
- isValid = false;
- } else {
- hideError('confirm-password');
- }
-
- // Проверка согласия с условиями
- if (!$('#privacy').is(':checked')) {
- $('#privacy-error').show();
- isValid = false;
- } else {
- $('#privacy-error').hide();
- }
-
- return isValid;
- }
-
- // Реальная валидация при вводе
- $('input').on('blur', function() {
- const fieldId = $(this).attr('id');
- const value = $(this).val();
-
- switch(fieldId) {
- case 'fio':
- if (!validateFIO(value)) {
- if (/\d/.test(value)) {
- showError(fieldId, 'ФИО не должно содержать цифры');
- } else {
- showError(fieldId, 'ФИО должно содержать минимум 2 слова');
- }
- } else {
- hideError(fieldId);
- }
- break;
- case 'city':
- if (!validateCity(value)) {
- showError(fieldId, 'Укажите корректное название города');
- } else {
- hideError(fieldId);
- }
- break;
- case 'email':
- if (!validateEmail(value)) {
- showError(fieldId, 'Введите корректный email адрес');
- } else {
- hideError(fieldId);
- }
- break;
- case 'phone':
- if (!validatePhone(value)) {
- showError(fieldId, 'Введите номер в формате: +7(912)999-12-23');
- } else {
- hideError(fieldId);
- }
- break;
- case 'password':
- if (!validatePassword(value)) {
- showError(fieldId, 'Пароль должен содержать минимум 6 символов');
- } else {
- hideError(fieldId);
- }
- break;
- case 'confirm-password':
- const password = $('#password').val();
- if (value !== password) {
- showError(fieldId, 'Пароли не совпадают');
- } else {
- hideError(fieldId);
- }
- break;
- }
- });
-
- // Обработка отправки формы
- $('#registrationForm').on('submit', function(e) {
- e.preventDefault();
-
- if (validateForm()) {
- const email = $('#email').val();
- const isAdmin = isAdminEmail(email);
-
- // Сохраняем данные пользователя в localStorage
- const userData = {
- email: email,
- fio: $('#fio').val(),
- phone: $('#phone').val(),
- isAdmin: isAdmin,
- registered: new Date().toISOString()
- };
-
- localStorage.setItem('userData', JSON.stringify(userData));
- localStorage.setItem('isLoggedIn', 'true');
- localStorage.setItem('isAdmin', isAdmin.toString());
-
- // Эмуляция успешной регистрации
- showMessage('success', 'Регистрация прошла успешно! ' +
- (isAdmin ? 'Вы зарегистрированы как администратор.' : 'Добро пожаловать в AETERNA!'));
-
- setTimeout(() => {
- // Перенаправление на главную страницу
- window.location.href = 'cite_mebel.php';
- }, 2000);
- } else {
- showMessage('error', 'Пожалуйста, исправьте ошибки в форме');
- }
- });
-
- // Плавная прокрутка к якорям
- $('a[href^="#"]').on('click', function(event) {
- var target = $(this.getAttribute('href'));
- if (target.length) {
- event.preventDefault();
- $('html, body').stop().animate({
- scrollTop: target.offset().top
- }, 1000);
- }
- });
-
- // Переключение между регистрацией и входом
- $('.login-btn').on('click', function() {
- showMessage('warning', 'Переход к форме входа...');
- setTimeout(() => {
- window.location.href = 'вход.php';
- }, 1000);
- });
-
- // Обработка ссылки "Сменить пароль"
- $('.password-link').on('click', function(e) {
- e.preventDefault();
- showMessage('warning', 'Функция смены пароля будет доступна после регистрации');
- });
-
- // Маска для телефона
- $('#phone').on('input', function() {
- let value = $(this).val().replace(/\D/g, '');
- if (value.startsWith('7') || value.startsWith('8')) {
- value = value.substring(1);
- }
- if (value.length > 0) {
- value = '+7(' + value;
- if (value.length > 6) value = value.substring(0, 6) + ')' + value.substring(6);
- if (value.length > 10) value = value.substring(0, 10) + '-' + value.substring(10);
- if (value.length > 13) value = value.substring(0, 13) + '-' + value.substring(13);
- if (value.length > 16) value = value.substring(0, 16);
- }
- $(this).val(value);
- });
-
- // Запрет ввода цифр в поле ФИО
- $('#fio').on('input', function() {
- let value = $(this).val();
- // Удаляем цифры из значения
- value = value.replace(/\d/g, '');
- $(this).val(value);
- });
-});
-
-// Для входа (файл вход.html)
-$(document).ready(function() {
- // Проверяем, если пользователь уже вошел
- if (localStorage.getItem('isLoggedIn') === 'true') {
- const userData = JSON.parse(localStorage.getItem('userData') || '{}');
- if (userData.email) {
- $('#login-email').val(userData.email);
- }
- }
-
- // Функция проверки пароля администратора
- function checkAdminPassword(email, password) {
- // Административные аккаунты
- const adminAccounts = {
- 'admin@aeterna.ru': 'admin123',
- 'administrator@aeterna.ru': 'admin123',
- 'aeterna@mail.ru': 'admin123'
- };
-
- return adminAccounts[email.toLowerCase()] === password;
- }
-
- // Валидация формы входа
- $('#loginForm').on('submit', function(e) {
- e.preventDefault();
-
- let isValid = true;
- const email = $('#login-email').val();
- const password = $('#login-password').val();
-
- // Валидация email
- if (!isValidEmail(email)) {
- $('#email-error').show();
- isValid = false;
- } else {
- $('#email-error').hide();
- }
-
- // Валидация пароля
- if (password.length < 6) {
- $('#password-error').show();
- isValid = false;
- } else {
- $('#password-error').hide();
- }
-
- if (isValid) {
- // Здесь обычно отправка данных на сервер
- showMessage('success', 'Вы успешно вошли в систему!');
-
- // Перенаправление на главную страницу через 1.5 секунды
- setTimeout(function() {
- window.location.href = 'cite_mebel.php';
- }, 1500);
- }
- });
-
- // Функция показа сообщений
- function showMessage(type, text) {
- const messageId = type + 'Message';
- const $message = $('#' + messageId);
-
- $message.text(text).show();
-
- setTimeout(function() {
- $message.fadeOut();
- }, 3000);
- }
-
- // Скрываем сообщения об ошибках при фокусе на полях
- $('input').on('focus', function() {
- $(this).next('.error-message').hide();
- });
-
- // Обработка чекбокса "Запомнить меня"
- $('#remember').on('change', function() {
- if ($(this).is(':checked')) {
- const email = $('#login-email').val();
- if (email) {
- localStorage.setItem('rememberedEmail', email);
- }
- } else {
- localStorage.removeItem('rememberedEmail');
- }
- });
-
- // Автозаполнение email, если пользователь выбрал "Запомнить меня"
- const rememberedEmail = localStorage.getItem('rememberedEmail');
- if (rememberedEmail) {
- $('#login-email').val(rememberedEmail);
- $('#remember').prop('checked', true);
- }
-
- // Обработка ссылки "Забыли пароль?"
- $('.forgot-password').on('click', function(e) {
- e.preventDefault();
- showMessage('info', 'Для восстановления пароля обратитесь к администратору');
- });
-});
\ No newline at end of file
+$(document).ready(function() {
+
+ function showMessage(type, text) {
+ const messageId = type + 'Message';
+ const $message = $('#' + messageId);
+ $message.text(text).fadeIn(300);
+ setTimeout(() => {
+ $message.fadeOut(300);
+ }, 5000);
+ }
+
+ function isAdminEmail(email) {
+ const adminEmails = ['admin@aeterna.ru', 'administrator@aeterna.ru', 'aeterna@mail.ru'];
+ return adminEmails.includes(email.toLowerCase());
+ }
+
+ function validateFIO(fio) {
+ const words = fio.trim().split(/\s+/);
+
+ const hasNoDigits = !/\d/.test(fio);
+ return words.length >= 2 && words.every(word => word.length >= 2) && hasNoDigits;
+ }
+
+ function validateCity(city) {
+ return city.trim().length >= 2 && /^[а-яА-ЯёЁ\s-]+$/.test(city);
+ }
+
+ function validateEmail(email) {
+ const localPart = email.split('@')[0];
+
+ if (/[а-яА-ЯёЁ]/.test(localPart)) {
+ showError('email', 'В тексте перед знаком "@" не должно быть кириллических символов');
+ return false;
+ }
+
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ if (!emailRegex.test(email)) {
+ showError('email', 'Введите корректный email адрес');
+ return false;
+ }
+
+ hideError('email');
+ return true;
+ }
+
+ function validatePhone(phone) {
+ const phoneRegex = /^(\+7|8)[\s-]?\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}$/;
+ return phoneRegex.test(phone.replace(/\s/g, ''));
+ }
+
+ function validatePassword(password) {
+ return password.length >= 6;
+ }
+
+ function showError(fieldId, message) {
+ $('#' + fieldId).addClass('error');
+ $('#' + fieldId + '-error').text(message).show();
+ }
+
+ function hideError(fieldId) {
+ $('#' + fieldId).removeClass('error');
+ $('#' + fieldId + '-error').hide();
+ }
+
+ function validateForm() {
+ let isValid = true;
+
+ const fio = $('#fio').val();
+ if (!validateFIO(fio)) {
+ if (/\d/.test(fio)) {
+ showError('fio', 'ФИО не должно содержать цифры');
+ } else {
+ showError('fio', 'ФИО должно содержать минимум 2 слова (каждое от 2 символов)');
+ }
+ isValid = false;
+ } else {
+ hideError('fio');
+ }
+
+ const city = $('#city').val();
+ if (!validateCity(city)) {
+ showError('city', 'Укажите корректное название города (только русские буквы)');
+ isValid = false;
+ } else {
+ hideError('city');
+ }
+
+ const email = $('#email').val();
+ if (!validateEmail(email)) {
+ showError('email', 'Введите корректный email адрес');
+ isValid = false;
+ } else {
+ hideError('email');
+ }
+
+ const phone = $('#phone').val();
+ if (!validatePhone(phone)) {
+ showError('phone', 'Введите номер в формате: +7(912)999-12-23');
+ isValid = false;
+ } else {
+ hideError('phone');
+ }
+
+ const password = $('#password').val();
+ if (!validatePassword(password)) {
+ showError('password', 'Пароль должен содержать минимум 6 символов');
+ isValid = false;
+ } else {
+ hideError('password');
+ }
+
+ const confirmPassword = $('#confirm-password').val();
+ if (password !== confirmPassword) {
+ showError('confirm-password', 'Пароли не совпадают');
+ isValid = false;
+ } else {
+ hideError('confirm-password');
+ }
+
+ if (!$('#privacy').is(':checked')) {
+ $('#privacy-error').show();
+ isValid = false;
+ } else {
+ $('#privacy-error').hide();
+ }
+
+ return isValid;
+ }
+
+ $('input').on('blur', function() {
+ const fieldId = $(this).attr('id');
+ const value = $(this).val();
+
+ switch(fieldId) {
+ case 'fio':
+ if (!validateFIO(value)) {
+ if (/\d/.test(value)) {
+ showError(fieldId, 'ФИО не должно содержать цифры');
+ } else {
+ showError(fieldId, 'ФИО должно содержать минимум 2 слова');
+ }
+ } else {
+ hideError(fieldId);
+ }
+ break;
+ case 'city':
+ if (!validateCity(value)) {
+ showError(fieldId, 'Укажите корректное название города');
+ } else {
+ hideError(fieldId);
+ }
+ break;
+ case 'email':
+ if (!validateEmail(value)) {
+ showError(fieldId, 'Введите корректный email адрес');
+ } else {
+ hideError(fieldId);
+ }
+ break;
+ case 'phone':
+ if (!validatePhone(value)) {
+ showError(fieldId, 'Введите номер в формате: +7(912)999-12-23');
+ } else {
+ hideError(fieldId);
+ }
+ break;
+ case 'password':
+ if (!validatePassword(value)) {
+ showError(fieldId, 'Пароль должен содержать минимум 6 символов');
+ } else {
+ hideError(fieldId);
+ }
+ break;
+ case 'confirm-password':
+ const password = $('#password').val();
+ if (value !== password) {
+ showError(fieldId, 'Пароли не совпадают');
+ } else {
+ hideError(fieldId);
+ }
+ break;
+ }
+ });
+
+ $('#registrationForm').on('submit', function(e) {
+ e.preventDefault();
+
+ if (validateForm()) {
+ const email = $('#email').val();
+ const isAdmin = isAdminEmail(email);
+
+ const userData = {
+ email: email,
+ fio: $('#fio').val(),
+ phone: $('#phone').val(),
+ isAdmin: isAdmin,
+ registered: new Date().toISOString()
+ };
+
+ localStorage.setItem('userData', JSON.stringify(userData));
+ localStorage.setItem('isLoggedIn', 'true');
+ localStorage.setItem('isAdmin', isAdmin.toString());
+
+ showMessage('success', 'Регистрация прошла успешно! ' +
+ (isAdmin ? 'Вы зарегистрированы как администратор.' : 'Добро пожаловать в AETERNA!'));
+
+ setTimeout(() => {
+
+ window.location.href = 'cite_mebel.php';
+ }, 2000);
+ } else {
+ showMessage('error', 'Пожалуйста, исправьте ошибки в форме');
+ }
+ });
+
+ $('a[href^="#"]').on('click', function(event) {
+ var target = $(this.getAttribute('href'));
+ if (target.length) {
+ event.preventDefault();
+ $('html, body').stop().animate({
+ scrollTop: target.offset().top
+ }, 1000);
+ }
+ });
+
+ $('.login-btn').on('click', function() {
+ showMessage('warning', 'Переход к форме входа...');
+ setTimeout(() => {
+ window.location.href = 'login.php';
+ }, 1000);
+ });
+
+ $('.password-link').on('click', function(e) {
+ e.preventDefault();
+ showMessage('warning', 'Функция смены пароля будет доступна после регистрации');
+ });
+
+ $('#phone').on('input', function() {
+ let value = $(this).val().replace(/\D/g, '');
+ if (value.startsWith('7') || value.startsWith('8')) {
+ value = value.substring(1);
+ }
+ if (value.length > 0) {
+ value = '+7(' + value;
+ if (value.length > 6) value = value.substring(0, 6) + ')' + value.substring(6);
+ if (value.length > 10) value = value.substring(0, 10) + '-' + value.substring(10);
+ if (value.length > 13) value = value.substring(0, 13) + '-' + value.substring(13);
+ if (value.length > 16) value = value.substring(0, 16);
+ }
+ $(this).val(value);
+ });
+
+ $('#fio').on('input', function() {
+ let value = $(this).val();
+
+ value = value.replace(/\d/g, '');
+ $(this).val(value);
+ });
+});
+
+$(document).ready(function() {
+
+ if (localStorage.getItem('isLoggedIn') === 'true') {
+ const userData = JSON.parse(localStorage.getItem('userData') || '{}');
+ if (userData.email) {
+ $('#login-email').val(userData.email);
+ }
+ }
+
+ function checkAdminPassword(email, password) {
+
+ const adminAccounts = {
+ 'admin@aeterna.ru': 'admin123',
+ 'administrator@aeterna.ru': 'admin123',
+ 'aeterna@mail.ru': 'admin123'
+ };
+
+ return adminAccounts[email.toLowerCase()] === password;
+ }
+
+ $('#loginForm').on('submit', function(e) {
+ e.preventDefault();
+
+ let isValid = true;
+ const email = $('#login-email').val();
+ const password = $('#login-password').val();
+
+ if (!isValidEmail(email)) {
+ $('#email-error').show();
+ isValid = false;
+ } else {
+ $('#email-error').hide();
+ }
+
+ if (password.length < 6) {
+ $('#password-error').show();
+ isValid = false;
+ } else {
+ $('#password-error').hide();
+ }
+
+ if (isValid) {
+
+ showMessage('success', 'Вы успешно вошли в систему!');
+
+ setTimeout(function() {
+ window.location.href = 'cite_mebel.php';
+ }, 1500);
+ }
+ });
+
+ function showMessage(type, text) {
+ const messageId = type + 'Message';
+ const $message = $('#' + messageId);
+
+ $message.text(text).show();
+
+ setTimeout(function() {
+ $message.fadeOut();
+ }, 3000);
+ }
+
+ $('input').on('focus', function() {
+ $(this).next('.error-message').hide();
+ });
+
+ $('#remember').on('change', function() {
+ if ($(this).is(':checked')) {
+ const email = $('#login-email').val();
+ if (email) {
+ localStorage.setItem('rememberedEmail', email);
+ }
+ } else {
+ localStorage.removeItem('rememberedEmail');
+ }
+ });
+
+ const rememberedEmail = localStorage.getItem('rememberedEmail');
+ if (rememberedEmail) {
+ $('#login-email').val(rememberedEmail);
+ $('#remember').prop('checked', true);
+ }
+
+ $('.forgot-password').on('click', function(e) {
+ e.preventDefault();
+ showMessage('info', 'Для восстановления пароля обратитесь к администратору');
+ });
+});
diff --git a/public/assets/less/checkout.less b/public/assets/less/checkout.less
new file mode 100644
index 0000000..a6fe073
--- /dev/null
+++ b/public/assets/less/checkout.less
@@ -0,0 +1,137 @@
+.error-message {
+ color: #ff0000;
+ font-size: 12px;
+ margin-top: 5px;
+ display: none;
+}
+
+.form__input.error {
+ border-color: #ff0000;
+}
+
+.form__group {
+ position: relative;
+ margin-bottom: 15px;
+}
+
+.page-messages {
+ position: fixed;
+ bottom: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 1000;
+ width: 90%;
+ max-width: 500px;
+}
+
+.message {
+ padding: 15px;
+ margin: 10px 0;
+ border-radius: 5px;
+ text-align: center;
+ font-weight: bold;
+ display: none;
+}
+
+.message.error {
+ background-color: #ffebee;
+ color: #c62828;
+ border: 1px solid #ffcdd2;
+}
+
+.message.success {
+ background-color: #e8f5e9;
+ color: #453227;
+ border: 1px solid #c8e6c9;
+}
+
+.message.warning {
+ background-color: #fff3e0;
+ color: #ef6c00;
+ border: 1px solid #ffe0b2;
+}
+
+.privacy-error {
+ color: #ff0000;
+ font-size: 12px;
+ margin-top: 5px;
+ display: none;
+ text-align: center;
+}
+
+.input-group {
+ position: relative;
+ margin-bottom: 20px;
+}
+
+.profile-form input.error {
+ border-color: #ff0000;
+ background-color: #fff5f5;
+}
+
+.privacy-checkbox {
+ margin: 20px 0;
+ text-align: center;
+}
+
+.privacy-checkbox label {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+.privacy-checkbox input[type="checkbox"] {
+ margin: 0;
+}
+
+.profile-page-main {
+ padding: 40px 0;
+ min-height: calc(100vh - 200px);
+}
+
+.profile-container {
+ margin: 0 auto;
+ position: relative;
+ z-index: 1;
+}
+
+.input-hint {
+ font-size: 12px;
+ color: #666;
+ margin-top: 5px;
+}
+
+.form-options {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin: 20px 0;
+}
+
+.remember-me {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ color: #453227;
+}
+
+.remember-me input[type="checkbox"] {
+ width: 16px;
+ height: 16px;
+ cursor: pointer;
+}
+
+.forgot-password {
+ font-size: 14px;
+ color: #453227;
+ text-decoration: underline;
+}
+
+.forgot-password:hover {
+ color: #617365;
+ text-decoration: none;
+}
\ No newline at end of file
diff --git a/public/assets/less/mixins.less b/public/assets/less/mixins.less
new file mode 100644
index 0000000..0e1f70b
--- /dev/null
+++ b/public/assets/less/mixins.less
@@ -0,0 +1,82 @@
+@color-primary: #617365;
+@color-secondary: #D1D1D1;
+@color-accent: #453227;
+@color-text-dark: #333;
+@color-text-light: #fff;
+@color-button: @color-accent;
+@color-beige: #A2A09A;
+
+@font-logo: 'Anek Kannada', sans-serif;
+@font-main: 'Anonymous Pro', monospace;
+@font-heading: 'Playfair Display', serif;
+
+@shadow-light: 0 5px 15px rgba(0, 0, 0, 0.2);
+@shadow-dark: 2px 2px 4px rgba(0, 0, 0, 0.3);
+
+.flex-center(@gap: 0) {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: @gap;
+}
+
+.flex-between() {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.flex-column() {
+ display: flex;
+ flex-direction: column;
+}
+
+.icon-base(@size: 18px, @hover-scale: 1.1) {
+ cursor: pointer;
+ transition: all 0.3s ease;
+ font-size: @size;
+ &:hover {
+ transform: scale(@hover-scale);
+ }
+}
+
+.image-overlay() {
+ position: absolute;
+ inset: 0;
+ .flex-center(15px);
+ flex-direction: column;
+ text-align: center;
+ background-color: rgba(0, 0, 0, 0.4);
+ padding: 20px;
+ color: @color-text-light;
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
+ transition: all 0.3s ease;
+}
+
+.menu-base() {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ width: 250px;
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
+ padding: 15px;
+ z-index: 1000;
+ margin-top: 5px;
+ display: none;
+}
+
+.input-base() {
+ width: 100%;
+ padding: 12px 15px;
+ border: 1px solid #ccc;
+ background-color: #fff;
+ font-family: @font-main;
+ font-size: 14px;
+ outline: none;
+ transition: border-color 0.3s ease;
+ &:focus {
+ border-color: @color-primary;
+ }
+}
diff --git a/public/assets/less/style.less b/public/assets/less/style.less
new file mode 100644
index 0000000..9e57559
--- /dev/null
+++ b/public/assets/less/style.less
@@ -0,0 +1,3387 @@
+@import "mixins.less";
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+html, body {
+ height: 100%;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ font-family: @font-main;
+ background-color: @color-secondary;
+ color: @color-text-dark;
+ line-height: 1.6;
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+}
+
+.container {
+ max-width: 1210px;
+ margin: 0 auto;
+ padding: 0 20px;
+}
+
+ul {
+ list-style: none;
+}
+
+a {
+ text-decoration: none;
+ color: inherit;
+ transition: all 0.3s ease;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-family: @font-heading;
+ margin: 0;
+}
+
+p, li, span {
+ font-family: @font-main;
+}
+
+// =======================
+// === КОМПОНЕНТЫ ===
+// =======================
+
+.logo, .footer-logo {
+ font: bold 32px/1 @font-logo;
+ letter-spacing: 2px;
+ text-shadow: @shadow-dark;
+ flex-shrink: 0;
+}
+
+.btn {
+ padding: 12px 30px;
+ border: none;
+ cursor: pointer;
+ font-size: 14px;
+ text-transform: uppercase;
+ transition: all 0.3s ease;
+ font-family: @font-main;
+
+ &.primary-btn {
+ background-color: @color-button;
+ color: @color-text-light;
+
+ &:hover {
+ background-color: lighten(@color-button, 10%);
+ transform: translateY(-2px);
+ box-shadow: @shadow-light;
+ }
+ }
+}
+
+.number-circle {
+ .flex-center();
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ background-color: @color-button;
+ color: @color-text-light;
+ font-size: 16px;
+ font-weight: bold;
+ flex-shrink: 0;
+}
+
+.breadcrumbs {
+ font-size: 14px;
+ margin-bottom: 20px;
+ color: #666;
+
+ a {
+ color: #666;
+ opacity: 0.7;
+ &:hover { opacity: 1; }
+ }
+
+ .current-page {
+ font-weight: bold;
+ color: @color-text-dark;
+ }
+}
+
+// =======================
+// === ШАПКА САЙТА ===
+// =======================
+.header {
+ background-color: @color-secondary;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+ z-index: 1000;
+
+ &__top, &__bottom {
+ padding: 15px 0;
+ .container {
+ .flex-between();
+ gap: 20px;
+ }
+ }
+
+ &__bottom {
+ padding: 10px 0;
+ border-top: 1px solid rgba(0, 0, 0, 0.05);
+
+ .catalog-link.active-catalog {
+ background-color: rgba(0, 0, 0, 0.08);
+ pointer-events: none;
+ }
+ }
+
+ .search-catalog {
+ .flex-center();
+ border: 2px solid @color-text-dark;
+ background-color: #fff;
+ max-width: 600px;
+ width: 100%;
+ margin: 0 auto;
+ overflow: hidden;
+
+ .catalog-dropdown {
+ position: relative;
+ background-color: #f8f8f8;
+ padding: 10px 15px 10px 25px;
+ font-size: 18px;
+ cursor: pointer;
+ border-right: 1px solid @color-text-dark;
+ .flex-center(10px);
+ width: 200px;
+ flex-shrink: 0;
+
+ &__menu {
+ .menu-base();
+ li {
+ padding: 8px 0;
+ cursor: pointer;
+ transition: color 0.3s;
+ border-bottom: 1px solid #f0f0f0;
+ &:last-child { border-bottom: none; }
+ &:hover { color: @color-accent; }
+ }
+ }
+ &:hover &__menu { display: block; }
+ }
+
+ .search-box {
+ .flex-center();
+ padding: 0 15px;
+ flex-grow: 1;
+ position: relative;
+ font-size: 15px;
+
+ input {
+ border: none;
+ padding: 10px 30px 10px 0;
+ outline: none;
+ font-size: 16px;
+ width: 100%;
+ text-align: left;
+ }
+
+ .search-icon {
+ font-size: 20px;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ }}
+
+ &__icons--top {
+ .flex-center(15px);
+ flex-shrink: 0;
+ .icon { .icon-base(); font-size: 20px;}
+ }
+
+ .nav-list {
+ .flex-center(30px);
+ font-size: 18px;
+ a {
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
+ &:hover { text-shadow: @shadow-dark; }
+ &.active {
+ border-bottom: 2px solid @color-button;
+ padding-bottom: 5px;
+ text-shadow: @shadow-dark;
+ }
+ &[href="#footer"] {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .catalog-link {
+ .flex-center(10px);
+ border-radius: 4px;
+ white-space: nowrap;
+ font-size: 18px;
+ padding: 10px 18px;
+ &:hover { background-color: rgba(0, 0, 0, 0.05); }
+ }
+
+ .header-phone {
+ font-weight: bold;
+ color: @color-button;
+ flex-shrink: 0;
+ }
+}
+
+// =======================
+// === ОСНОВНЫЕ СЕКЦИИ ===
+// =======================
+.hero {
+ padding: 15px 0;
+
+ &__content {
+ .flex-center(50px);
+ min-height: 60vh;
+ align-items: center;
+ }
+
+ &__image-block {
+ position: relative;
+ flex: 0 0 40%;
+ max-width: 600px;
+ height: 600px;
+ .flex-center();
+
+ .hero__circle {
+ position: absolute;
+ width: 450px;
+ height: 450px;
+ background-color: @color-primary;
+ border-radius: 50%;
+ z-index: 1;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+ .hero__img {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ z-index: 2;
+ }
+ }
+
+ &__text-block {
+ flex: 0 0 60%;
+ padding-left: 50px;
+
+ h1 {
+ font-size: 42px;
+ font-weight: normal;
+ margin-bottom: 25px;
+ line-height: 1.3;
+ }
+
+ .hero__usp-text {
+ position: relative;
+ padding-left: 50px;
+ margin-bottom: 35px;
+ line-height: 1.7;
+ .flex-center();
+ justify-content: flex-start;
+ min-height: 40px;
+ font-size: 16px;
+
+ &::before {
+ content: "✓";
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 32px;
+ height: 32px;
+ border: 2px solid @color-button;
+ background-color: transparent;
+ color: @color-button;
+ border-radius: 50%;
+ .flex-center();
+ font-size: 16px;
+ font-weight: bold;
+ }
+ }
+
+ .btn.primary-btn {
+ margin: 25px 0 0 50px;
+ padding: 14px 35px;
+ font-size: 15px;
+ }
+ }
+}
+
+.advantages {
+ padding: 30px 0 40px;
+
+ &__header {
+ display: flex;
+ align-items: center;
+ gap: 50px;
+ margin-bottom: 40px;
+ h2 {
+ font-size: 32px;
+ font-weight: normal;
+ flex: 0 0 30%;
+ }
+ }
+
+ &__items {
+ flex: 0 0 70%;
+ display: flex;
+ gap: 30px;
+ }
+
+ .advantage-item {
+ flex: 1;
+ text-align: left;
+ position: relative;
+ padding-top: 30px;
+
+ &__number {
+ .number-circle();
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+
+ h4 {
+ font-weight: 600;
+ margin-bottom: 10px;
+ }
+ }
+}
+
+.promo-images {
+ display: flex;
+ gap: 20px;
+ margin-top: 50px;
+
+ .promo-image-col {
+ position: relative;
+ overflow: hidden;
+ border-radius: 8px;
+ flex: 1;
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: translateY(-5px);
+ box-shadow: @shadow-light;
+ .image-overlay-text { background-color: rgba(0, 0, 0, 0.6); }
+ img { transform: scale(1.05); }
+ .image-overlay-text h4,
+ .image-overlay-text .overlay-link { transform: translateY(0); }
+ }
+
+ img {
+ width: 100%;
+ height: 350px;
+ object-fit: cover;
+ display: block;
+ transition: transform 0.5s ease;
+ }
+
+ .image-overlay-text {
+ .image-overlay();
+ h4 {
+ font-size: 24px;
+ text-transform: uppercase;
+ line-height: 1.2;
+ margin-bottom: 15px;
+ transform: translateY(20px);
+ transition: transform 0.3s ease;
+ }
+ }
+
+ .overlay-link {
+ display: inline-block;
+ text-transform: uppercase;
+ font-weight: bold;
+ border-radius: 3px;
+ margin-top: 15px;
+ padding: 10px 25px;
+ background-color: @color-button;
+ color: @color-text-light;
+ font-size: 12px;
+ transform: translateY(20px);
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: lighten(@color-button, 10%);
+ transform: translateY(-2px);
+ box-shadow: @shadow-light;
+ }
+ }
+ }
+}
+
+.about {
+ padding: 40px 0 80px;
+
+ &__content {
+ display: flex;
+ align-items: flex-start;
+ gap: 50px;
+ }
+
+ &__column {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ &--left { flex: 0 0 40%; margin-bottom: 30px; }
+ &--right {
+ flex: 0 0 60%;
+ .about__caption {
+ padding-right: 50px;
+ }
+ }
+ }
+
+ &__text-block {
+ margin-bottom: 30px;
+ h2 { margin-bottom: 15px; }
+ }
+
+ &__img {
+ width: 93%;
+ object-fit: cover;
+ display: block;
+ &--small { height: 300px; }
+ &--large { height: 450px; }
+ }
+
+ .text-justified {
+ text-align: justify;
+ color: #555;
+ }
+}
+
+.solutions {
+ padding: 0;
+ background-color: @color-secondary;
+
+ &-slider {
+ position: relative;
+ width: 100%;
+ max-width: 1200px;
+ margin: 40px auto;
+ border-radius: 8px;
+ overflow: hidden;
+
+ &__slides {
+ display: flex;
+ width: 200%;
+ height: 100%;
+ animation: slideLeftRight 10s infinite ease-in-out;
+ }
+
+ &__slide {
+ width: 50%;
+ flex-shrink: 0;
+ position: relative;
+ overflow: hidden;
+ transition: transform 0.5s ease, box-shadow 0.5s ease;
+
+ &:hover {
+ transform: scale(1.02);
+ box-shadow: 0 10px 25px rgba(0,0,0,0.3);
+ .solution-img {
+ transform: scale(1.05);
+ filter: brightness(0.8);
+ }
+ .solution-text-overlay {
+ opacity: 1;
+ transform: translateY(-5px);
+ }
+ .solution-image-link {
+ transform: translateX(-50%) translateY(-6px);
+ background-color: rgba(255,255,255,0.9);
+ color: @color-text-dark;
+ }
+ }
+ }
+
+ .solution-img {
+ width: 100%;
+ height: auto;
+ object-fit: cover;
+ display: block;
+ }
+
+ .solution-text-overlay {
+ position: absolute;
+ top: 15%;
+ left: 8%;
+ color: #493131;
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.6);
+ z-index: 2;
+ opacity: 0.9;
+ transition: opacity 0.5s ease, transform 0.5s ease;
+ h2 {
+ font-size: 35px;
+ text-transform: uppercase;
+ margin-bottom: 10px;
+ }
+ p {
+ font-size: 25px;
+ text-transform: uppercase;
+ }
+ }
+
+ .solution-image-link {
+ position: absolute;
+ bottom: 40px;
+ left: 50%;
+ transform: translateX(-50%);
+ padding: 12px 30px;
+ border: 2px solid @color-text-light;
+ color: #493131;
+ text-transform: uppercase;
+ font-size: 16px;
+ font-weight: bold;
+ background: transparent;
+ transition: 0.4s ease;
+ z-index: 2;
+ &:hover {
+ background: @color-text-light;
+ color: @color-text-dark;
+ transform: translateX(-50%) translateY(-2px);
+ }
+ }
+ }
+}
+
+@keyframes slideLeftRight {
+ 0%, 40% { transform: translateX(0); }
+ 50%, 90% { transform: translateX(-50%); }
+ 100% { transform: translateX(0); }
+}
+
+.stats {
+ padding: 0;
+ margin-top: 20px;
+
+ .container {
+ display: flex;
+ justify-content: flex-end;
+ }
+
+ &__items {
+ display: flex;
+ gap: 20px;
+ .stat-item {
+ text-align: left;
+ .stat-number {
+ font-size: 36px;
+ font-weight: bold;
+ color: @color-text-dark;
+ margin-bottom: 5px;
+ }
+ .stat-label { color: @color-text-dark; }
+ }
+ }
+}
+
+.faq {
+ padding: 50px 0;
+
+ h2 {
+ text-align: left;
+ font-size: 32px;
+ font-weight: normal;
+ margin-bottom: 40px;
+ }
+
+ &__items {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 40px 60px;
+ margin-bottom: 40px;
+ }
+
+ .faq-item {
+ flex: 0 0 calc(50% - 30px);
+ .flex-center(15px);
+ align-items: flex-start;
+ &__content h4 {
+ font-weight: 600;
+ margin-bottom: 10px;
+ }
+ }
+
+ .btn.primary-btn {
+ display: block;
+ width: 100%;
+ margin: 20px auto 80px;
+ }
+}
+
+// =======================
+// === СТИЛИ КАТАЛОГА ===
+// =======================
+.catalog-main {
+ padding: 30px 0 60px;
+ background-color: lighten(@color-secondary, 5%);
+}
+
+.catalog-wrapper {
+ display: flex;
+ gap: 20px;
+}
+
+.catalog-sidebar {
+ flex: 0 0 250px;
+ background-color: #fff;
+ padding: 20px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+ height: fit-content;
+}
+
+.filter-group {
+ margin-bottom: 30px;
+}
+
+.filter-title {
+ font-size: 16px;
+ font-weight: bold;
+ margin-bottom: 15px;
+ text-transform: uppercase;
+}
+
+.filter-list li {
+ padding: 5px 0;
+ font-size: 16px;
+ a {
+ color: #555;
+ transition: color 0.2s;
+ &:hover { color: @color-accent; }
+ &.active-category {
+ font-weight: bold;
+ color: @color-primary;
+ }
+ }
+}
+
+.price-range {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ width: 100%;
+
+ .range-slider {
+ width: 100%;
+
+ input[type="range"] {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 100%;
+ height: 5px;
+ background: @color-primary;
+ border-radius: 5px;
+ outline: none;
+ margin: 0;
+
+ &::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 20px;
+ height: 20px;
+ background: @color-accent;
+ border: 2px solid #fff;
+ border-radius: 50%;
+ cursor: pointer;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: scale(1.1);
+ background: lighten(@color-accent, 10%);
+ }
+ }
+
+ &::-moz-range-thumb {
+ width: 20px;
+ height: 20px;
+ background: @color-accent;
+ border: 2px solid #fff;
+ border-radius: 50%;
+ cursor: pointer;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: scale(1.1);
+ background: lighten(@color-accent, 10%);
+ }
+ }
+ }
+ }
+
+ .price-display {
+ font-size: 14px;
+ font-weight: bold;
+ text-align: center;
+ color: @color-text-dark;
+ padding: 10px;
+ background: #f8f8f8;
+ border-radius: 4px;
+ }
+}
+
+.filter-options {
+ list-style: none;
+ li {
+ display: flex;
+ align-items: center;
+ padding: 4px 0;
+ font-size: 14px;
+ }
+ label {
+ margin-left: 10px;
+ cursor: pointer;
+ color: #555;
+ }
+ input[type="checkbox"] {
+ width: 15px;
+ height: 15px;
+ cursor: pointer;
+ accent-color: @color-primary;
+ &:checked + label {
+ font-weight: bold;
+ color: @color-primary;
+ }
+ }
+}
+
+.filter-apply-btn {
+ width: 100%;
+ margin-top: 20px;
+}
+
+.catalog-products {
+ flex-grow: 1;
+}
+
+.products-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20px;
+}
+
+.product-card {
+ background-color: #fff;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ transition: transform 0.3s ease;
+ box-sizing: border-box;
+
+ &:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
+ .product-img { transform: scale(1.05); }
+ }
+}
+
+.product-image-container {
+ position: relative;
+ overflow: hidden;
+ margin-bottom: 0;
+ padding: 0;
+ height: 250px;
+ .flex-center();
+}
+
+.product-img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+ transition: transform 0.3s ease;
+ margin: 0;
+}
+
+.product-img1 {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ transition: transform 0.3s ease;
+ margin: 0;
+}
+
+.product-discount {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ background-color: @color-button;
+ color: @color-text-light;
+ padding: 3px 8px;
+ font-size: 12px;
+ font-weight: bold;
+ z-index: 10;
+}
+
+.product-wishlist-icon {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ color: #333;
+ font-size: 18px;
+ cursor: pointer;
+ transition: color 0.3s ease;
+ z-index: 10;
+ &:hover { color: @color-accent; }
+}
+
+.product-name {
+ font-size: 16px;
+ font-weight: bold;
+ margin-bottom: 5px;
+}
+
+.product-details {
+ font-size: 13px;
+ color: #777;
+ margin-bottom: 10px;
+ flex-grow: 1;
+}
+
+.product-price {
+ font-size: 18px;
+ font-weight: bold;
+ color: @color-button;
+}
+
+.product-card.small { flex: 0 0 300px; max-width: 300px; height: 200px; }
+.product-card.small1 { flex: 0 0 320px; max-width: 320px; height: 250px;width: 320px; }
+.product-card.large { flex: 0 0 580px; max-width: 580px; height: 380px; }
+.product-card.wide { flex: 0 0 240px; max-width: 240px; height: 250px; }
+.product-card.wide1 { flex: 0 0 350px; max-width: 350px; height: 250px; }
+.product-card.wide2 { flex: 0 0 560px; max-width: 560px; height: 260px; }
+.product-card.wide2_1 { flex: 0 0 560px; max-width: 560px; height: 260px; margin: -280px 0 0; }
+.product-card.wide3 {
+ flex: 0 0 320px; max-width: 320px; height: 540px;
+ .product-image-container { height: 580px; }
+}
+.product-card.wide4 {
+ flex: 0 0 545px; max-width: 545px; margin: -270px 0 0; height: 250px;
+ .product-image-container { padding: 0; justify-content: flex-start; }
+ .product-img { margin-left: 0; align-self: flex-start; object-position: left center; }
+}
+.product-card.tall { flex: 0 0 300px; max-width: 300px; margin: -180px 0 0; height: 430px; }
+.product-card.full-width { flex: 0 0 100%; margin: -20px 0 0; max-width: 900px; height: 300px;}
+
+.product-card.full-width {
+ flex: 0 0 100%;
+ max-width: 100%;
+ height: 300px;
+
+ .product-image-container {
+ height: 100%;
+ padding: 0;
+ margin: 0;
+
+ .product-img1 {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ margin: 0;
+ padding: 0;
+ }
+ }
+}
+
+.product-card.tall .product-image-container,
+.product-card.large .product-image-container { height: 430px; }
+
+// =======================
+// === СТРАНИЦА ТОВАРА ===
+// =======================
+.product__section {
+ display: grid;
+ grid-template-columns: 600px 1fr;
+ gap: 40px;
+ margin: 40px 0;
+ padding: 20px 0;
+}
+
+.product__gallery {
+ position: relative;
+}
+
+.product__main-image {
+ width: 100%;
+ height: 600px;
+ background: #f8f9fa;
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 20px;
+ border: 1px solid #e9ecef;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ padding: 20px;
+ transition: transform 0.3s ease;
+
+ &:hover {
+ transform: scale(1.02);
+ }
+ }
+}
+
+.product__thumbnails {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 12px;
+}
+
+.product__thumbnail {
+ height: 120px;
+ background: #f8f9fa;
+ border: 2px solid #e9ecef;
+ border-radius: 8px;
+ overflow: hidden;
+ cursor: pointer;
+ padding: 0;
+ transition: all 0.3s ease;
+
+ &:hover {
+ border-color: #453227;
+ box-shadow: 0 2px 8px rgba(69, 50, 39, 0.2);
+ }
+
+ &.active {
+ border-color: #453227;
+ box-shadow: 0 0 0 2px rgba(69, 50, 39, 0.1);
+ }
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+}
+
+.product__info {
+ padding: 0;
+
+ h1 {
+ font-size: 32px;
+ font-weight: 600;
+ color: #212529;
+ margin-bottom: 20px;
+ line-height: 1.3;
+ }
+}
+
+.product__rating {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 25px;
+ padding-bottom: 25px;
+ border-bottom: 1px solid #e9ecef;
+
+ .stars {
+ display: flex;
+ gap: 4px;
+
+ .star {
+ font-size: 18px;
+ color: #ffc107;
+
+ &.filled {
+ color: #ffc107;
+ }
+ }
+ }
+
+ .rating-value {
+ font-weight: 600;
+ font-size: 16px;
+ color: #212529;
+ }
+
+ .reviews-count {
+ color: #6c757d;
+ font-size: 14px;
+ }
+}
+
+.product__price {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ margin-bottom: 25px;
+ flex-wrap: wrap;
+
+ .current-price {
+ font-size: 36px;
+ font-weight: 700;
+ color: #453227;
+ }
+
+ .old-price {
+ font-size: 24px;
+ color: #6c757d;
+ text-decoration: line-through;
+ }
+
+ .discount-badge {
+ background: #dc3545;
+ color: white;
+ padding: 6px 12px;
+ border-radius: 6px;
+ font-size: 14px;
+ font-weight: 600;
+ }
+}
+
+.product__color-selector {
+ display: flex;
+ gap: 10px;
+ margin-bottom: 40px;
+}
+
+.product__color-option {
+ width: 45px;
+ height: 45px;
+ border-radius: 50%;
+ border: 2px solid transparent;
+ cursor: pointer;
+ transition: transform 0.3s ease;
+
+ &:hover{
+ transform: translateY(-2px);
+ }
+}
+
+.product__color-option.active {
+ border-color: @color-primary;
+}
+
+.product__description {
+ margin-bottom: 65px;
+ line-height: 1.5;
+}
+
+.product__details-link {
+ display: inline-block;
+ margin-bottom: 20px;
+ color: @color-primary;
+ font-weight: bold;
+}
+
+.product__purchase {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 35px;
+}
+
+.product__price {
+ font-size: 24px;
+ font-weight: bold;
+}
+
+.product__quantity {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.product__qty-btn {
+ width: 30px;
+ height: 30px;
+ background: @color-button;
+ color: @color-text-light;
+ border: none;
+ border-radius: 50%;
+ cursor: pointer;
+ font-weight: bold;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background: lighten(@color-button, 10%);
+ transform: scale(1.1);
+ }
+}
+
+.product__qty-value {
+ font-weight: bold;
+ min-width: 30px;
+ text-align: center;
+}
+
+.product__actions {
+ display: flex;
+ gap: 15px;
+}
+
+.product__btn {
+ flex: 1;
+ padding: 12px 20px;
+ border: none;
+ border-radius: 4px;
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: @shadow-light;
+ }
+}
+
+.product__btn.primary {
+ background: @color-button;
+ color: @color-text-light;
+
+ &:hover {
+ background: lighten(@color-button, 10%);
+ }
+}
+
+.product__btn.secondary {
+ background: transparent;
+ border: 1px solid @color-button;
+ color: @color-button;
+
+ &:hover {
+ background: @color-button;
+ color: @color-text-light;
+ }
+}
+
+.similar {
+ margin: 60px 0;
+}
+
+.similar__title {
+ margin-bottom: 30px;
+ font-size: 28px;
+ font-weight: bold;
+}
+
+.similar__grid {
+ display: flex;
+ gap: 25px;
+ flex-wrap: wrap;
+ justify-content: space-between;
+}
+
+.similar__card {
+ flex: 0 0 calc(33.333% - 17px);
+ min-width: 320px;
+ background: @color-secondary;
+ border-radius: 12px;
+ overflow: hidden;
+ transition: all 0.3s ease;
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
+
+ &:hover {
+ transform: translateY(-8px);
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+ }
+}
+
+.similar__card-image {
+ height: 300px;
+ overflow: hidden;
+ background: white;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ transition: transform 0.3s ease;
+
+ &:hover {
+ transform: scale(1.05);
+ }
+ }
+}
+
+.similar__card-content {
+ padding: 25px;
+}
+
+.similar__card-title {
+ font-weight: bold;
+ margin-bottom: 10px;
+ font-size: 20px;
+ color: @color-text-dark;
+}
+
+.similar__card-description {
+ font-size: 15px;
+ margin-bottom: 15px;
+ color: #666;
+ line-height: 1.5;
+}
+
+.similar__card-price {
+ font-weight: bold;
+ font-size: 22px;
+ color: @color-button;
+}
+
+@media (max-width: 1024px) {
+ .similar {
+ &__card {
+ flex: 0 0 calc(50% - 13px);
+ min-width: 280px;
+ }
+ }
+}
+
+@media (max-width: 768px) {
+ .similar {
+ &__grid {
+ justify-content: center;
+ }
+
+ &__card {
+ flex: 0 0 100%;
+ max-width: 400px;
+ }
+ }
+}
+
+// =======================
+// === КОРЗИНА И ЗАКАЗ ===
+// =======================
+.main__content {
+ display: flex;
+ gap: 40px;
+ margin: 30px 0;
+
+ .products {
+ flex: 1;
+ }
+
+ .order {
+ flex: 0 0 65%;
+ padding: 40px;
+
+ &__header {
+ .flex-between();
+ margin-bottom: 20px;
+ }
+
+ &__title {
+ font-family: @font-logo;
+ font-size: 28px;
+ color: @color-text-dark;
+ margin: 0;
+ }
+
+ &__total {
+ font-weight: bold;
+ color: @color-text-dark;
+ }
+
+ &__section {
+ margin-bottom: 25px;
+ }
+
+ &__section-title {
+ font-family: @font-logo;
+ margin-bottom: 15px;
+ font-size: 18px;
+ color: @color-text-dark;
+ }
+ }
+}
+
+.products {
+ &__title {
+ font-family: @font-logo;
+ margin-bottom: 20px;
+ font-size: 24px;
+ color: @color-text-dark;
+ }
+
+ &__list {
+ .flex-column();
+ gap: 20px;
+ }
+
+ &__item {
+ background-color: @color-secondary;
+ border-radius: 8px;
+ padding: 20px;
+ display: flex;
+ gap: 15px;
+ border: 1px solid @color-secondary;
+ transition: transform 0.3s ease;
+ align-items: flex-start;
+ position: relative;
+
+ &:hover {
+ transform: translateY(-2px);
+ }
+ }
+
+ &__image {
+ width: 300px;
+ height: 200px;
+ border-radius: 4px;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ }
+
+ .product-img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ transition: transform 0.3s ease;
+ margin: 0;
+ }
+
+ &__details {
+ flex: 1;
+ .flex-column();
+ justify-content: space-between;
+ align-items: flex-start;
+ min-height: 200px;
+ }
+
+ &__name {
+ font-weight: bold;
+ margin-bottom: 5px;
+ color: @color-accent;
+ font-size: 18px;
+ font-family: @font-main;
+ }
+
+ &__price {
+ font-weight: bold;
+ font-size: 18px;
+ margin-bottom: 15px;
+ color: @color-text-dark;
+ }
+
+ &__controls {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ margin-top: auto;
+ width: 100%;
+ justify-content: space-between;
+ }
+
+ &__quantity {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+
+ &__qty-btn {
+ width: 30px;
+ height: 30px;
+ background-color: @color-text-dark;
+ color: @color-text-light;
+ border: none;
+ border-radius: 50%;
+ cursor: pointer;
+ .flex-center();
+ font-family: @font-main;
+ font-weight: bold;
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: scale(1.1);
+ }
+ }
+
+ &__qty-value {
+ font-weight: bold;
+ min-width: 30px;
+ text-align: center;
+ font-size: 16px;
+ }
+
+ &__cart-icon {
+ background-color: transparent;
+ color: @color-text-dark;
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ border: 2px solid @color-text-dark;
+ margin-left: 20px;
+
+ &:hover {
+ transform: scale(1.1);
+ }
+
+ i { font-size: 18px; }
+ }
+}
+
+.form {
+ &__group { margin-bottom: 15px; }
+ &__label {
+ display: block;
+ margin-bottom: 5px;
+ font-weight: bold;
+ color: #000000;
+ }
+ &__input {
+ width: 100%;
+ padding: 14px 16px;
+ border: 2px solid #ccc;
+ font-family: @font-main;
+ font-size: 15px;
+ transition: border-color 0.3s ease;
+
+ &:focus {
+ border-color: @color-primary;
+ }
+
+ &:hover {
+ border-color: darken(#ccc, 10%);
+ }
+ &::placeholder {
+ font-style: italic;
+ color: #999;
+ }
+ }
+ &__row {
+ display: flex;
+ gap: 20px;
+ justify-content: space-between;
+ }
+ &__input--half {
+ width: 100%;
+ }
+ &__radio-group {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 20px;
+ margin-top: 20px;
+ }
+ &__radio-label {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ color: @color-text-dark;
+ position: relative;
+ padding-left: 30px;
+ flex: 1;
+
+ &:hover {
+ .form__custom-radio {
+ border-color: lighten(@color-accent, 10%);
+ }
+ }
+ }
+ &__radio-input {
+ position: absolute;
+ opacity: 0;
+ cursor: pointer;
+ }
+ &__custom-radio {
+ position: absolute;
+ left: 0;
+ height: 20px;
+ width: 20px;
+ background-color: @color-secondary;
+ border: 2px solid @color-accent;
+ border-radius: 50%;
+ transition: border-color 0.3s ease;
+ }
+ &__radio-input:checked ~ &__custom-radio {
+ background-color: @color-accent;
+
+ &:after {
+ content: "";
+ position: absolute;
+ display: block;
+ top: 4px;
+ left: 4px;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: white;
+ }
+ }
+}
+
+.divider {
+ height: 1px;
+ background-color: #999;
+ margin: 20px 0;
+}
+
+.promo {
+ display: flex;
+ margin-bottom: 20px;
+
+ &__input {
+ flex: 1;
+ padding: 10px;
+ border: 1px solid #000;
+ background-color: @color-secondary;
+ font-family: @font-main;
+ height: auto;
+ min-height: 48px;
+
+ &:hover {
+ border-color: @color-primary;
+ }
+
+ &::placeholder {
+ font-style: italic;
+ color: #999;
+ }
+ }
+
+ &__btn {
+ background-color: @color-accent;
+ color: @color-secondary;
+ border: none;
+ padding: 10px 60px;
+ cursor: pointer;
+ font-family: @font-main;
+ font-size: 18px;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: lighten(@color-accent, 10%);
+ transform: translateY(-2px);
+ }
+ }
+}
+
+.summary {
+ margin-bottom: 20px;
+
+ &__item {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+ color: @color-text-dark;
+
+ &.total {
+ font-weight: bold;
+ font-size: 18px;
+ padding-top: 10px;
+ margin-top: 10px;
+ }
+ }
+}
+
+.order-btn {
+ width: 100%;
+ background-color: @color-accent;
+ color: @color-secondary;
+ border: none;
+ padding: 15px;
+ border-radius: 4px;
+ font-size: 18px;
+ cursor: pointer;
+ margin-bottom: 10px;
+ font-family: @font-main;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: lighten(@color-accent, 10%);
+ transform: translateY(-2px);
+ }
+}
+
+.privacy {
+ display: flex;
+ gap: 8px;
+ font-size: 16px;
+ color: #666;
+ margin-bottom: 20px;
+
+ input[type="checkbox"] {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+ }
+}
+
+.services {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 24px;
+ margin-bottom: 24px;
+
+ &__title {
+ font-family: @font-logo;
+ margin-bottom: 10px;
+ font-size: 18px;
+ color: @color-text-dark;
+ display: block;
+ width: 100%;
+ }
+
+ &__item {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 8px;
+ color: @color-text-dark;
+ width: 100%;
+ }
+}
+
+.cart-icon {
+ position: relative;
+}
+
+.cart-count {
+ position: absolute;
+ top: -8px;
+ right: -8px;
+ background: @color-accent;
+ color: @color-text-light;
+ border-radius: 50%;
+ width: 18px;
+ height: 18px;
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.form__input.error {
+ border-color: #ff4444;
+ box-shadow: 0 0 5px rgba(255, 68, 68, 0.3);
+}
+
+.empty-cart {
+ text-align: center;
+ padding: 40px;
+ color: #666;
+ font-size: 18px;
+}
+
+// =======================
+// === АВТОРИЗАЦИЯ ===
+// =======================
+.profile-page-main {
+ .flex-center();
+ min-height: 80vh;
+ padding: 40px 0;
+ background-color: lighten(@color-secondary, 5%);
+ z-index: 1;
+
+ .profile-container {
+ display: flex;
+ width: 100%;
+ max-width: 1000px;
+ min-height: 600px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+ background-color: @color-text-light;
+ }
+
+ .profile-left-col {
+ flex: 0 0 35%;
+ background-color: @color-primary;
+ color: @color-text-light;
+ display: flex;
+ justify-content: flex-start;
+ align-items: flex-start;
+ padding: 40px;
+ .logo {
+ font-size: 32px;
+ font-weight: normal;
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
+ color: @color-text-light;
+ }
+ }
+
+ .profile-right-col {
+ flex: 0 0 65%;
+ .flex-center();
+ padding: 40px;
+ .profile-form-block {
+ width: 100%;
+ max-width: 400px;
+ h2 {
+ font-size: 28px;
+ font-weight: normal;
+ margin-bottom: 40px;
+ text-align: left;
+ color: @color-text-dark;
+ }
+ }
+ }
+
+ .profile-form {
+ .input-group {
+ margin-bottom: 20px;
+ label {
+ display: block;
+ font-size: 12px;
+ font-weight: bold;
+ color: @color-text-dark;
+ margin-bottom: 5px;
+ text-transform: uppercase;
+ }
+ }
+
+ input[type="text"],
+ input[type="email"],
+ input[type="tel"] {
+ .input-base();
+ }
+
+ .password-link {
+ display: block;
+ text-align: left;
+ font-size: 13px;
+ color: @color-text-dark;
+ text-decoration: underline;
+ margin: 10px 0 20px;
+ &:hover {
+ color: @color-accent;
+ text-decoration: none;
+ }
+ }
+
+ .save-btn {
+ padding: 15px 30px;
+ border: none;
+ cursor: pointer;
+ font-size: 15px;
+ text-transform: uppercase;
+ transition: all 0.3s ease;
+ font-family: @font-main;
+ width: 100%;
+ margin-top: 20px;
+ background-color: @color-primary;
+ color: @color-text-light;
+
+ &:hover {
+ background-color: lighten(@color-primary, 10%);
+ transform: translateY(-2px);
+ box-shadow: @shadow-light;
+ }
+ }
+
+ .auth-actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 25px;
+ padding-top: 20px;
+ border-top: 1px solid #eee;
+
+ .auth-text {
+ font-size: 13px;
+ color: @color-text-dark;
+ }
+
+ .login-btn {
+ background-color: transparent;
+ color: @color-accent;
+ border: 1px solid @color-accent;
+ padding: 10px 25px;
+ font-size: 13px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: @color-primary;
+ color: @color-text-light;
+ }
+ }
+ }
+ }
+}
+
+// =======================
+// === СЕКЦИЯ УСЛУГ ===
+// =======================
+.services-section {
+ padding: 60px 0;
+ background-color: @color-secondary;
+}
+
+.services__wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: 30px;
+}
+
+.services__top-row {
+ display: flex;
+ gap: 30px;
+ justify-content: center;
+
+ @media (max-width: 768px) {
+ flex-direction: column;
+ align-items: center;
+ }
+}
+
+.service-card {
+ border-radius: 8px;
+ padding: 40px;
+ min-height: 200px;
+ text-align: center;
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: translateY(-5px);
+ box-shadow: @shadow-light;
+ }
+
+ &--green {
+ background: @color-primary;
+ color: @color-text-light;
+ flex: 1;
+ max-width: 450px;
+ }
+
+ &--beige {
+ background: @color-beige;
+ color: @color-text-light;
+ width: 100%;
+ max-width: 930px;
+ margin: 0 auto;
+ }
+
+ &__title {
+ font-family: @font-logo;
+ font-size: 24px;
+ font-weight: bold;
+ margin-bottom: 15px;
+ text-transform: uppercase;
+ }
+
+ &__text {
+ font-family: @font-main;
+ font-size: 16px;
+ line-height: 1.6;
+ margin: 0;
+ }
+}
+
+// =======================
+// === ФУТЕР ===
+// =======================
+.footer {
+ background-color: @color-primary;
+ color: black;
+ padding: 40px 0 10px;
+ position: relative;
+ z-index: 1000;
+
+ &::before {
+ content: '';
+ display: block;
+ position: absolute;
+ top: -80px;
+ left: 0;
+ width: 100%;
+ height: 1px;
+ visibility: hidden;
+ }
+
+ &__content {
+ display: flex;
+ gap: 20px;
+ padding-bottom: 30px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+ }
+
+ &__col {
+ flex: 1;
+ &--logo { flex: 1.5; }
+ h5 {
+ margin-bottom: 15px;
+ font-size: 14px;
+ text-transform: uppercase;
+ }
+ ul li {
+ margin-bottom: 8px;
+ a:hover { text-decoration: underline; }
+ }
+ .social-icons,
+ .payment-icons {
+ .flex-center(15px);
+ justify-content: flex-start;
+ margin-top: 10px;
+ }
+ .social-icons .icon {
+ .icon-base(20px, 1.1);
+ color: black;
+ &:hover { color: @color-accent; }
+ }
+ .payment-icons .pay-icon {
+ .icon-base(24px, 1.05);
+ color: black;
+ }
+ }
+
+ .copyright {
+ text-align: center;
+ font-size: 12px;
+ padding-top: 20px;
+ color: rgba(255, 255, 255, 0.6);
+ }
+}
+
+// =======================
+// === ДОСТАВКА ===
+// =======================
+.delivery-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 40px 20px;
+}
+
+.delivery-content h1 {
+ font-family: @font-logo;
+ font-size: 42px;
+ text-align: center;
+ margin-bottom: 50px;
+ color: #453227;
+}
+
+.delivery-section {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 30px;
+ margin-bottom: 60px;
+}
+
+.delivery-card {
+ background: white;
+ padding: 30px;
+ border-radius: 12px;
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
+ text-align: center;
+ transition: transform 0.3s ease;
+ flex: 1;
+ min-width: 350px;
+ max-width: 400px;
+}
+
+.delivery-card:hover {
+ transform: translateY(-5px);
+}
+
+.delivery-icon {
+ font-size: 48px;
+ color: #617365;
+ margin-bottom: 20px;
+}
+
+.delivery-card h3 {
+ font-family: @font-logo;
+ font-size: 24px;
+ margin-bottom: 20px;
+ color: #453227;
+}
+
+.delivery-details {
+ text-align: left;
+}
+
+.detail-item {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 12px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.detail-label {
+ font-weight: bold;
+ color: #333;
+}
+
+.detail-value {
+ color: #617365;
+ text-align: right;
+}
+
+// =======================
+// === ГАРАНТИЯ ===
+// =======================
+.warranty-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 40px 20px;
+}
+
+.warranty-content h1 {
+ font-family: 'Anek Kannada', sans-serif;
+ font-size: 42px;
+ text-align: center;
+ margin-bottom: 50px;
+ color: #453227;
+}
+
+.warranty-overview {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 25px;
+ margin-bottom: 60px;
+}
+
+.warranty-card {
+ background: white;
+ padding: 30px;
+ border-radius: 12px;
+ text-align: center;
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
+ transition: transform 0.3s ease;
+ flex: 1;
+ min-width: 250px;
+ max-width: 280px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.warranty-card:hover {
+ transform: translateY(-5px);
+}
+
+.warranty-icon {
+ font-size: 48px;
+ color: #617365;
+ margin-bottom: 20px;
+}
+
+.warranty-card h3 {
+ font-family: 'Anek Kannada', sans-serif;
+ font-size: 20px;
+ margin-bottom: 15px;
+ color: #453227;
+}
+
+.warranty-period {
+ font-size: 24px;
+ font-weight: bold;
+ color: #617365;
+ margin-top: auto;
+}
+
+.coverage-section {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 40px;
+ margin-bottom: 60px;
+}
+
+.coverage-covered,
+.coverage-not-covered {
+ flex: 1;
+ min-width: 300px;
+}
+
+.coverage-section h2 {
+ font-family: 'Anek Kannada', sans-serif;
+ font-size: 24px;
+ margin-bottom: 25px;
+ color: #453227;
+}
+
+.coverage-list {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.coverage-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 15px;
+ padding: 20px;
+ border-radius: 8px;
+ background: white;
+ box-shadow: 0 3px 10px rgba(0,0,0,0.1);
+}
+
+.coverage-item.covered i {
+ color: #28a745;
+ font-size: 20px;
+ margin-top: 2px;
+ flex-shrink: 0;
+}
+
+.coverage-item.not-covered i {
+ color: #dc3545;
+ font-size: 20px;
+ margin-top: 2px;
+ flex-shrink: 0;
+}
+
+.coverage-text {
+ flex: 1;
+}
+
+.coverage-item h4 {
+ font-family: 'Anek Kannada', sans-serif;
+ font-size: 16px;
+ margin-bottom: 5px;
+ color: #333;
+}
+
+.card {
+ min-height: 250px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: @color-text-light;
+ text-align: center;
+
+ &--green {
+ background: @color-primary;
+ flex: 0 1 450px;
+ max-width: 450px;
+ }
+
+ &--beige {
+ background: @color-beige;
+ color: @color-text-dark;
+ flex: 0 1 925px;
+ max-width: 925px;
+ }
+}
+
+.design-section {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 40px;
+ .card { width: 100%; }
+}
+
+// =======================
+// === АДАПТИВНОСТЬ ===
+// =======================
+@media (max-width: 1240px) {
+ .catalog-wrapper { gap: 20px; }
+ .catalog-sidebar { flex: 0 0 200px; }
+ .products-container {
+ gap: 15px;
+ display: flex;
+ flex-wrap: wrap;
+ }
+
+ .product-card.small1 {
+ margin-top: 100px;
+ }
+
+ .product-card.small,
+ .product-card.small1,
+ .product-card.large,
+ .product-card.wide,
+ .product-card.wide1,
+ .product-card.wide2,
+ .product-card.wide2_1,
+ .product-card.wide4 {
+ flex: 0 0 calc(33.333% - 10px);
+ max-width: calc(33.333% - 10px);
+ height: 180px;
+ margin: 0;
+
+ .product-image-container {
+ height: 180px;
+ }
+ }
+
+ .product-card.wide3 {
+ flex: 0 0 calc(25% - 10px);
+ max-width: calc(25% - 10px);
+ height: 300px;
+ margin: 0;
+
+ .product-image-container {
+ height: 350px;
+ }
+ }
+
+ .product-card.tall {
+ flex: 0 0 calc(25% - 10px);
+ max-width: calc(25% - 10px);
+ height: 300px;
+ margin: 0;
+
+ .product-image-container {
+ height: 300px;
+ }
+ }
+
+ .product-card.full-width {
+ flex: 0 0 100%;
+ max-width: 100%;
+ height: 300px;
+ margin: 0;
+
+ .product-image-container {
+ height: 300px;
+ }
+ }
+
+ .product-card.small { order: 1; }
+ .product-card.large { order: 2; }
+ .product-card.wide { order: 3; }
+
+ .product-card.small1 { order: 11; }
+ .product-card.wide2 { order: 12; }
+ .product-card.wide2_1 { order: 13; }
+
+ .product-card.wide3 { order: 21; }
+ .product-card.tall { order: 22; }
+
+ .product-card.wide3 { order: 31; }
+
+ .product-card.full-width { order: 41; flex-basis: 100%; }
+
+ .main__content {
+ gap: 20px;
+ .products {
+ flex: 0 0 35%;
+ .products__image {
+ width: 250px;
+ height: 180px;
+ }
+ }
+ .order {
+ flex: 0 0 60%;
+ padding: 30px;
+
+ .order__title {
+ font-size: 24px;
+ }
+
+ .order__section-title {
+ font-size: 16px;
+ }
+ }
+ }
+
+ .solutions-slider {
+ &__slide {
+ .solution-text-overlay {
+ top: 10%;
+ left: 5%;
+ h2 {
+ font-size: 26px;
+ margin-bottom: 5px;
+ line-height: 1.2;
+ }
+ p {
+ font-size: 18px;
+ line-height: 1.2;
+ }
+ }
+ .solution-image-link {
+ bottom: 70px;
+ padding: 10px 25px;
+ font-size: 14px;
+ }
+ }
+ }
+
+ .product__image {
+ width: 350px;
+ height: 250px;
+ }
+
+ .product__thumbnail img {
+ width: 170px;
+ height: 120px;
+ }
+}
+
+@media (max-width: 1024px) {
+ .main__content {
+ gap: 25px;
+ .products {
+ flex: 0 0 30%;
+ .products__image {
+ width: 200px;
+ height: 150px;
+ }
+
+ .products__name {
+ font-size: 16px;
+ }
+
+ .products__price {
+ font-size: 16px;
+ }
+ }
+ .order {
+ flex: 0 0 60%;
+ padding: 25px;
+
+ .order__title {
+ font-size: 22px;
+ }
+
+ .form__input {
+ padding: 12px 14px;
+ font-size: 14px;
+ }
+
+ .promo__btn {
+ padding: 10px 40px;
+ font-size: 16px;
+ }
+ }
+ }
+}
+
+@media (max-width: 768px) {
+ .container { padding: 0 15px; }
+
+ .delivery-section {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .delivery-card {
+ min-width: 100%;
+ max-width: 100%;
+ }
+
+ .delivery-content h1 {
+ font-size: 32px;
+ }
+
+ .warranty-overview {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .warranty-card {
+ max-width: 100%;
+ width: 100%;
+ }
+
+ .coverage-section {
+ flex-direction: column;
+ }
+
+ .warranty-content h1 {
+ font-size: 32px;
+ }
+
+ .header__top .container,
+ .header__bottom .container,
+ .hero__content,
+ .advantages__header,
+ .about__content,
+ .advantages__items,
+ .promo-images,
+ .stats__items,
+ .faq__items,
+ .catalog-wrapper,
+ .main__content {
+ flex-direction: column;
+ gap: 30px;
+ }
+
+ .search-catalog {
+ order: 3;
+ width: 100%;
+ max-width: 100%;
+ }
+
+ .nav-list {
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 15px;
+ }
+
+ .hero {
+ &__image-block {
+ flex: none;
+ max-width: 400px;
+ height: 400px;
+ }
+ &__circle {
+ width: 380px;
+ height: 380px;
+ }
+ &__text-block {
+ flex: none;
+ padding-left: 0;
+ text-align: center;
+ h1 { font-size: 32px; }
+ .hero__usp-text {
+ padding-left: 0;
+ justify-content: center;
+ &::before { display: none; }
+ }
+ .btn.primary-btn { margin-left: 0; }
+ }
+ }
+
+ .advantages__header h2,
+ .faq h2 { font-size: 28px; }
+
+ .faq-item,
+ .stat-item {
+ flex: none;
+ .flex-center();
+ text-align: center;
+ }
+
+ .stats .container { justify-content: center; }
+ .catalog-dropdown__menu { width: 200px; }
+
+ .catalog-sidebar { width: 100%; flex: none; }
+ .products-container { gap: 15px; }
+
+ .product-card.small,
+ .product-card.small1,
+ .product-card.large,
+ .product-card.wide,
+ .product-card.wide1,
+ .product-card.wide2,
+ .product-card.wide2_1,
+ .product-card.wide3,
+ .product-card.wide4,
+ .product-card.tall,
+ .product-card.full-width {
+ flex: 0 0 100%;
+ max-width: 100%;
+ height: 250px;
+ margin: 0;
+
+ .product-image-container {
+ height: 200px;
+ }
+ }
+
+ .main__content {
+ flex-direction: column;
+ gap: 20px;
+
+ .products,
+ .order {
+ flex: 0 0 100%;
+ width: 100%;
+ }
+
+ .products {
+ .products__item {
+ flex-direction: column;
+ text-align: center;
+ gap: 15px;
+ }
+
+ .products__image {
+ width: 100%;
+ height: 200px;
+ justify-content: center;
+ }
+
+ .products__details {
+ min-height: auto;
+ align-items: center;
+ }
+
+ .products__controls {
+ justify-content: center;
+ margin-top: 15px;
+ }
+
+ .products__cart-icon {
+ margin-left: 0;
+ }
+ }
+
+ .order {
+ padding: 20px;
+
+ .order__title {
+ font-size: 20px;
+ text-align: center;
+ }
+
+ .order__total {
+ text-align: center;
+ }
+
+ .form__radio-group {
+ flex-direction: column;
+ gap: 15px;
+ }
+
+ .form__radio-label {
+ flex: none;
+ justify-content: flex-start;
+ }
+
+ .promo {
+ flex-direction: column;
+ gap: 10px;
+
+ &__btn {
+ width: 100%;
+ padding: 12px;
+ }
+ }
+
+ .order-btn {
+ padding: 12px;
+ font-size: 16px;
+ }
+
+ .services {
+ flex-direction: column;
+ align-items: center;
+ }
+ }
+ }
+
+ .product-image-container { height: 200px; }
+ .product-card.tall .product-image-container,
+ .product-card.large .product-image-container { height: 250px; }
+
+ .profile-page-main {
+ .profile-container {
+ flex-direction: column;
+ min-height: auto;
+ max-width: 100%;
+ box-shadow: none;
+ }
+ .profile-left-col {
+ flex: none;
+ width: 100%;
+ height: 100px;
+ .flex-center();
+ padding: 0;
+ }
+ .profile-right-col {
+ flex: none;
+ width: 100%;
+ padding: 30px 20px;
+ }
+ .profile-form-block { max-width: 100%; }
+ }
+
+ .form__row { flex-direction: column; }
+ .form__input--half { flex: 0 0 100%; max-width: 100%; }
+ .services { flex-direction: column; align-items: center; }
+
+ .services-section {
+ padding: 40px 0;
+ }
+
+ .service-card {
+ padding: 30px 20px;
+ min-height: 180px;
+
+ &--green,
+ &--beige {
+ max-width: 100%;
+ }
+
+ &__title {
+ font-size: 20px;
+ }
+
+ &__text {
+ font-size: 14px;
+ }
+ }
+ .solutions-slider {
+ margin: 20px auto;
+ &__slide {
+ .solution-text-overlay {
+ top: 8%;
+ left: 4%;
+ h2 {
+ font-size: 20px;
+ margin-bottom: 3px;
+ line-height: 1.1;
+ }
+ p {
+ font-size: 15px;
+ line-height: 1.1;
+ }
+ }
+ .solution-image-link {
+ bottom: 90px;
+ padding: 8px 20px;
+ font-size: 13px;
+ }
+ }
+ }
+}
+
+// Стили для ошибок полей
+.error-input {
+ border-color: #ff4444 !important;
+ box-shadow: 0 0 0 1px #ff4444;
+}
+
+.field-error {
+ color: #ff4444;
+ font-size: 12px;
+ margin-top: 5px;
+ margin-bottom: 10px;
+}
+
+// Стили для сообщений
+.message {
+ padding: 15px;
+ margin: 20px 0;
+ border-radius: 5px;
+ display: none;
+}
+
+.message.error {
+ background-color: #ffebee;
+ color: #c62828;
+ border: 1px solid #ffcdd2;
+}
+
+.message.success {
+ background-color: #e8f5e9;
+ color: #2e7d32;
+ border: 1px solid #c8e6c9;
+}
+
+// Добавьте в конец файла
+.access-denied {
+ text-align: center;
+ padding: 80px 20px;
+ background: white;
+ border-radius: 10px;
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
+ margin: 50px 0;
+
+ h2 {
+ color: #dc3545;
+ margin-bottom: 30px;
+ font-size: 28px;
+ }
+
+ p {
+ color: #666;
+ margin-bottom: 40px;
+ font-size: 18px;
+ line-height: 1.6;
+ }
+
+ .btn {
+ margin: 5px;
+ min-width: 200px;
+ }
+}
+// =======================
+// === ПРОФИЛЬ ПОЛЬЗОВАТЕЛЯ ===
+// =======================
+
+.user-profile-dropdown {
+ position: relative;
+ display: inline-block;
+
+ &__toggle {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+ padding: 8px 12px;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.05);
+ }
+
+ .user-avatar {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ background-color: @color-primary;
+ color: @color-text-light;
+ .flex-center();
+ font-weight: bold;
+ }
+
+ .user-info {
+ display: flex;
+ flex-direction: column;
+
+ .user-email {
+ font-size: 12px;
+ color: #666;
+ max-width: 150px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .user-status {
+ font-size: 10px;
+ padding: 2px 6px;
+ border-radius: 10px;
+ text-transform: uppercase;
+
+ &.admin {
+ background-color: #617365;
+ color: white;
+ }
+
+ &.user {
+ background-color: #28a745;
+ color: white;
+ }
+ }
+ }
+ }
+
+ &__menu {
+ .menu-base();
+ width: 220px;
+ top: 100%;
+ right: 0;
+
+ .user-details {
+ padding: 15px;
+ border-bottom: 1px solid #eee;
+
+ .user-name {
+ font-weight: bold;
+ margin-bottom: 5px;
+ }
+
+ .user-registered {
+ font-size: 11px;
+ color: #999;
+ }
+ }
+
+ ul {
+ padding: 10px 0;
+
+ li {
+ padding: 8px 15px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ transition: background-color 0.3s ease;
+
+ &:hover {
+ background-color: #f5f5f5;
+ }
+
+ &.logout {
+ color: #dc3545;
+ border-top: 1px solid #eee;
+ margin-top: 5px;
+ padding-top: 12px;
+
+ &:hover {
+ background-color: #ffe6e6;
+ }
+ }
+ }
+ }
+ }
+
+ &:hover &__menu {
+ display: block;
+ }
+}
+
+// =======================
+// === КАРТОЧКА ТОВАРА ===
+// =======================
+
+.product-image-container {
+ position: relative;
+ overflow: hidden;
+ margin-bottom: 0;
+ padding: 0;
+ height: 250px;
+ .flex-center();
+
+ &:hover {
+ .product-overlay-info {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+}
+
+.product-overlay-info {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
+ color: white;
+ padding: 15px;
+ opacity: 0;
+ transform: translateY(10px);
+ transition: all 0.3s ease;
+
+ .product-overlay-name {
+ font-weight: bold;
+ font-size: 14px;
+ margin-bottom: 5px;
+ }
+
+ .product-overlay-price {
+ font-size: 16px;
+ font-weight: bold;
+
+ .old-price {
+ text-decoration: line-through;
+ font-size: 12px;
+ color: #ccc;
+ margin-right: 5px;
+ }
+
+ .current-price {
+ color: #ffd700;
+ }
+ }
+
+ .product-overlay-category {
+ font-size: 11px;
+ opacity: 0.8;
+ margin-top: 3px;
+ }
+
+ .product-overlay-stock {
+ font-size: 11px;
+ margin-top: 5px;
+
+ &.out-of-stock {
+ color: #ff6b6b;
+ }
+
+ i {
+ margin-right: 5px;
+ }
+ }
+}
+
+.product-card-details {
+ padding: 15px;
+ background: white;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+
+ .product-card-name {
+ font-weight: bold;
+ font-size: 16px;
+ margin-bottom: 8px;
+ color: @color-text-dark;
+ }
+
+ .product-card-description {
+ font-size: 13px;
+ color: #777;
+ margin-bottom: 10px;
+ flex-grow: 1;
+ }
+
+ .product-card-attributes {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 10px;
+
+ .attribute {
+ font-size: 11px;
+ background: #f5f5f5;
+ padding: 3px 8px;
+ border-radius: 12px;
+ color: #666;
+
+ i {
+ margin-right: 3px;
+ }
+ }
+ }
+
+ .product-card-price {
+ margin-bottom: 10px;
+
+ .old-price {
+ text-decoration: line-through;
+ font-size: 14px;
+ color: #999;
+ margin-right: 8px;
+ }
+
+ .current-price {
+ font-size: 18px;
+ font-weight: bold;
+ color: @color-button;
+ }
+ }
+
+ .add-to-cart-btn {
+ width: 100%;
+ padding: 8px;
+ font-size: 14px;
+ }
+
+ .admin-actions {
+ display: flex;
+ gap: 5px;
+ margin-top: 10px;
+
+ .admin-btn {
+ flex: 1;
+ font-size: 12px;
+ padding: 6px;
+
+ &.delete-btn {
+ background: #dc3545;
+
+ &:hover {
+ background: #c82333;
+ }
+ }
+ }
+ }
+}
+
+// =======================
+// === ПРОФИЛЬ ПОЛЬЗОВАТЕЛЯ ===
+// =======================
+
+.user-profile-dropdown {
+ position: relative;
+ display: inline-block;
+
+ .user-profile-toggle {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ cursor: pointer;
+ padding: 8px 12px;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.05);
+
+ .dropdown-arrow {
+ transform: rotate(180deg);
+ }
+ }
+
+ .user-avatar {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #617365 0%, #453227 100%);
+ color: @color-text-light;
+ .flex-center();
+ font-weight: bold;
+ font-size: 16px;
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
+ }
+
+ .user-info {
+ display: flex;
+ flex-direction: column;
+
+ .user-email {
+ font-size: 12px;
+ color: #666;
+ max-width: 120px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .user-status {
+ font-size: 10px;
+ padding: 2px 8px;
+ border-radius: 12px;
+ text-transform: uppercase;
+ font-weight: bold;
+ text-align: center;
+ margin-top: 2px;
+
+ &.admin {
+ background-color: #617365;
+ color: white;
+ border: 1px solid #617365;
+ }
+
+ &.user {
+ background-color: #28a745;
+ color: white;
+ border: 1px solid #28a745;
+ }
+ }
+ }
+
+ .dropdown-arrow {
+ font-size: 10px;
+ color: #666;
+ transition: transform 0.3s ease;
+ }
+ }
+
+ .user-profile-menu {
+ .menu-base();
+ width: 280px;
+ top: 100%;
+ right: 0;
+ margin-top: 10px;
+ padding: 0;
+ overflow: hidden;
+
+ .user-profile-header {
+ padding: 20px;
+ background: linear-gradient(135deg, #617365 0%, #453227 100%);
+ color: white;
+
+ .user-profile-name {
+ font-weight: bold;
+ margin-bottom: 8px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 16px;
+ }
+
+ .user-profile-details {
+ small {
+ display: block;
+ opacity: 0.8;
+ margin-bottom: 5px;
+ font-size: 11px;
+
+ i {
+ margin-right: 5px;
+ width: 14px;
+ text-align: center;
+ }
+ }
+ }
+ }
+
+ .user-profile-links {
+ list-style: none;
+ padding: 10px 0;
+
+ li {
+ border-bottom: 1px solid #f0f0f0;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ a {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 20px;
+ color: #333;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: #f8f9fa;
+ color: @color-primary;
+ text-decoration: none;
+
+ i {
+ transform: scale(1.1);
+ }
+ }
+
+ i {
+ width: 20px;
+ text-align: center;
+ font-size: 14px;
+ color: #617365;
+ transition: transform 0.3s ease;
+ }
+
+ span {
+ flex-grow: 1;
+ }
+ }
+ }
+
+ .logout-item {
+ border-top: 2px solid #f0f0f0;
+ margin-top: 5px;
+
+ a {
+ color: #dc3545;
+
+ &:hover {
+ background-color: #ffe6e6;
+ color: #c82333;
+
+ i {
+ color: #dc3545;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ &:hover .user-profile-menu {
+ display: block;
+ animation: fadeIn 0.3s ease;
+ }
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+// Для мобильных устройств
+@media (max-width: 768px) {
+ .user-profile-dropdown {
+ .user-profile-toggle {
+ .user-info {
+ display: none;
+ }
+
+ .dropdown-arrow {
+ display: none;
+ }
+ }
+
+ .user-profile-menu {
+ width: 250px;
+ right: -50px;
+ }
+ }
+}
+// Добавьте в конец файла
+.unavailable-product {
+ position: relative;
+ opacity: 0.6;
+ filter: grayscale(0.7);
+
+ &::before {
+ content: "ТОВАР ЗАКОНЧИЛСЯ";
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: rgba(0, 0, 0, 0.85);
+ color: white;
+ padding: 15px 25px;
+ border-radius: 5px;
+ font-weight: bold;
+ font-size: 16px;
+ text-align: center;
+ z-index: 100;
+ white-space: nowrap;
+ pointer-events: none;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
+ }
+
+ .product-name-overlay {
+ .name, .price {
+ color: #999 !important;
+ text-shadow: none !important;
+ }
+ }
+
+ .add-to-cart-btn {
+ display: none !important;
+ }
+
+ &:hover {
+ transform: none !important;
+ cursor: not-allowed;
+ }
+}
+
+.out-of-stock-badge {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ background: #6c757d;
+ color: white;
+ padding: 5px 10px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: bold;
+ z-index: 10;
+}
+
+// Для админ-таблицы
+.admin-table tr.unavailable {
+ background-color: #f8f9fa !important;
+ opacity: 0.7;
+
+ td {
+ color: #999;
+ }
+}
+
+.status-unavailable {
+ background-color: #6c757d !important;
+ color: white !important;
+}
+
+// Похожие товары
+.similar-products {
+ margin: 40px 0;
+
+ h2 {
+ font-size: 24px;
+ margin-bottom: 20px;
+ color: #453227;
+ }
+}
+
+.products-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 20px;
+}
+
+.product-card {
+ background: white;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
+
+ &:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
+ }
+}
+
+.product-image {
+ width: 100%;
+ height: 250px;
+ overflow: hidden;
+ background: #f5f5f5;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ transition: transform 0.3s ease;
+ }
+
+ &:hover img {
+ transform: scale(1.05);
+ }
+}
+
+.product-info {
+ padding: 15px;
+
+ h3 {
+ font-size: 16px;
+ margin: 0 0 10px;
+ color: #333;
+ min-height: 40px;
+ }
+
+ .product-price {
+ font-size: 20px;
+ font-weight: bold;
+ color: #453227;
+ margin: 10px 0;
+ }
+
+ .btn {
+ width: 100%;
+ padding: 10px;
+ text-align: center;
+ margin-top: 10px;
+ }
+}
+
+// Адаптивность для страницы продукта
+@media (max-width: 1200px) {
+ .product__section {
+ grid-template-columns: 500px 1fr;
+ gap: 30px;
+ }
+
+ .product__main-image {
+ height: 500px;
+ }
+}
+
+@media (max-width: 992px) {
+ .product__section {
+ grid-template-columns: 1fr;
+ gap: 30px;
+ }
+
+ .product__main-image {
+ height: 450px;
+ }
+
+ .product__thumbnails {
+ grid-template-columns: repeat(4, 1fr);
+ }
+}
+
+@media (max-width: 576px) {
+ .product__main-image {
+ height: 350px;
+ }
+
+ .product__thumbnails {
+ grid-template-columns: repeat(3, 1fr);
+ }
+
+ .product__info h1 {
+ font-size: 24px;
+ }
+
+ .product__price .current-price {
+ font-size: 28px;
+ }
+}
\ No newline at end of file
diff --git a/catalog.php b/public/catalog.php
similarity index 84%
rename from catalog.php
rename to public/catalog.php
index f731d5f..75c9c09 100644
--- a/catalog.php
+++ b/public/catalog.php
@@ -1,1382 +1,1302 @@
-getConnection();
-
-// Получаем параметры фильтрации
-$category_id = $_GET['category'] ?? 0;
-$search = $_GET['search'] ?? '';
-$min_price = $_GET['min_price'] ?? 0;
-$max_price = $_GET['max_price'] ?? 1000000;
-$colors = isset($_GET['colors']) ? (array)$_GET['colors'] : [];
-$materials = isset($_GET['materials']) ? (array)$_GET['materials'] : [];
-$show_all = isset($_GET['show_all']) && $_GET['show_all'] == '1';
-
-// Проверяем уведомления
-$success_message = $_GET['success'] ?? '';
-$error_message = $_GET['error'] ?? '';
-
-try {
- // Получаем информацию о пользователе
- $user_id = $_SESSION['user_id'] ?? 0;
- $userStmt = $db->prepare("SELECT * FROM users WHERE user_id = ?");
- $userStmt->execute([$user_id]);
- $user = $userStmt->fetch();
-
- $isAdmin = isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true;
- $userEmail = $_SESSION['user_email'] ?? '';
- $fullName = $_SESSION['full_name'] ?? $userEmail;
- $loginTime = $_SESSION['login_time'] ?? time();
-
- // Получаем категории (ИСПРАВЛЕНО: убрано дублирование fetchAll)
- try {
- $categoriesStmt = $db->prepare("
- SELECT * FROM categories
- WHERE is_active = TRUE
- ORDER BY sort_order, name
- ");
- $categoriesStmt->execute();
- $categories = $categoriesStmt->fetchAll();
- } catch (PDOException $e) {
- $categories = [];
- error_log("Ошибка получения категорий: " . $e->getMessage());
- }
-
- // Получаем подкатегории для текущей категории (если выбрана)
- $subcategories = [];
- if ($category_id > 0) {
- $subStmt = $db->prepare("
- SELECT * FROM subcategories
- WHERE category_id = ? AND is_active = TRUE
- ORDER BY sort_order, name
- ");
- $subStmt->execute([$category_id]);
- $subcategories = $subStmt->fetchAll();
- }
-
- // Проверяем наличие столбцов color и material в таблице products
- $checkColumns = $db->query("
- SELECT column_name
- FROM information_schema.columns
- WHERE table_name = 'products'
- AND column_name IN ('color', 'material')
- ");
- $existingColumns = $checkColumns->fetchAll(PDO::FETCH_COLUMN);
-
- $hasColorColumn = in_array('color', $existingColumns);
- $hasMaterialColumn = in_array('material', $existingColumns);
-
- // Получаем доступные цвета из базы данных (если столбец существует)
- $availableColors = [];
- if ($hasColorColumn) {
- $colorsStmt = $db->query("
- SELECT DISTINCT color FROM products
- WHERE color IS NOT NULL AND color != '' AND is_available = TRUE
- ORDER BY color
- ");
- $availableColors = $colorsStmt->fetchAll(PDO::FETCH_COLUMN);
- }
-
- // Получаем доступные материалы из базы данных (если столбец существует)
- $availableMaterials = [];
- if ($hasMaterialColumn) {
- $materialsStmt = $db->query("
- SELECT DISTINCT material FROM products
- WHERE material IS NOT NULL AND material != '' AND is_available = TRUE
- ORDER BY material
- ");
- $availableMaterials = $materialsStmt->fetchAll(PDO::FETCH_COLUMN);
- }
-
- // Получаем ВСЕ товары ИЗ БАЗЫ ДАННЫХ с фильтрами
- $sql = "SELECT p.*, c.name as category_name
- FROM products p
- LEFT JOIN categories c ON p.category_id = c.category_id
- WHERE 1=1";
-
- $params = [];
- $hasFilters = false;
-
- // Фильтрация по доступности (если не админ и не показываем все)
- if (!$show_all && !$isAdmin) {
- $sql .= " AND p.is_available = TRUE";
- }
-
- // Фильтрация по категории
- if ($category_id > 0) {
- $sql .= " AND p.category_id = ?";
- $params[] = $category_id;
- $hasFilters = true;
- }
-
- // Фильтрация по цене
- if ($min_price > 0 || $max_price < 1000000) {
- $sql .= " AND p.price BETWEEN ? AND ?";
- $params[] = $min_price;
- $params[] = $max_price;
- $hasFilters = true;
- }
-
- // Фильтрация по цвету
- if ($hasColorColumn && !empty($colors)) {
- $placeholders = implode(',', array_fill(0, count($colors), '?'));
- $sql .= " AND p.color IN ($placeholders)";
- $params = array_merge($params, $colors);
- $hasFilters = true;
- }
-
- // Фильтрация по материалу
- if ($hasMaterialColumn && !empty($materials)) {
- $placeholders = implode(',', array_fill(0, count($materials), '?'));
- $sql .= " AND p.material IN ($placeholders)";
- $params = array_merge($params, $materials);
- $hasFilters = true;
- }
-
- // Поиск
- if (!empty($search)) {
- $sql .= " AND (p.name LIKE ? OR p.description LIKE ?)";
- $params[] = "%$search%";
- $params[] = "%$search%";
- $hasFilters = true;
- }
-
- // Получаем товары из базы данных с фильтрами
- $sql = "SELECT p.*, c.name as category_name
- FROM products p
- LEFT JOIN categories c ON p.category_id = c.category_id
- WHERE 1=1";
-
- $params = [];
-
- // Фильтрация по доступности
- if (!$show_all && !$isAdmin) {
- $sql .= " AND p.is_available = TRUE";
- }
-
- // Фильтрация по категории
- if ($category_id > 0) {
- $sql .= " AND p.category_id = ?";
- $params[] = $category_id;
- }
-
- // Фильтрация по цене
- if ($min_price > 0 || $max_price < 1000000) {
- $sql .= " AND p.price BETWEEN ? AND ?";
- $params[] = $min_price;
- $params[] = $max_price;
- }
-
- // Поиск
- if (!empty($search)) {
- $sql .= " AND (p.name LIKE ? OR p.description LIKE ?)";
- $params[] = "%$search%";
- $params[] = "%$search%";
- }
-
- $sql .= " ORDER BY p.product_id ASC LIMIT 9"; // Ограничиваем 9 товаров
-
- $stmt = $db->prepare($sql);
- $stmt->execute($params);
- $filteredProducts = $stmt->fetchAll();
-
- // Оригинальные размеры для первых 9 товаров (ID 1-9)
- $originalSizes = [
- 1 => 'small', // Светильник MINNIGHT
- 2 => 'large', // Диван MODERN (Кровать MODER)
- 3 => 'tall align-right', // Торшер MARCIA
- 4 => 'wide', // Светильник POLET
- 5 => 'small1', // Стол NORD
- 6 => 'wide2', // Диван ROYALTY
- 7 => 'wide3', // Кресло MINIMAL
- 8 => 'wide2_1', // Стол LONKI
- 9 => 'full-width' // Диван HEMMINS
- ];
-
- // Классы для изображений
- $imgClasses = ['small1', 'wide2', 'wide3', 'wide2_1'];
-
-} catch (PDOException $e) {
- die("Ошибка базы данных: " . $e->getMessage());
-}
-?>
-
-
-
-
-
- AETERNA - Каталог
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 'Категория успешно добавлена!',
- 'category_updated' => 'Категория успешно обновлена!',
- 'category_deleted' => 'Категория успешно удалена!',
- 'product_added' => 'Товар успешно добавлен!',
- 'product_updated' => 'Товар успешно обновлен!',
- 'product_deleted' => 'Товар успешно удален!'
- ];
- echo $messages[$success_message] ?? 'Операция выполнена успешно!';
- ?>
-
-
-
-
-
- Ошибка: = htmlspecialchars($error_message) ?>
-
-
-
-
-
-
-
- Панель управления каталогом
-
- Вы вошли как администратор
-
-
-
-
-
-
-
-
-
- Добро пожаловать, = htmlspecialchars($fullName) ?> !
-
-
- Администратор
-
-
-
-
- Показаны все товары
-
-
-
-
-
-
-
-
-
-
- Каталог мебели
-
- ( товаров)
-
-
-
-
-
- Результаты поиска по запросу: "= htmlspecialchars($search) ?> "
-
- Очистить поиск
-
-
-
-
-
-
-
-
- Показаны все товары, включая недоступные. Недоступные товары отмечены серым цветом.
-
-
-
-
-
-
-
- ['name' => 'Светильник MINNIGHT', 'price' => 7999, 'image' => 'img2/1_2.png', 'size' => 'small'],
- 2 => ['name' => 'Кровать MODER', 'price' => 45999, 'image' => 'img2/3_3.png', 'size' => 'large'],
- 3 => ['name' => 'Торшер MARCIA', 'price' => 11999, 'image' => 'img2/2_2.png', 'size' => 'tall align-right'],
- 4 => ['name' => 'Светильник POLET', 'price' => 5499, 'image' => 'img2/4.jpg', 'size' => 'wide'],
- 5 => ['name' => 'Стол NORD', 'price' => 23999, 'image' => 'img2/5_5.png', 'size' => 'small1'],
- 6 => ['name' => 'Диван ROYALTY', 'price' => 78999, 'image' => 'img2/6_6.png', 'size' => 'wide2'],
- 7 => ['name' => 'Кресло MINIMAL', 'price' => 29999, 'image' => 'img2/7_7.png', 'size' => 'wide3'],
- 8 => ['name' => 'Стол LONKI', 'price' => 34999, 'image' => 'img2/8_8.png', 'size' => 'wide2_1'],
- 9 => ['name' => 'Диван HEMMINS', 'price' => 89999, 'image' => 'img2/9_9.png', 'size' => 'full-width']
- ];
-
- // Если в базе данных товаров нет, показываем оригинальные
- if (empty($filteredProducts)) {
- foreach ($originalProducts as $id => $product): ?>
-
-
-
-
-
-
= $product['name'] ?>
-
= number_format($product['price'], 0, '', ' ') ?> ₽
-
-
-
-
-
-
-
-
-
-
-
-
= htmlspecialchars($product['name']) ?>
-
= number_format($product['price'], 0, '', ' ') ?> ₽
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+getConnection();
+
+$category_id = $_GET['category'] ?? 0;
+$search = $_GET['search'] ?? '';
+$min_price = $_GET['min_price'] ?? 0;
+$max_price = $_GET['max_price'] ?? 1000000;
+$colors = isset($_GET['colors']) ? (array)$_GET['colors'] : [];
+$materials = isset($_GET['materials']) ? (array)$_GET['materials'] : [];
+$show_all = isset($_GET['show_all']) && $_GET['show_all'] == '1';
+
+$success_message = $_GET['success'] ?? '';
+$error_message = $_GET['error'] ?? '';
+
+try {
+
+ $user_id = $_SESSION['user_id'] ?? 0;
+ $userStmt = $db->prepare("SELECT * FROM users WHERE user_id = ?");
+ $userStmt->execute([$user_id]);
+ $user = $userStmt->fetch();
+
+ $isAdmin = isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true;
+ $userEmail = $_SESSION['user_email'] ?? '';
+ $fullName = $_SESSION['full_name'] ?? $userEmail;
+ $loginTime = $_SESSION['login_time'] ?? time();
+
+ try {
+ $categoriesStmt = $db->prepare("
+ SELECT * FROM categories
+ WHERE is_active = TRUE
+ ORDER BY sort_order, name
+ ");
+ $categoriesStmt->execute();
+ $categories = $categoriesStmt->fetchAll();
+ } catch (PDOException $e) {
+ $categories = [];
+ error_log("Ошибка получения категорий: " . $e->getMessage());
+ }
+
+ $subcategories = [];
+ if ($category_id > 0) {
+ $subStmt = $db->prepare("
+ SELECT * FROM subcategories
+ WHERE category_id = ? AND is_active = TRUE
+ ORDER BY sort_order, name
+ ");
+ $subStmt->execute([$category_id]);
+ $subcategories = $subStmt->fetchAll();
+ }
+
+ $checkColumns = $db->query("
+ SELECT column_name
+ FROM information_schema.columns
+ WHERE table_name = 'products'
+ AND column_name IN ('color', 'material')
+ ");
+ $existingColumns = $checkColumns->fetchAll(PDO::FETCH_COLUMN);
+
+ $hasColorColumn = in_array('color', $existingColumns);
+ $hasMaterialColumn = in_array('material', $existingColumns);
+
+ $availableColors = [];
+ if ($hasColorColumn) {
+ $colorsStmt = $db->query("
+ SELECT DISTINCT color FROM products
+ WHERE color IS NOT NULL AND color != '' AND is_available = TRUE
+ ORDER BY color
+ ");
+ $availableColors = $colorsStmt->fetchAll(PDO::FETCH_COLUMN);
+ }
+
+ $availableMaterials = [];
+ if ($hasMaterialColumn) {
+ $materialsStmt = $db->query("
+ SELECT DISTINCT material FROM products
+ WHERE material IS NOT NULL AND material != '' AND is_available = TRUE
+ ORDER BY material
+ ");
+ $availableMaterials = $materialsStmt->fetchAll(PDO::FETCH_COLUMN);
+ }
+
+ $sql = "SELECT p.*, c.name as category_name
+ FROM products p
+ LEFT JOIN categories c ON p.category_id = c.category_id
+ WHERE 1=1";
+
+ $params = [];
+
+ if (!$show_all && !$isAdmin) {
+ $sql .= " AND p.is_available = TRUE";
+ }
+
+ if ($category_id > 0) {
+ $sql .= " AND p.category_id = ?";
+ $params[] = $category_id;
+ }
+
+ if ($min_price > 0 || $max_price < 1000000) {
+ $sql .= " AND p.price BETWEEN ? AND ?";
+ $params[] = $min_price;
+ $params[] = $max_price;
+ }
+
+ if ($hasColorColumn && !empty($colors)) {
+ $placeholders = implode(',', array_fill(0, count($colors), '?'));
+ $sql .= " AND p.color IN ($placeholders)";
+ $params = array_merge($params, $colors);
+ }
+
+ if ($hasMaterialColumn && !empty($materials)) {
+ $placeholders = implode(',', array_fill(0, count($materials), '?'));
+ $sql .= " AND p.material IN ($placeholders)";
+ $params = array_merge($params, $materials);
+ }
+
+ if (!empty($search)) {
+ $sql .= " AND (p.name LIKE ? OR p.description LIKE ?)";
+ $params[] = "%$search%";
+ $params[] = "%$search%";
+ }
+
+ $sql .= " ORDER BY p.product_id ASC LIMIT 50";
+
+ $stmt = $db->prepare($sql);
+ $stmt->execute($params);
+ $filteredProducts = $stmt->fetchAll();
+
+ $originalSizes = [
+ 1 => 'small',
+ 2 => 'large',
+ 3 => 'tall align-right',
+ 4 => 'wide',
+ 5 => 'small1',
+ 6 => 'wide2',
+ 7 => 'wide3',
+ 8 => 'wide2_1',
+ 9 => 'full-width'
+ ];
+
+ $imgClasses = ['small1', 'wide2', 'wide3', 'wide2_1'];
+
+} catch (PDOException $e) {
+ die("Ошибка базы данных: " . $e->getMessage());
+}
+?>
+
+
+
+
+
+
+ AETERNA - Каталог
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'Категория успешно добавлена!',
+ 'category_updated' => 'Категория успешно обновлена!',
+ 'category_deleted' => 'Категория успешно удалена!',
+ 'product_added' => 'Товар успешно добавлен!',
+ 'product_updated' => 'Товар успешно обновлен!',
+ 'product_deleted' => 'Товар успешно удален!'
+ ];
+ echo $messages[$success_message] ?? 'Операция выполнена успешно!';
+ ?>
+
+
+
+
+
+ Ошибка: = htmlspecialchars($error_message) ?>
+
+
+
+
+
+
+
+ Панель управления каталогом
+
+ Вы вошли как администратор
+
+
+
+
+
+
+
+
+
+ Добро пожаловать, = htmlspecialchars($fullName) ?> !
+
+
+ Администратор
+
+
+
+
+ Показаны все товары
+
+
+
+
+
+
+
+
+
+
+ Каталог мебели
+
+ ( товаров)
+
+
+
+
+
+ Результаты поиска по запросу: "= htmlspecialchars($search) ?> "
+
+ Очистить поиск
+
+
+
+
+
+
+
+
+ Показаны все товары, включая недоступные. Недоступные товары отмечены серым цветом.
+
+
+
+
+
+
+ ['name' => 'Светильник MINNIGHT', 'price' => 7999, 'image' => 'img2/1_2.png', 'size' => 'small'],
+ 2 => ['name' => 'Кровать MODER', 'price' => 45999, 'image' => 'img2/3_3.png', 'size' => 'large'],
+ 3 => ['name' => 'Торшер MARCIA', 'price' => 11999, 'image' => 'img2/2_2.png', 'size' => 'tall align-right'],
+ 4 => ['name' => 'Светильник POLET', 'price' => 5499, 'image' => 'img2/4.jpg', 'size' => 'wide'],
+ 5 => ['name' => 'Стол NORD', 'price' => 23999, 'image' => 'img2/5_5.png', 'size' => 'small1'],
+ 6 => ['name' => 'Диван ROYALTY', 'price' => 78999, 'image' => 'img2/6_6.png', 'size' => 'wide2'],
+ 7 => ['name' => 'Кресло MINIMAL', 'price' => 29999, 'image' => 'img2/7_7.png', 'size' => 'wide3'],
+ 8 => ['name' => 'Стол LONKI', 'price' => 34999, 'image' => 'img2/8_8.png', 'size' => 'wide2_1'],
+ 9 => ['name' => 'Диван HEMMINS', 'price' => 89999, 'image' => 'img2/9_9.png', 'size' => 'full-width']
+ ];
+
+ if (empty($filteredProducts)) {
+ foreach ($originalProducts as $id => $product): ?>
+
+
+
+
+
+
= $product['name'] ?>
+
= number_format($product['price'], 0, '', ' ') ?> ₽
+
+
+
+
+
+
+
+
+
+
+
+
= htmlspecialchars($product['name']) ?>
+
= number_format($product['price'], 0, '', ' ') ?> ₽
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/оформление_заказа.php b/public/checkout.php
similarity index 81%
rename from оформление_заказа.php
rename to public/checkout.php
index b3fe155..7214617 100644
--- a/оформление_заказа.php
+++ b/public/checkout.php
@@ -1,484 +1,501 @@
-getConnection();
-
-// Получаем корзину пользователя
-$cart_items = [];
-$total_amount = 0;
-$total_quantity = 0;
-
-try {
- $stmt = $db->prepare("
- SELECT
- c.cart_id,
- c.product_id,
- c.quantity,
- p.name,
- p.price,
- p.image_url,
- p.stock_quantity
- FROM cart c
- JOIN products p ON c.product_id = p.product_id
- WHERE c.user_id = ? AND p.is_available = TRUE
- ORDER BY c.created_at DESC
- ");
- $stmt->execute([$user_id]);
- $cart_items = $stmt->fetchAll();
-
- // Рассчитываем общую сумму
- foreach ($cart_items as $item) {
- $total_amount += $item['price'] * $item['quantity'];
- $total_quantity += $item['quantity'];
- }
-
-} catch (PDOException $e) {
- $error = "Ошибка загрузки корзины: " . $e->getMessage();
-}
-?>
-
-
-
-
-
- AETERNA - Оформление заказа
-
-
-
-
-
-
-
-
-
-
-
-
-
- Товары в корзине
-
-
-
-
-
Ваша корзина пуста
-
-
-
-
-
-
-
-
-
-
-
= htmlspecialchars($item['name']) ?>
-
= number_format($item['price'], 0, '', ' ') ?> ₽
-
-
- -
- = $item['quantity'] ?>
- +
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ПРИМЕНИТЬ
-
-
-
-
- Товары, = $total_quantity ?> шт.
- = number_format($total_amount, 0, '', ' ') ?> ₽
-
-
- Скидка
- 0 ₽
-
-
-
- Доставка
- 2000 ₽
-
-
-
- ИТОГО:
- = number_format($total_amount + 2000, 0, '', ' ') ?> ₽
-
-
-
- ОФОРМИТЬ ЗАКАЗ
-
-
-
- Даю согласие на обработку персональных данных
-
-
- Необходимо согласие на обработку персональных данных
-
-
-
-
УСЛУГИ
-
- Доставка
- 2000 ₽
-
-
- Сборка
- 1000 ₽
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+getConnection();
+
+$cart_items = [];
+$total_amount = 0;
+$total_quantity = 0;
+
+try {
+ $stmt = $db->prepare("
+ SELECT
+ c.cart_id,
+ c.product_id,
+ c.quantity,
+ p.name,
+ p.price,
+ p.image_url,
+ p.stock_quantity
+ FROM cart c
+ JOIN products p ON c.product_id = p.product_id
+ WHERE c.user_id = ? AND p.is_available = TRUE
+ ORDER BY c.created_at DESC
+ ");
+ $stmt->execute([$user_id]);
+ $cart_items = $stmt->fetchAll();
+
+ foreach ($cart_items as $item) {
+ $total_amount += $item['price'] * $item['quantity'];
+ $total_quantity += $item['quantity'];
+ }
+
+} catch (PDOException $e) {
+ $error = "Ошибка загрузки корзины: " . $e->getMessage();
+}
+?>
+
+
+
+
+
+
+ AETERNA - Оформление заказа
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Товары в корзине
+
+
+
+
+
Ваша корзина пуста
+
+
+
+
+
+
+
+
+
+
+
= htmlspecialchars($item['name']) ?>
+
= number_format($item['price'], 0, '', ' ') ?> ₽
+
+
+ -
+ = $item['quantity'] ?>
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ПРИМЕНИТЬ
+
+
+
+
+ Товары, = $total_quantity ?> шт.
+ = number_format($total_amount, 0, '', ' ') ?> ₽
+
+
+ Скидка
+ 0 ₽
+
+
+
+ Доставка
+ 2000 ₽
+
+
+
+ ИТОГО:
+ = number_format($total_amount + 2000, 0, '', ' ') ?> ₽
+
+
+
+ ОФОРМИТЬ ЗАКАЗ
+
+
+
+ Даю согласие на обработку персональных данных
+
+
+ Необходимо согласие на обработку персональных данных
+
+
+
+
УСЛУГИ
+
+ Доставка
+ 2000 ₽
+
+
+ Сборка
+ 1000 ₽
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cite_mebel.php b/public/cite_mebel.php
similarity index 86%
rename from cite_mebel.php
rename to public/cite_mebel.php
index ad53c38..8c22cbe 100644
--- a/cite_mebel.php
+++ b/public/cite_mebel.php
@@ -1,762 +1,760 @@
-
-
-
-
-
- AETERNA - Мебель и Интерьер
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ДОБАВЬТЕ ИЗЫСКАННОСТИ В СВОЙ ИНТЕРЬЕР
-
Мы создаем мебель, которая сочетает в себе безупречный дизайн, натуральные материалы, продуманный функционал, чтобы ваш день начинался и заканчивался с комфортом.
-
-
-
ПЕРЕЙТИ В КАТАЛОГ
-
-
ПЕРЕЙТИ В КАТАЛОГ
-
-
-
-
-
-
-
-
-
-
-
-
О НАС
-
Компания AETERNA - российский производитель качественной корпусной и мягкой мебели для дома и офиса. С 2015 года мы успешно реализуем проекты любой сложности, сочетая современные технологии, проверенные материалы и классическое мастерство.
-
-
-
-
-
-
-
Наша сеть включает 30+ российских фабрик, отобранных по строгим стандартам качества. Мы сотрудничаем исключительно с лидерами рынка, чья продукция доказала свое превосходство временем.
-
-
-
-
-
-
-
-
-
-
-
-
ГОТОВОЕ РЕШЕНИЕ ДЛЯ ВАШЕЙ ГОСТИНОЙ
-
УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС
-
-
Подробнее
-
-
-
-
-
-
ГОТОВОЕ РЕШЕНИЕ ДЛЯ ВАШЕЙ СПАЛЬНИ
-
УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС
-
-
Подробнее
-
-
-
-
-
-
-
-
-
-
-
-
30 000+
-
Довольных покупателей
-
-
-
4500+
-
Реализованных заказов
-
-
-
-
-
-
-
-
ОТВЕТЫ НА ВОПРОСЫ
-
-
-
1
-
-
Сколько времени занимает доставка?
-
Доставка готовых позиций занимает 1-3 дня. Мебель на заказ изготавливается от 14 до 45 рабочих дней, в зависимости от сложности. Точные сроки озвучит ваш менеджер при оформлении заказа.
-
-
-
-
2
-
-
Нужно ли вносить предоплату?
-
Да, для запуска заказа в производство необходима предоплата в размере 50-70% от стоимости, в зависимости от изделия. Оставшаяся сумма оплачивается при доставке и приемке мебели.
-
-
-
-
3
-
-
Предоставляется ли рассрочка или кредит?
-
Да, мы сотрудничаем с несколькими банками и предлагаем рассрочку на 6 или 12 месяцев без первоначального взноса, а также кредит на более длительный срок. Все условия уточняйте у вашего менеджера.
-
-
-
-
4
-
-
Что делать, если мебель пришла с дефектом?
-
В этом случае необходимо в течение 7 дней со дня доставки сообщить нам о проблеме, прислать фото/видео дефекта. Мы оперативно решим вопрос о бесплатной замене или ремонте изделия.
-
-
-
-
Задать вопрос
-
-
-
-
-
-
-
-
-
-
-
-
- Быстрый вход
-
-
-
- Войти как Администратор
-
-
- Войти как Пользователь
-
-
-
- Для полного функционала перейдите на страницу входа
-
-
-
-
-
-
-
-
-
-
-
-
Быстрый вход:
-
- Войти как Админ
-
-
- Войти как Пользователь
-
-
-
-
+
+
+
+
+
+
+ AETERNA - Мебель и Интерьер
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ДОБАВЬТЕ ИЗЫСКАННОСТИ В СВОЙ ИНТЕРЬЕР
+
Мы создаем мебель, которая сочетает в себе безупречный дизайн, натуральные материалы, продуманный функционал, чтобы ваш день начинался и заканчивался с комфортом.
+
+
+
ПЕРЕЙТИ В КАТАЛОГ
+
+
ПЕРЕЙТИ В КАТАЛОГ
+
+
+
+
+
+
+
+
+
+
+
+
О НАС
+
Компания AETERNA - российский производитель качественной корпусной и мягкой мебели для дома и офиса. С 2015 года мы успешно реализуем проекты любой сложности, сочетая современные технологии, проверенные материалы и классическое мастерство.
+
+
+
+
+
+
+
Наша сеть включает 30+ российских фабрик, отобранных по строгим стандартам качества. Мы сотрудничаем исключительно с лидерами рынка, чья продукция доказала свое превосходство временем.
+
+
+
+
+
+
+
+
+
+
+
+
ГОТОВОЕ РЕШЕНИЕ ДЛЯ ВАШЕЙ ГОСТИНОЙ
+
УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС
+
+
Подробнее
+
+
+
+
+
+
ГОТОВОЕ РЕШЕНИЕ ДЛЯ ВАШЕЙ СПАЛЬНИ
+
УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС
+
+
Подробнее
+
+
+
+
+
+
+
+
+
+
+
+
30 000+
+
Довольных покупателей
+
+
+
4500+
+
Реализованных заказов
+
+
+
+
+
+
+
+
ОТВЕТЫ НА ВОПРОСЫ
+
+
+
1
+
+
Сколько времени занимает доставка?
+
Доставка готовых позиций занимает 1-3 дня. Мебель на заказ изготавливается от 14 до 45 рабочих дней, в зависимости от сложности. Точные сроки озвучит ваш менеджер при оформлении заказа.
+
+
+
+
2
+
+
Нужно ли вносить предоплату?
+
Да, для запуска заказа в производство необходима предоплата в размере 50-70% от стоимости, в зависимости от изделия. Оставшаяся сумма оплачивается при доставке и приемке мебели.
+
+
+
+
3
+
+
Предоставляется ли рассрочка или кредит?
+
Да, мы сотрудничаем с несколькими банками и предлагаем рассрочку на 6 или 12 месяцев без первоначального взноса, а также кредит на более длительный срок. Все условия уточняйте у вашего менеджера.
+
+
+
+
4
+
+
Что делать, если мебель пришла с дефектом?
+
В этом случае необходимо в течение 7 дней со дня доставки сообщить нам о проблеме, прислать фото/видео дефекта. Мы оперативно решим вопрос о бесплатной замене или ремонте изделия.
+
+
+
+
Задать вопрос
+
+
+
+
+
+
+
+
+
+
+
+ Быстрый вход
+
+
+
+ Войти как Администратор
+
+
+ Войти как Пользователь
+
+
+
+ Для полного функционала перейдите на страницу входа
+
+
+
+
+
+
+
+
+
+
+
+
+
Быстрый вход:
+
+ Войти в аккаунт
+
+
+
+
+
\ No newline at end of file
diff --git a/config/database.php b/public/config/database.php
similarity index 81%
rename from config/database.php
rename to public/config/database.php
index d7e632b..5223e40 100644
--- a/config/database.php
+++ b/public/config/database.php
@@ -1,32 +1,31 @@
-connection = new PDO(
- "pgsql:host=localhost;dbname=aeterna_db;",
- "postgres",
- "1234"
- );
- $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- $this->connection->exec("SET NAMES 'utf8'");
- } catch(PDOException $e) {
- die("Ошибка подключения: " . $e->getMessage());
- }
- }
-
- public static function getInstance() {
- if (self::$instance == null) {
- self::$instance = new Database();
- }
- return self::$instance;
- }
-
- public function getConnection() {
- return $this->connection;
- }
-}
-?>
\ No newline at end of file
+connection = new PDO(
+ "pgsql:host=185.130.224.177;port=5481;dbname=postgres",
+ "admin",
+ "38feaad2840ccfda0e71243a6faaecfd"
+ );
+ $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $this->connection->exec("SET NAMES 'utf8'");
+ } catch(PDOException $e) {
+ die("Ошибка подключения: " . $e->getMessage());
+ }
+ }
+
+ public static function getInstance() {
+ if (self::$instance == null) {
+ self::$instance = new Database();
+ }
+ return self::$instance;
+ }
+
+ public function getConnection() {
+ return $this->connection;
+ }
+}
\ No newline at end of file
diff --git a/Доставка.php b/public/delivery.php
similarity index 90%
rename from Доставка.php
rename to public/delivery.php
index ba659c5..2db4c67 100644
--- a/Доставка.php
+++ b/public/delivery.php
@@ -1,174 +1,171 @@
-
-
-
-
-
- AETERNA - Доставка и оплата
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ДОСТАВКА И ОПЛАТА
-
-
-
-
-
-
-
Курьерская доставка
-
-
- Бесплатная доставка:
- при заказе от 30 000 ₽
-
-
- В пределах МКАД:
- 1 500 ₽
-
-
- За МКАД:
- 1 500 ₽ + 50 ₽/км
-
-
- Время доставки:
- с 9:00 до 21:00
-
-
-
-
-
-
-
-
-
Самовывоз из шоурума
-
-
- Адрес:
- г. Москва, ул. Дизайнерская, 15
-
-
- Стоимость:
- Бесплатно
-
-
- Время получения:
- в течение 2 часов после подтверждения
-
-
- Парковка:
- Бесплатная для клиентов
-
-
-
-
-
-
-
-
-
Доставка по России
-
-
- Стоимость:
- рассчитывается индивидуально
-
-
- Сроки:
- от 3 до 14 дней
-
-
- Транспортные компании:
- СДЭК, Boxberry, Деловые Линии
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ AETERNA - Доставка и оплата
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ДОСТАВКА И ОПЛАТА
+
+
+
+
+
+
+
Курьерская доставка
+
+
+ Бесплатная доставка:
+ при заказе от 30 000 ₽
+
+
+ В пределах МКАД:
+ 1 500 ₽
+
+
+ За МКАД:
+ 1 500 ₽ + 50 ₽/км
+
+
+ Время доставки:
+ с 9:00 до 21:00
+
+
+
+
+
+
+
+
+
Самовывоз из шоурума
+
+
+ Адрес:
+ г. Москва, ул. Дизайнерская, 15
+
+
+ Стоимость:
+ Бесплатно
+
+
+ Время получения:
+ в течение 2 часов после подтверждения
+
+
+ Парковка:
+ Бесплатная для клиентов
+
+
+
+
+
+
+
+
+
Доставка по России
+
+
+ Стоимость:
+ рассчитывается индивидуально
+
+
+ Сроки:
+ от 3 до 14 дней
+
+
+ Транспортные компании:
+ СДЭК, Boxberry, Деловые Линии
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/header_common.php b/public/header_common.php
new file mode 100644
index 0000000..9952ff0
--- /dev/null
+++ b/public/header_common.php
@@ -0,0 +1,161 @@
+
+
+
+
+
+
diff --git a/public/img/1 — копия.jpg b/public/img/1 — копия.jpg
new file mode 100644
index 0000000..68992d6
Binary files /dev/null and b/public/img/1 — копия.jpg differ
diff --git a/img2/1.jpg b/public/img/1.jpg
similarity index 100%
rename from img2/1.jpg
rename to public/img/1.jpg
diff --git a/public/img/100.jpg b/public/img/100.jpg
new file mode 100644
index 0000000..646480d
Binary files /dev/null and b/public/img/100.jpg differ
diff --git a/public/img/11.jpg b/public/img/11.jpg
new file mode 100644
index 0000000..3a8270e
Binary files /dev/null and b/public/img/11.jpg differ
diff --git a/public/img/111.jpg b/public/img/111.jpg
new file mode 100644
index 0000000..33dc453
Binary files /dev/null and b/public/img/111.jpg differ
diff --git a/public/img/11_1.png b/public/img/11_1.png
new file mode 100644
index 0000000..4735774
Binary files /dev/null and b/public/img/11_1.png differ
diff --git a/public/img/1_1.jpg b/public/img/1_1.jpg
new file mode 100644
index 0000000..3f9e1a1
Binary files /dev/null and b/public/img/1_1.jpg differ
diff --git a/public/img/1_2.jpg b/public/img/1_2.jpg
new file mode 100644
index 0000000..2d7a54a
Binary files /dev/null and b/public/img/1_2.jpg differ
diff --git a/public/img/1_2.png b/public/img/1_2.png
new file mode 100644
index 0000000..9a8a6c0
Binary files /dev/null and b/public/img/1_2.png differ
diff --git a/public/img/2.jpg b/public/img/2.jpg
new file mode 100644
index 0000000..417df4a
Binary files /dev/null and b/public/img/2.jpg differ
diff --git a/public/img/22.jpg b/public/img/22.jpg
new file mode 100644
index 0000000..f59b320
Binary files /dev/null and b/public/img/22.jpg differ
diff --git a/public/img/25.jpg b/public/img/25.jpg
new file mode 100644
index 0000000..03eddcf
Binary files /dev/null and b/public/img/25.jpg differ
diff --git a/public/img/2_2.jpg b/public/img/2_2.jpg
new file mode 100644
index 0000000..76ba9ba
Binary files /dev/null and b/public/img/2_2.jpg differ
diff --git a/public/img/2_2.png b/public/img/2_2.png
new file mode 100644
index 0000000..8fa6e8c
Binary files /dev/null and b/public/img/2_2.png differ
diff --git a/public/img/3.jpg b/public/img/3.jpg
new file mode 100644
index 0000000..973449d
Binary files /dev/null and b/public/img/3.jpg differ
diff --git a/public/img/3_3.jpg b/public/img/3_3.jpg
new file mode 100644
index 0000000..7e56605
Binary files /dev/null and b/public/img/3_3.jpg differ
diff --git a/public/img/3_3.png b/public/img/3_3.png
new file mode 100644
index 0000000..ba16e91
Binary files /dev/null and b/public/img/3_3.png differ
diff --git a/public/img/4.jpg b/public/img/4.jpg
new file mode 100644
index 0000000..da2945c
Binary files /dev/null and b/public/img/4.jpg differ
diff --git a/public/img/44.jpg b/public/img/44.jpg
new file mode 100644
index 0000000..e38c994
Binary files /dev/null and b/public/img/44.jpg differ
diff --git a/public/img/444 b/public/img/444
new file mode 100644
index 0000000..dc6e3fa
Binary files /dev/null and b/public/img/444 differ
diff --git a/public/img/444 (1).png b/public/img/444 (1).png
new file mode 100644
index 0000000..ddf7e92
Binary files /dev/null and b/public/img/444 (1).png differ
diff --git a/public/img/444.jpg b/public/img/444.jpg
new file mode 100644
index 0000000..dc6e3fa
Binary files /dev/null and b/public/img/444.jpg differ
diff --git a/public/img/444.png b/public/img/444.png
new file mode 100644
index 0000000..cd53804
Binary files /dev/null and b/public/img/444.png differ
diff --git a/public/img/4_1.jpg b/public/img/4_1.jpg
new file mode 100644
index 0000000..69f2236
Binary files /dev/null and b/public/img/4_1.jpg differ
diff --git a/img2/5.jpg b/public/img/5.jpg
similarity index 100%
rename from img2/5.jpg
rename to public/img/5.jpg
diff --git a/public/img/5_5.jpg b/public/img/5_5.jpg
new file mode 100644
index 0000000..9ced54f
Binary files /dev/null and b/public/img/5_5.jpg differ
diff --git a/public/img/5_5.png b/public/img/5_5.png
new file mode 100644
index 0000000..369b63b
Binary files /dev/null and b/public/img/5_5.png differ
diff --git a/public/img/6.jpg b/public/img/6.jpg
new file mode 100644
index 0000000..550e17d
Binary files /dev/null and b/public/img/6.jpg differ
diff --git a/public/img/6_6.jpg b/public/img/6_6.jpg
new file mode 100644
index 0000000..0aa5c4b
Binary files /dev/null and b/public/img/6_6.jpg differ
diff --git a/public/img/6_6.png b/public/img/6_6.png
new file mode 100644
index 0000000..af949f9
Binary files /dev/null and b/public/img/6_6.png differ
diff --git a/public/img/7.jpg b/public/img/7.jpg
new file mode 100644
index 0000000..17150c7
Binary files /dev/null and b/public/img/7.jpg differ
diff --git a/public/img/77.jpg b/public/img/77.jpg
new file mode 100644
index 0000000..d9bf304
Binary files /dev/null and b/public/img/77.jpg differ
diff --git a/public/img/777 (1).png b/public/img/777 (1).png
new file mode 100644
index 0000000..6b01d3f
Binary files /dev/null and b/public/img/777 (1).png differ
diff --git a/public/img/777.jpg b/public/img/777.jpg
new file mode 100644
index 0000000..908d164
Binary files /dev/null and b/public/img/777.jpg differ
diff --git a/public/img/777.png b/public/img/777.png
new file mode 100644
index 0000000..41d57c8
Binary files /dev/null and b/public/img/777.png differ
diff --git a/public/img/7_7.jpg b/public/img/7_7.jpg
new file mode 100644
index 0000000..5ee1524
Binary files /dev/null and b/public/img/7_7.jpg differ
diff --git a/public/img/7_7.png b/public/img/7_7.png
new file mode 100644
index 0000000..d50782e
Binary files /dev/null and b/public/img/7_7.png differ
diff --git a/public/img/8.jpg b/public/img/8.jpg
new file mode 100644
index 0000000..8b41c63
Binary files /dev/null and b/public/img/8.jpg differ
diff --git a/public/img/88.jpg b/public/img/88.jpg
new file mode 100644
index 0000000..713eeec
Binary files /dev/null and b/public/img/88.jpg differ
diff --git a/public/img/888 (1).png b/public/img/888 (1).png
new file mode 100644
index 0000000..127e80d
Binary files /dev/null and b/public/img/888 (1).png differ
diff --git a/public/img/888.jpg b/public/img/888.jpg
new file mode 100644
index 0000000..e8f33f7
Binary files /dev/null and b/public/img/888.jpg differ
diff --git a/public/img/888.png b/public/img/888.png
new file mode 100644
index 0000000..35e3403
Binary files /dev/null and b/public/img/888.png differ
diff --git a/public/img/8_8.png b/public/img/8_8.png
new file mode 100644
index 0000000..9f86b2c
Binary files /dev/null and b/public/img/8_8.png differ
diff --git a/public/img/9.jpg b/public/img/9.jpg
new file mode 100644
index 0000000..0b772e3
Binary files /dev/null and b/public/img/9.jpg differ
diff --git a/public/img/99.jpg b/public/img/99.jpg
new file mode 100644
index 0000000..ed309ba
Binary files /dev/null and b/public/img/99.jpg differ
diff --git a/public/img/99.png b/public/img/99.png
new file mode 100644
index 0000000..8fd29b2
Binary files /dev/null and b/public/img/99.png differ
diff --git a/public/img/99_1.jpg b/public/img/99_1.jpg
new file mode 100644
index 0000000..0247358
Binary files /dev/null and b/public/img/99_1.jpg differ
diff --git a/public/img/99_2.jpg b/public/img/99_2.jpg
new file mode 100644
index 0000000..468fd51
Binary files /dev/null and b/public/img/99_2.jpg differ
diff --git a/public/img/99_3.png b/public/img/99_3.png
new file mode 100644
index 0000000..ae2cd8e
Binary files /dev/null and b/public/img/99_3.png differ
diff --git a/img2/9_9.jpg b/public/img/9_9.jpg
similarity index 100%
rename from img2/9_9.jpg
rename to public/img/9_9.jpg
diff --git a/public/img/9_9.png b/public/img/9_9.png
new file mode 100644
index 0000000..733a453
Binary files /dev/null and b/public/img/9_9.png differ
diff --git a/public/img/black.png b/public/img/black.png
new file mode 100644
index 0000000..a10af51
Binary files /dev/null and b/public/img/black.png differ
diff --git a/public/img/black1.png b/public/img/black1.png
new file mode 100644
index 0000000..f529bd3
Binary files /dev/null and b/public/img/black1.png differ
diff --git a/public/img/black2.png b/public/img/black2.png
new file mode 100644
index 0000000..75758a2
Binary files /dev/null and b/public/img/black2.png differ
diff --git a/public/img/brown.png b/public/img/brown.png
new file mode 100644
index 0000000..9ef4db4
Binary files /dev/null and b/public/img/brown.png differ
diff --git a/public/img/brown1.png b/public/img/brown1.png
new file mode 100644
index 0000000..0b178f1
Binary files /dev/null and b/public/img/brown1.png differ
diff --git a/public/img/brown2.png b/public/img/brown2.png
new file mode 100644
index 0000000..346b22f
Binary files /dev/null and b/public/img/brown2.png differ
diff --git a/img2/chair.PNG b/public/img/chair.PNG
similarity index 100%
rename from img2/chair.PNG
rename to public/img/chair.PNG
diff --git a/public/img/gray.png b/public/img/gray.png
new file mode 100644
index 0000000..55ab5bb
Binary files /dev/null and b/public/img/gray.png differ
diff --git a/public/img/gray1.png b/public/img/gray1.png
new file mode 100644
index 0000000..3137e25
Binary files /dev/null and b/public/img/gray1.png differ
diff --git a/public/img/gray2.png b/public/img/gray2.png
new file mode 100644
index 0000000..95b8781
Binary files /dev/null and b/public/img/gray2.png differ
diff --git a/img2/диван.jpg b/public/img/диван.jpg
similarity index 100%
rename from img2/диван.jpg
rename to public/img/диван.jpg
diff --git a/img2/диван_1.jpg b/public/img/диван_1.jpg
similarity index 100%
rename from img2/диван_1.jpg
rename to public/img/диван_1.jpg
diff --git a/img2/кресло.jpg b/public/img/кресло.jpg
similarity index 100%
rename from img2/кресло.jpg
rename to public/img/кресло.jpg
diff --git a/img2/кресло_1.jpg b/public/img/кресло_1.jpg
similarity index 100%
rename from img2/кресло_1.jpg
rename to public/img/кресло_1.jpg
diff --git a/img2/слайдер_1.jpg b/public/img/слайдер_1.jpg
similarity index 100%
rename from img2/слайдер_1.jpg
rename to public/img/слайдер_1.jpg
diff --git a/img2/слайдер_2.jpg b/public/img/слайдер_2.jpg
similarity index 100%
rename from img2/слайдер_2.jpg
rename to public/img/слайдер_2.jpg
diff --git a/img2/слайдер_3.jpg b/public/img/слайдер_3.jpg
similarity index 100%
rename from img2/слайдер_3.jpg
rename to public/img/слайдер_3.jpg
diff --git a/img2/слайдер_4.jpg b/public/img/слайдер_4.jpg
similarity index 100%
rename from img2/слайдер_4.jpg
rename to public/img/слайдер_4.jpg
diff --git a/img2/слайдер_5.jpg b/public/img/слайдер_5.jpg
similarity index 100%
rename from img2/слайдер_5.jpg
rename to public/img/слайдер_5.jpg
diff --git a/img2/слайдер_6.jpg b/public/img/слайдер_6.jpg
similarity index 100%
rename from img2/слайдер_6.jpg
rename to public/img/слайдер_6.jpg
diff --git a/img2/спальня.jpg b/public/img/спальня.jpg
similarity index 100%
rename from img2/спальня.jpg
rename to public/img/спальня.jpg
diff --git a/public/img/стили_оформления.css b/public/img/стили_оформления.css
new file mode 100644
index 0000000..2e1d09c
--- /dev/null
+++ b/public/img/стили_оформления.css
@@ -0,0 +1,61 @@
+
+.error-message {
+ color: #ff0000;
+ font-size: 12px;
+ margin-top: 5px;
+ display: none;
+}
+
+.form__input.error {
+ border-color: #ff0000;
+}
+
+.form__group {
+ position: relative;
+ margin-bottom: 15px;
+}
+
+.page-messages {
+ position: fixed;
+ bottom: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 1000;
+ width: 90%;
+ max-width: 500px;
+}
+
+.message {
+ padding: 15px;
+ margin: 10px 0;
+ border-radius: 5px;
+ text-align: center;
+ font-weight: bold;
+ display: none;
+}
+
+.message.error {
+ background-color: #ffebee;
+ color: #c62828;
+ border: 1px solid #ffcdd2;
+}
+
+.message.success {
+ background-color: #e8f5e9;
+ color: #453227;
+ border: 1px solid #c8e6c9;
+}
+
+.message.warning {
+ background-color: #fff3e0;
+ color: #ef6c00;
+ border: 1px solid #ffe0b2;
+}
+
+.privacy-error {
+ color: #ff0000;
+ font-size: 12px;
+ margin-top: 5px;
+ display: none;
+ text-align: center;
+}
diff --git a/public/img1 b/public/img1
new file mode 120000
index 0000000..cf2d769
--- /dev/null
+++ b/public/img1
@@ -0,0 +1 @@
+img
\ No newline at end of file
diff --git a/public/img2/1 — копия.jpg b/public/img2/1 — копия.jpg
new file mode 100644
index 0000000..68992d6
Binary files /dev/null and b/public/img2/1 — копия.jpg differ
diff --git a/public/img2/1.jpg b/public/img2/1.jpg
new file mode 100644
index 0000000..1df7668
Binary files /dev/null and b/public/img2/1.jpg differ
diff --git a/public/img2/100.jpg b/public/img2/100.jpg
new file mode 100644
index 0000000..646480d
Binary files /dev/null and b/public/img2/100.jpg differ
diff --git a/public/img2/11.jpg b/public/img2/11.jpg
new file mode 100644
index 0000000..3a8270e
Binary files /dev/null and b/public/img2/11.jpg differ
diff --git a/public/img2/111.jpg b/public/img2/111.jpg
new file mode 100644
index 0000000..33dc453
Binary files /dev/null and b/public/img2/111.jpg differ
diff --git a/public/img2/11_1.png b/public/img2/11_1.png
new file mode 100644
index 0000000..4735774
Binary files /dev/null and b/public/img2/11_1.png differ
diff --git a/public/img2/1_1.jpg b/public/img2/1_1.jpg
new file mode 100644
index 0000000..3f9e1a1
Binary files /dev/null and b/public/img2/1_1.jpg differ
diff --git a/public/img2/1_2.jpg b/public/img2/1_2.jpg
new file mode 100644
index 0000000..2d7a54a
Binary files /dev/null and b/public/img2/1_2.jpg differ
diff --git a/public/img2/1_2.png b/public/img2/1_2.png
new file mode 100644
index 0000000..9a8a6c0
Binary files /dev/null and b/public/img2/1_2.png differ
diff --git a/public/img2/2.jpg b/public/img2/2.jpg
new file mode 100644
index 0000000..417df4a
Binary files /dev/null and b/public/img2/2.jpg differ
diff --git a/public/img2/22.jpg b/public/img2/22.jpg
new file mode 100644
index 0000000..f59b320
Binary files /dev/null and b/public/img2/22.jpg differ
diff --git a/public/img2/25.jpg b/public/img2/25.jpg
new file mode 100644
index 0000000..03eddcf
Binary files /dev/null and b/public/img2/25.jpg differ
diff --git a/public/img2/2_2.jpg b/public/img2/2_2.jpg
new file mode 100644
index 0000000..76ba9ba
Binary files /dev/null and b/public/img2/2_2.jpg differ
diff --git a/public/img2/2_2.png b/public/img2/2_2.png
new file mode 100644
index 0000000..8fa6e8c
Binary files /dev/null and b/public/img2/2_2.png differ
diff --git a/public/img2/3.jpg b/public/img2/3.jpg
new file mode 100644
index 0000000..973449d
Binary files /dev/null and b/public/img2/3.jpg differ
diff --git a/public/img2/3_3.jpg b/public/img2/3_3.jpg
new file mode 100644
index 0000000..7e56605
Binary files /dev/null and b/public/img2/3_3.jpg differ
diff --git a/public/img2/3_3.png b/public/img2/3_3.png
new file mode 100644
index 0000000..ba16e91
Binary files /dev/null and b/public/img2/3_3.png differ
diff --git a/public/img2/4.jpg b/public/img2/4.jpg
new file mode 100644
index 0000000..da2945c
Binary files /dev/null and b/public/img2/4.jpg differ
diff --git a/public/img2/44.jpg b/public/img2/44.jpg
new file mode 100644
index 0000000..e38c994
Binary files /dev/null and b/public/img2/44.jpg differ
diff --git a/public/img2/444 b/public/img2/444
new file mode 100644
index 0000000..dc6e3fa
Binary files /dev/null and b/public/img2/444 differ
diff --git a/public/img2/444 (1).png b/public/img2/444 (1).png
new file mode 100644
index 0000000..ddf7e92
Binary files /dev/null and b/public/img2/444 (1).png differ
diff --git a/public/img2/444.jpg b/public/img2/444.jpg
new file mode 100644
index 0000000..dc6e3fa
Binary files /dev/null and b/public/img2/444.jpg differ
diff --git a/public/img2/444.png b/public/img2/444.png
new file mode 100644
index 0000000..cd53804
Binary files /dev/null and b/public/img2/444.png differ
diff --git a/public/img2/4_1.jpg b/public/img2/4_1.jpg
new file mode 100644
index 0000000..69f2236
Binary files /dev/null and b/public/img2/4_1.jpg differ
diff --git a/public/img2/5.jpg b/public/img2/5.jpg
new file mode 100644
index 0000000..456cec1
Binary files /dev/null and b/public/img2/5.jpg differ
diff --git a/public/img2/5_5.jpg b/public/img2/5_5.jpg
new file mode 100644
index 0000000..9ced54f
Binary files /dev/null and b/public/img2/5_5.jpg differ
diff --git a/public/img2/5_5.png b/public/img2/5_5.png
new file mode 100644
index 0000000..369b63b
Binary files /dev/null and b/public/img2/5_5.png differ
diff --git a/public/img2/6.jpg b/public/img2/6.jpg
new file mode 100644
index 0000000..550e17d
Binary files /dev/null and b/public/img2/6.jpg differ
diff --git a/public/img2/6_6.jpg b/public/img2/6_6.jpg
new file mode 100644
index 0000000..0aa5c4b
Binary files /dev/null and b/public/img2/6_6.jpg differ
diff --git a/public/img2/6_6.png b/public/img2/6_6.png
new file mode 100644
index 0000000..af949f9
Binary files /dev/null and b/public/img2/6_6.png differ
diff --git a/public/img2/7.jpg b/public/img2/7.jpg
new file mode 100644
index 0000000..17150c7
Binary files /dev/null and b/public/img2/7.jpg differ
diff --git a/public/img2/77.jpg b/public/img2/77.jpg
new file mode 100644
index 0000000..d9bf304
Binary files /dev/null and b/public/img2/77.jpg differ
diff --git a/public/img2/777 (1).png b/public/img2/777 (1).png
new file mode 100644
index 0000000..6b01d3f
Binary files /dev/null and b/public/img2/777 (1).png differ
diff --git a/public/img2/777.jpg b/public/img2/777.jpg
new file mode 100644
index 0000000..908d164
Binary files /dev/null and b/public/img2/777.jpg differ
diff --git a/public/img2/777.png b/public/img2/777.png
new file mode 100644
index 0000000..41d57c8
Binary files /dev/null and b/public/img2/777.png differ
diff --git a/public/img2/7_7.jpg b/public/img2/7_7.jpg
new file mode 100644
index 0000000..5ee1524
Binary files /dev/null and b/public/img2/7_7.jpg differ
diff --git a/public/img2/7_7.png b/public/img2/7_7.png
new file mode 100644
index 0000000..d50782e
Binary files /dev/null and b/public/img2/7_7.png differ
diff --git a/public/img2/8.jpg b/public/img2/8.jpg
new file mode 100644
index 0000000..8b41c63
Binary files /dev/null and b/public/img2/8.jpg differ
diff --git a/public/img2/88.jpg b/public/img2/88.jpg
new file mode 100644
index 0000000..713eeec
Binary files /dev/null and b/public/img2/88.jpg differ
diff --git a/public/img2/888 (1).png b/public/img2/888 (1).png
new file mode 100644
index 0000000..127e80d
Binary files /dev/null and b/public/img2/888 (1).png differ
diff --git a/public/img2/888.jpg b/public/img2/888.jpg
new file mode 100644
index 0000000..e8f33f7
Binary files /dev/null and b/public/img2/888.jpg differ
diff --git a/public/img2/888.png b/public/img2/888.png
new file mode 100644
index 0000000..35e3403
Binary files /dev/null and b/public/img2/888.png differ
diff --git a/public/img2/8_8.png b/public/img2/8_8.png
new file mode 100644
index 0000000..9f86b2c
Binary files /dev/null and b/public/img2/8_8.png differ
diff --git a/public/img2/9.jpg b/public/img2/9.jpg
new file mode 100644
index 0000000..0b772e3
Binary files /dev/null and b/public/img2/9.jpg differ
diff --git a/public/img2/99.jpg b/public/img2/99.jpg
new file mode 100644
index 0000000..ed309ba
Binary files /dev/null and b/public/img2/99.jpg differ
diff --git a/public/img2/99.png b/public/img2/99.png
new file mode 100644
index 0000000..8fd29b2
Binary files /dev/null and b/public/img2/99.png differ
diff --git a/public/img2/99_1.jpg b/public/img2/99_1.jpg
new file mode 100644
index 0000000..0247358
Binary files /dev/null and b/public/img2/99_1.jpg differ
diff --git a/public/img2/99_2.jpg b/public/img2/99_2.jpg
new file mode 100644
index 0000000..468fd51
Binary files /dev/null and b/public/img2/99_2.jpg differ
diff --git a/public/img2/99_3.png b/public/img2/99_3.png
new file mode 100644
index 0000000..ae2cd8e
Binary files /dev/null and b/public/img2/99_3.png differ
diff --git a/public/img2/9_9.jpg b/public/img2/9_9.jpg
new file mode 100644
index 0000000..a77f2ae
Binary files /dev/null and b/public/img2/9_9.jpg differ
diff --git a/public/img2/9_9.png b/public/img2/9_9.png
new file mode 100644
index 0000000..733a453
Binary files /dev/null and b/public/img2/9_9.png differ
diff --git a/public/img2/black.png b/public/img2/black.png
new file mode 100644
index 0000000..a10af51
Binary files /dev/null and b/public/img2/black.png differ
diff --git a/public/img2/black1.png b/public/img2/black1.png
new file mode 100644
index 0000000..f529bd3
Binary files /dev/null and b/public/img2/black1.png differ
diff --git a/public/img2/black2.png b/public/img2/black2.png
new file mode 100644
index 0000000..75758a2
Binary files /dev/null and b/public/img2/black2.png differ
diff --git a/public/img2/brown.png b/public/img2/brown.png
new file mode 100644
index 0000000..9ef4db4
Binary files /dev/null and b/public/img2/brown.png differ
diff --git a/public/img2/brown1.png b/public/img2/brown1.png
new file mode 100644
index 0000000..0b178f1
Binary files /dev/null and b/public/img2/brown1.png differ
diff --git a/public/img2/brown2.png b/public/img2/brown2.png
new file mode 100644
index 0000000..346b22f
Binary files /dev/null and b/public/img2/brown2.png differ
diff --git a/public/img2/chair.PNG b/public/img2/chair.PNG
new file mode 100644
index 0000000..12af9db
Binary files /dev/null and b/public/img2/chair.PNG differ
diff --git a/public/img2/gray.png b/public/img2/gray.png
new file mode 100644
index 0000000..55ab5bb
Binary files /dev/null and b/public/img2/gray.png differ
diff --git a/public/img2/gray1.png b/public/img2/gray1.png
new file mode 100644
index 0000000..3137e25
Binary files /dev/null and b/public/img2/gray1.png differ
diff --git a/public/img2/gray2.png b/public/img2/gray2.png
new file mode 100644
index 0000000..95b8781
Binary files /dev/null and b/public/img2/gray2.png differ
diff --git a/public/img2/диван.jpg b/public/img2/диван.jpg
new file mode 100644
index 0000000..578d1a3
Binary files /dev/null and b/public/img2/диван.jpg differ
diff --git a/public/img2/диван_1.jpg b/public/img2/диван_1.jpg
new file mode 100644
index 0000000..3618723
Binary files /dev/null and b/public/img2/диван_1.jpg differ
diff --git a/public/img2/кресло.jpg b/public/img2/кресло.jpg
new file mode 100644
index 0000000..6d1c12b
Binary files /dev/null and b/public/img2/кресло.jpg differ
diff --git a/public/img2/кресло_1.jpg b/public/img2/кресло_1.jpg
new file mode 100644
index 0000000..e59b3b1
Binary files /dev/null and b/public/img2/кресло_1.jpg differ
diff --git a/public/img2/слайдер_1.jpg b/public/img2/слайдер_1.jpg
new file mode 100644
index 0000000..255440e
Binary files /dev/null and b/public/img2/слайдер_1.jpg differ
diff --git a/public/img2/слайдер_2.jpg b/public/img2/слайдер_2.jpg
new file mode 100644
index 0000000..9cb6403
Binary files /dev/null and b/public/img2/слайдер_2.jpg differ
diff --git a/public/img2/слайдер_3.jpg b/public/img2/слайдер_3.jpg
new file mode 100644
index 0000000..dc65d0b
Binary files /dev/null and b/public/img2/слайдер_3.jpg differ
diff --git a/public/img2/слайдер_4.jpg b/public/img2/слайдер_4.jpg
new file mode 100644
index 0000000..f6d8d38
Binary files /dev/null and b/public/img2/слайдер_4.jpg differ
diff --git a/public/img2/слайдер_5.jpg b/public/img2/слайдер_5.jpg
new file mode 100644
index 0000000..a9cfbb6
Binary files /dev/null and b/public/img2/слайдер_5.jpg differ
diff --git a/public/img2/слайдер_6.jpg b/public/img2/слайдер_6.jpg
new file mode 100644
index 0000000..0dc28a0
Binary files /dev/null and b/public/img2/слайдер_6.jpg differ
diff --git a/public/img2/спальня.jpg b/public/img2/спальня.jpg
new file mode 100644
index 0000000..f403324
Binary files /dev/null and b/public/img2/спальня.jpg differ
diff --git a/public/img2/стили_оформления.css b/public/img2/стили_оформления.css
new file mode 100644
index 0000000..2e1d09c
--- /dev/null
+++ b/public/img2/стили_оформления.css
@@ -0,0 +1,61 @@
+
+.error-message {
+ color: #ff0000;
+ font-size: 12px;
+ margin-top: 5px;
+ display: none;
+}
+
+.form__input.error {
+ border-color: #ff0000;
+}
+
+.form__group {
+ position: relative;
+ margin-bottom: 15px;
+}
+
+.page-messages {
+ position: fixed;
+ bottom: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 1000;
+ width: 90%;
+ max-width: 500px;
+}
+
+.message {
+ padding: 15px;
+ margin: 10px 0;
+ border-radius: 5px;
+ text-align: center;
+ font-weight: bold;
+ display: none;
+}
+
+.message.error {
+ background-color: #ffebee;
+ color: #c62828;
+ border: 1px solid #ffcdd2;
+}
+
+.message.success {
+ background-color: #e8f5e9;
+ color: #453227;
+ border: 1px solid #c8e6c9;
+}
+
+.message.warning {
+ background-color: #fff3e0;
+ color: #ef6c00;
+ border: 1px solid #ffe0b2;
+}
+
+.privacy-error {
+ color: #ff0000;
+ font-size: 12px;
+ margin-top: 5px;
+ display: none;
+ text-align: center;
+}
diff --git a/public/includes/auth.php b/public/includes/auth.php
new file mode 100644
index 0000000..5326642
--- /dev/null
+++ b/public/includes/auth.php
@@ -0,0 +1,127 @@
+getConnection();
+
+ try {
+ $stmt = $db->prepare("
+ SELECT user_id, email, password_hash, full_name, phone, city, is_admin, is_active
+ FROM users WHERE email = ?
+ ");
+ $stmt->execute([$email]);
+ $user = $stmt->fetch();
+
+ if (!$user) {
+ return ['success' => false, 'message' => 'Пользователь не найден'];
+ }
+
+ if (!$user['is_active']) {
+ return ['success' => false, 'message' => 'Аккаунт заблокирован'];
+ }
+
+ if (!password_verify($password, $user['password_hash'])) {
+ return ['success' => false, 'message' => 'Неверный пароль'];
+ }
+
+ $_SESSION['user_id'] = $user['user_id'];
+ $_SESSION['user_email'] = $user['email'];
+ $_SESSION['full_name'] = $user['full_name'];
+ $_SESSION['user_phone'] = $user['phone'] ?? '';
+ $_SESSION['user_city'] = $user['city'] ?? '';
+ $_SESSION['isLoggedIn'] = true;
+ $_SESSION['isAdmin'] = (bool)$user['is_admin'];
+ $_SESSION['login_time'] = time();
+
+ $updateStmt = $db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE user_id = ?");
+ $updateStmt->execute([$user['user_id']]);
+
+ return ['success' => true, 'user' => $user];
+
+ } catch (PDOException $e) {
+ return ['success' => false, 'message' => 'Ошибка базы данных'];
+ }
+}
+
+function registerUser(array $data): array {
+ $db = Database::getInstance()->getConnection();
+
+ $email = trim($data['email'] ?? '');
+ $password = $data['password'] ?? '';
+ $fullName = trim($data['full_name'] ?? '');
+ $phone = trim($data['phone'] ?? '');
+ $city = trim($data['city'] ?? '');
+
+ if (empty($email) || empty($password) || empty($fullName)) {
+ return ['success' => false, 'message' => 'Заполните все обязательные поля'];
+ }
+
+ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+ return ['success' => false, 'message' => 'Некорректный email'];
+ }
+
+ if (strlen($password) < 6) {
+ return ['success' => false, 'message' => 'Пароль должен содержать минимум 6 символов'];
+ }
+
+ try {
+
+ $checkStmt = $db->prepare("SELECT user_id FROM users WHERE email = ?");
+ $checkStmt->execute([$email]);
+
+ if ($checkStmt->fetch()) {
+ return ['success' => false, 'message' => 'Пользователь с таким email уже существует'];
+ }
+
+ $passwordHash = password_hash($password, PASSWORD_DEFAULT);
+
+ $stmt = $db->prepare("
+ INSERT INTO users (email, password_hash, full_name, phone, city, is_active)
+ VALUES (?, ?, ?, ?, ?, TRUE)
+ RETURNING user_id
+ ");
+ $stmt->execute([$email, $passwordHash, $fullName, $phone, $city]);
+ $userId = $stmt->fetchColumn();
+
+ $_SESSION['user_id'] = $userId;
+ $_SESSION['user_email'] = $email;
+ $_SESSION['full_name'] = $fullName;
+ $_SESSION['user_phone'] = $phone;
+ $_SESSION['user_city'] = $city;
+ $_SESSION['isLoggedIn'] = true;
+ $_SESSION['isAdmin'] = false;
+ $_SESSION['login_time'] = time();
+
+ return ['success' => true, 'user_id' => $userId];
+
+ } catch (PDOException $e) {
+ return ['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()];
+ }
+}
+
+function logoutUser(): void {
+ $_SESSION = [];
+
+ if (ini_get("session.use_cookies")) {
+ $params = session_get_cookie_params();
+ setcookie(session_name(), '', time() - 42000,
+ $params["path"], $params["domain"],
+ $params["secure"], $params["httponly"]
+ );
+ }
+
+ session_destroy();
+}
+
+function checkAdminAccess(): bool {
+ if (!isset($_SESSION['isLoggedIn']) || $_SESSION['isLoggedIn'] !== true) {
+ return false;
+ }
+
+ if (!isset($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/public/index.php b/public/index.php
new file mode 100644
index 0000000..308ddc7
--- /dev/null
+++ b/public/index.php
@@ -0,0 +1,760 @@
+
+
+
+
+
+
+ AETERNA - Мебель и Интерьер
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ДОБАВЬТЕ ИЗЫСКАННОСТИ В СВОЙ ИНТЕРЬЕР
+
Мы создаем мебель, которая сочетает в себе безупречный дизайн, натуральные материалы, продуманный функционал, чтобы ваш день начинался и заканчивался с комфортом.
+
+
+
ПЕРЕЙТИ В КАТАЛОГ
+
+
ПЕРЕЙТИ В КАТАЛОГ
+
+
+
+
+
+
+
+
+
+
+
+
О НАС
+
Компания AETERNA - российский производитель качественной корпусной и мягкой мебели для дома и офиса. С 2015 года мы успешно реализуем проекты любой сложности, сочетая современные технологии, проверенные материалы и классическое мастерство.
+
+
+
+
+
+
+
Наша сеть включает 30+ российских фабрик, отобранных по строгим стандартам качества. Мы сотрудничаем исключительно с лидерами рынка, чья продукция доказала свое превосходство временем.
+
+
+
+
+
+
+
+
+
+
+
+
ГОТОВОЕ РЕШЕНИЕ ДЛЯ ВАШЕЙ ГОСТИНОЙ
+
УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС
+
+
Подробнее
+
+
+
+
+
+
ГОТОВОЕ РЕШЕНИЕ ДЛЯ ВАШЕЙ СПАЛЬНИ
+
УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС
+
+
Подробнее
+
+
+
+
+
+
+
+
+
+
+
+
30 000+
+
Довольных покупателей
+
+
+
4500+
+
Реализованных заказов
+
+
+
+
+
+
+
+
ОТВЕТЫ НА ВОПРОСЫ
+
+
+
1
+
+
Сколько времени занимает доставка?
+
Доставка готовых позиций занимает 1-3 дня. Мебель на заказ изготавливается от 14 до 45 рабочих дней, в зависимости от сложности. Точные сроки озвучит ваш менеджер при оформлении заказа.
+
+
+
+
2
+
+
Нужно ли вносить предоплату?
+
Да, для запуска заказа в производство необходима предоплата в размере 50-70% от стоимости, в зависимости от изделия. Оставшаяся сумма оплачивается при доставке и приемке мебели.
+
+
+
+
3
+
+
Предоставляется ли рассрочка или кредит?
+
Да, мы сотрудничаем с несколькими банками и предлагаем рассрочку на 6 или 12 месяцев без первоначального взноса, а также кредит на более длительный срок. Все условия уточняйте у вашего менеджера.
+
+
+
+
4
+
+
Что делать, если мебель пришла с дефектом?
+
В этом случае необходимо в течение 7 дней со дня доставки сообщить нам о проблеме, прислать фото/видео дефекта. Мы оперативно решим вопрос о бесплатной замене или ремонте изделия.
+
+
+
+
Задать вопрос
+
+
+
+
+
+
+
+
+
+
+
+ Быстрый вход
+
+
+
+ Войти как Администратор
+
+
+ Войти как Пользователь
+
+
+
+ Для полного функционала перейдите на страницу входа
+
+
+
+
+
+
+
+
+
+
+
+
+
Быстрый вход:
+
+ Войти в аккаунт
+
+
+
+
+
+
\ No newline at end of file
diff --git a/вход.php b/public/login.php
similarity index 73%
rename from вход.php
rename to public/login.php
index bb358b8..fb62884 100644
--- a/вход.php
+++ b/public/login.php
@@ -1,305 +1,295 @@
-
-
-
-
-
- AETERNA - Вход
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-// В файле вход.html добавьте:
-
-
-
-
+
+
+
+
+
+
+ AETERNA - Вход
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/logout.php b/public/logout.php
new file mode 100644
index 0000000..02ef1c7
--- /dev/null
+++ b/public/logout.php
@@ -0,0 +1,17 @@
+getConnection();
-
-try {
- // Получаем информацию о товаре
- $productStmt = $db->prepare("
- SELECT
- p.*,
- c.name as category_name,
- c.slug as category_slug
- FROM products p
- LEFT JOIN categories c ON p.category_id = c.category_id
- WHERE p.product_id = ? AND p.is_available = TRUE
- ");
- $productStmt->execute([$product_id]);
- $product = $productStmt->fetch();
-
- if (!$product) {
- header('Location: catalog.php?error=product_not_found');
- exit();
- }
-
- // Получаем похожие товары
- $similarStmt = $db->prepare("
- SELECT * FROM products
- WHERE category_id = ?
- AND product_id != ?
- AND is_available = TRUE
- ORDER BY RANDOM()
- LIMIT 3
- ");
- $similarStmt->execute([$product['category_id'], $product_id]);
- $similarProducts = $similarStmt->fetchAll();
-
- // Получаем отзывы (если есть отдельная таблица reviews)
- $reviewsStmt = $db->prepare("
- SELECT rating, comment, created_at
- FROM reviews
- WHERE product_id = ?
- ORDER BY created_at DESC
- LIMIT 5
- ");
- $reviewsStmt->execute([$product_id]);
- $reviews = $reviewsStmt->fetchAll();
-
-} catch (PDOException $e) {
- die("Ошибка базы данных: " . $e->getMessage());
-}
-
-// HTML код страницы товара
-?>
-
-
-
-
-
- AETERNA - = htmlspecialchars($product['name']) ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
= htmlspecialchars($product['name']) ?>
-
-
-
- = 0.5;
-
- for ($i = 1; $i <= 5; $i++) {
- if ($i <= $fullStars) {
- echo '★ ';
- } elseif ($i == $fullStars + 1 && $hasHalfStar) {
- echo '★ ';
- } else {
- echo '☆ ';
- }
- }
- ?>
-
-
= number_format($rating, 1) ?>
-
(= $product['review_count'] ?> отзывов)
-
-
-
-
- = number_format($product['price'], 0, '', ' ') ?> ₽
-
- $product['price']): ?>
-
- = number_format($product['old_price'], 0, '', ' ') ?> ₽
-
-
- -= round(($product['old_price'] - $product['price']) / $product['old_price'] * 100) ?>%
-
-
-
-
-
- 10) {
- echo ' В наличии';
- } elseif ($product['stock_quantity'] > 0) {
- echo ' Осталось мало: ' . $product['stock_quantity'] . ' шт.';
- } else {
- echo ' Нет в наличии';
- }
- ?>
-
-
-
-
- Артикул:
- = $product['sku'] ?? 'N/A' ?>
-
-
- Категория:
- = htmlspecialchars($product['category_name'] ?? 'Без категории') ?>
-
-
- На складе:
- = $product['stock_quantity'] ?> шт.
-
-
-
-
- = nl2br(htmlspecialchars($product['description'] ?? 'Описание отсутствует')) ?>
-
-
- 0): ?>
-
-
- -
-
- +
-
-
-
-
- В корзину
-
-
- Купить сейчас
-
-
-
-
-
-
- Уведомить о поступлении
-
-
-
-
-
-
-
-
-
-
-
-
- Похожие товары
-
-
-
-
-
-
-
-
= htmlspecialchars($similar['name']) ?>
-
- = number_format($similar['price'], 0, '', ' ') ?> ₽
-
-
- Подробнее
-
-
-
-
-
-
-
-
-
-
-
-
-
+getConnection();
+
+try {
+
+ $productStmt = $db->prepare("
+ SELECT
+ p.*,
+ c.name as category_name,
+ c.slug as category_slug
+ FROM products p
+ LEFT JOIN categories c ON p.category_id = c.category_id
+ WHERE p.product_id = ? AND p.is_available = TRUE
+ ");
+ $productStmt->execute([$product_id]);
+ $product = $productStmt->fetch();
+
+ if (!$product) {
+ header('Location: catalog.php?error=product_not_found');
+ exit();
+ }
+
+ $similarStmt = $db->prepare("
+ SELECT * FROM products
+ WHERE category_id = ?
+ AND product_id != ?
+ AND is_available = TRUE
+ ORDER BY RANDOM()
+ LIMIT 3
+ ");
+ $similarStmt->execute([$product['category_id'], $product_id]);
+ $similarProducts = $similarStmt->fetchAll();
+
+ $reviews = [];
+
+} catch (PDOException $e) {
+ die("Ошибка базы данных: " . $e->getMessage());
+}
+
+?>
+
+
+
+
+
+
+ AETERNA - = htmlspecialchars($product['name']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
= htmlspecialchars($product['name']) ?>
+
+
+
+ = 0.5;
+
+ for ($i = 1; $i <= 5; $i++) {
+ if ($i <= $fullStars) {
+ echo '★ ';
+ } elseif ($i == $fullStars + 1 && $hasHalfStar) {
+ echo '★ ';
+ } else {
+ echo '☆ ';
+ }
+ }
+ ?>
+
+
= number_format($rating, 1) ?>
+
(= $product['review_count'] ?> отзывов)
+
+
+
+
+ = number_format($product['price'], 0, '', ' ') ?> ₽
+
+ $product['price']): ?>
+
+ = number_format($product['old_price'], 0, '', ' ') ?> ₽
+
+
+ -= round(($product['old_price'] - $product['price']) / $product['old_price'] * 100) ?>%
+
+
+
+
+
+ 10) {
+ echo ' В наличии';
+ } elseif ($product['stock_quantity'] > 0) {
+ echo ' Осталось мало: ' . $product['stock_quantity'] . ' шт.';
+ } else {
+ echo ' Нет в наличии';
+ }
+ ?>
+
+
+
+
+ Артикул:
+ = $product['sku'] ?? 'N/A' ?>
+
+
+ Категория:
+ = htmlspecialchars($product['category_name'] ?? 'Без категории') ?>
+
+
+ На складе:
+ = $product['stock_quantity'] ?> шт.
+
+
+
+
+ = nl2br(htmlspecialchars($product['description'] ?? 'Описание отсутствует')) ?>
+
+
+ 0): ?>
+
+
+ -
+
+ +
+
+
+
+
+ В корзину
+
+
+ Купить сейчас
+
+
+
+
+
+
+ Уведомить о поступлении
+
+
+
+
+
+
+
+
+
+
+
+
+ Похожие товары
+
+
+
+
+
+
+
+
= htmlspecialchars($similar['name']) ?>
+
+ = number_format($similar['price'], 0, '', ' ') ?> ₽
+
+
+ Подробнее
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/profile.php b/public/profile.php
new file mode 100644
index 0000000..065f18c
--- /dev/null
+++ b/public/profile.php
@@ -0,0 +1,261 @@
+getConnection();
+
+$user = null;
+try {
+ $stmt = $db->prepare("SELECT user_id, email, full_name, phone, city FROM users WHERE user_id = ?");
+ $stmt->execute([$user_id]);
+ $user = $stmt->fetch(PDO::FETCH_ASSOC);
+} catch (PDOException $e) {
+ $error = "Ошибка загрузки данных: " . $e->getMessage();
+}
+
+if (!$user) {
+ header('Location: login.php?error=user_not_found');
+ exit();
+}
+
+$update_errors = $_SESSION['update_errors'] ?? [];
+$update_success = $_SESSION['update_success'] ?? '';
+unset($_SESSION['update_errors']);
+unset($_SESSION['update_success']);
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $full_name = trim($_POST['fio'] ?? '');
+ $city = trim($_POST['city'] ?? '');
+ $phone = trim($_POST['phone'] ?? '');
+ $password = $_POST['password'] ?? '';
+ $confirm_password = $_POST['confirm-password'] ?? '';
+
+ $errors = [];
+
+ if (empty($full_name) || strlen($full_name) < 3) {
+ $errors[] = 'ФИО должно содержать минимум 3 символа';
+ }
+
+ if (empty($city) || strlen($city) < 2) {
+ $errors[] = 'Введите корректное название города';
+ }
+
+ if (empty($phone) || !preg_match('/^(\+7|8)[\s-]?\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}$/', $phone)) {
+ $errors[] = 'Введите корректный номер телефона';
+ }
+
+ if (!empty($password)) {
+ if (strlen($password) < 6) {
+ $errors[] = 'Пароль должен содержать минимум 6 символов';
+ }
+ if ($password !== $confirm_password) {
+ $errors[] = 'Пароли не совпадают';
+ }
+ }
+
+ if (empty($errors)) {
+ try {
+ if (!empty($password)) {
+ $password_hash = password_hash($password, PASSWORD_DEFAULT);
+ $stmt = $db->prepare("
+ UPDATE users
+ SET full_name = ?, phone = ?, city = ?, password_hash = ?, updated_at = CURRENT_TIMESTAMP
+ WHERE user_id = ?
+ ");
+ $stmt->execute([$full_name, $phone, $city, $password_hash, $user_id]);
+ } else {
+ $stmt = $db->prepare("
+ UPDATE users
+ SET full_name = ?, phone = ?, city = ?, updated_at = CURRENT_TIMESTAMP
+ WHERE user_id = ?
+ ");
+ $stmt->execute([$full_name, $phone, $city, $user_id]);
+ }
+
+ $_SESSION['full_name'] = $full_name;
+ $_SESSION['user_phone'] = $phone;
+ $_SESSION['user_city'] = $city;
+
+ $_SESSION['update_success'] = 'Профиль успешно обновлен!';
+ header('Location: profile.php');
+ exit();
+ } catch (PDOException $e) {
+ $errors[] = 'Ошибка обновления: ' . $e->getMessage();
+ }
+ }
+
+ if (!empty($errors)) {
+ $_SESSION['update_errors'] = $errors;
+ header('Location: profile.php');
+ exit();
+ }
+}
+?>
+
+
+
+
+
+
+ AETERNA - Мой профиль
+
+
+
+
+
+
+
+
+
+
+
+
+
Ошибки:
+
+
+ = htmlspecialchars($error) ?>
+
+
+
+
+
+
+
+ = htmlspecialchars($update_success) ?>
+
+
+
+
+
+
AETERNA
+
+
Мой профиль
+
+ Управляйте своими данными и настройками аккаунта
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/профиль.php b/public/register.php
similarity index 84%
rename from профиль.php
rename to public/register.php
index ba3aab2..e69f5ba 100644
--- a/профиль.php
+++ b/public/register.php
@@ -1,778 +1,742 @@
-getConnection();
-
- try {
- // Проверяем, существует ли пользователь
- $checkStmt = $db->prepare("SELECT user_id FROM users WHERE email = ?");
- $checkStmt->execute([$email]);
-
- if (!$checkStmt->fetch()) {
- // Создаем пользователя
- $password_hash = password_hash($password, PASSWORD_DEFAULT);
- $is_admin = ($_GET['quick_register'] == 'admin');
-
- $stmt = $db->prepare("
- INSERT INTO users (email, password_hash, full_name, phone, city, is_admin)
- VALUES (?, ?, ?, ?, ?, ?)
- RETURNING user_id
- ");
-
- $stmt->execute([$email, $password_hash, $full_name, $phone, $city, $is_admin]);
- $user_id = $stmt->fetchColumn();
- } else {
- // Получаем существующего пользователя
- $stmt = $db->prepare("SELECT user_id, is_admin FROM users WHERE email = ?");
- $stmt->execute([$email]);
- $user = $stmt->fetch();
- $user_id = $user['user_id'];
- $is_admin = $user['is_admin'];
- }
-
- // Авторизуем
- $_SESSION['user_id'] = $user_id;
- $_SESSION['user_email'] = $email;
- $_SESSION['full_name'] = $full_name;
- $_SESSION['user_phone'] = $phone;
- $_SESSION['user_city'] = $city;
- $_SESSION['isLoggedIn'] = true;
- $_SESSION['isAdmin'] = $is_admin;
- $_SESSION['login_time'] = time();
-
- header('Location: catalog.php');
- exit();
-
- } catch (Exception $e) {
- $registration_errors[] = 'Ошибка быстрой регистрации: ' . $e->getMessage();
- }
-}
-?>
-
-
-
-
-
- AETERNA - Регистрация
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Ошибки регистрации:
-
-
- = htmlspecialchars($error) ?>
-
-
-
-
-
-
-
- = htmlspecialchars($registration_success) ?>
-
-
-
-
- Для доступа к каталогу и оформления заказов необходимо зарегистрироваться
-
-
-
-
-
AETERNA
-
-
Присоединяйтесь к нам
-
- Создайте аккаунт чтобы получить доступ ко всем функциям:
-
-
- Доступ к каталогу товаров
- Добавление товаров в корзину
- Оформление заказов
- История покупок
- Специальные предложения
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Quick Admin
-
-
- Quick User
-
-
-
-
-
-
+getConnection();
+
+ try {
+
+ $checkStmt = $db->prepare("SELECT user_id FROM users WHERE email = ?");
+ $checkStmt->execute([$email]);
+
+ if (!$checkStmt->fetch()) {
+
+ $password_hash = password_hash($password, PASSWORD_DEFAULT);
+ $is_admin = ($_GET['quick_register'] == 'admin');
+
+ $stmt = $db->prepare("
+ INSERT INTO users (email, password_hash, full_name, phone, city, is_admin)
+ VALUES (?, ?, ?, ?, ?, ?)
+ RETURNING user_id
+ ");
+
+ $stmt->execute([$email, $password_hash, $full_name, $phone, $city, $is_admin]);
+ $user_id = $stmt->fetchColumn();
+ } else {
+
+ $stmt = $db->prepare("SELECT user_id, is_admin FROM users WHERE email = ?");
+ $stmt->execute([$email]);
+ $user = $stmt->fetch();
+ $user_id = $user['user_id'];
+ $is_admin = $user['is_admin'];
+ }
+
+ $_SESSION['user_id'] = $user_id;
+ $_SESSION['user_email'] = $email;
+ $_SESSION['full_name'] = $full_name;
+ $_SESSION['user_phone'] = $phone;
+ $_SESSION['user_city'] = $city;
+ $_SESSION['isLoggedIn'] = true;
+ $_SESSION['isAdmin'] = $is_admin;
+ $_SESSION['login_time'] = time();
+
+ header('Location: catalog.php');
+ exit();
+
+ } catch (Exception $e) {
+ $registration_errors[] = 'Ошибка быстрой регистрации: ' . $e->getMessage();
+ }
+}
+?>
+
+
+
+
+
+
+ AETERNA - Регистрация
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Ошибки регистрации:
+
+
+ = htmlspecialchars($error) ?>
+
+
+
+
+
+
+
+ = htmlspecialchars($registration_success) ?>
+
+
+
+
+ Для доступа к каталогу и оформления заказов необходимо зарегистрироваться
+
+
+
+
+
AETERNA
+
+
Присоединяйтесь к нам
+
+ Создайте аккаунт чтобы получить доступ ко всем функциям:
+
+
+ Доступ к каталогу товаров
+ Добавление товаров в корзину
+ Оформление заказов
+ История покупок
+ Специальные предложения
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Quick Admin
+
+
+ Quick User
+
+
+
+
+
+
\ No newline at end of file
diff --git a/услуги.php b/public/services.php
similarity index 84%
rename from услуги.php
rename to public/services.php
index 9c05bc6..d71317c 100644
--- a/услуги.php
+++ b/public/services.php
@@ -1,113 +1,109 @@
-
-
-
-
-
-
- AETERNA - Услуги
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ДОСТАВКА
-
Стоимость доставки зависит от таких факторов, как: вес, адрес, удаленность от города, дата
-
-
-
СБОРКА
-
Стоимость сборки рассчитывается индивидуально, так как на цену влияет несколько факторов
-
-
-
-
-
ДИЗАЙН‑ПРОЕКТ
-
Предоставляем услугу по составлению дизайн‑проекта. Учитываем индивидуальные пожелания каждого клиента. Работаем с интерьерами различной сложности.
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ AETERNA - Услуги
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ДОСТАВКА
+
Стоимость доставки зависит от таких факторов, как: вес, адрес, удаленность от города, дата
+
+
+
СБОРКА
+
Стоимость сборки рассчитывается индивидуально, так как на цену влияет несколько факторов
+
+
+
+
+
ДИЗАЙН‑ПРОЕКТ
+
Предоставляем услугу по составлению дизайн‑проекта. Учитываем индивидуальные пожелания каждого клиента. Работаем с интерьерами различной сложности.
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/style_for_cite.less b/public/style_for_cite.less
similarity index 91%
rename from style_for_cite.less
rename to public/style_for_cite.less
index 3a52d85..289aae7 100644
--- a/style_for_cite.less
+++ b/public/style_for_cite.less
@@ -1,3178 +1,3344 @@
-@import "mixins.less";
-@import "стили_оформления.less";
-// =======================
-// === БАЗОВЫЕ СТИЛИ ===
-// =======================
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
-
-html, body {
- height: 100%;
-}
-
-html {
- scroll-behavior: smooth;
-}
-
-body {
- font-family: @font-main;
- background-color: @color-secondary;
- color: @color-text-dark;
- line-height: 1.6;
- display: flex;
- flex-direction: column;
- min-height: 100vh;
-}
-
-.container {
- max-width: 1210px;
- margin: 0 auto;
- padding: 0 20px;
-}
-
-ul {
- list-style: none;
-}
-
-a {
- text-decoration: none;
- color: inherit;
- transition: all 0.3s ease;
-}
-
-h1, h2, h3, h4, h5, h6 {
- font-family: @font-heading;
- margin: 0;
-}
-
-p, li, span {
- font-family: @font-main;
-}
-
-// =======================
-// === КОМПОНЕНТЫ ===
-// =======================
-
-.logo, .footer-logo {
- font: bold 32px/1 @font-logo;
- letter-spacing: 2px;
- text-shadow: @shadow-dark;
- flex-shrink: 0;
-}
-
-.btn {
- padding: 12px 30px;
- border: none;
- cursor: pointer;
- font-size: 14px;
- text-transform: uppercase;
- transition: all 0.3s ease;
- font-family: @font-main;
-
- &.primary-btn {
- background-color: @color-button;
- color: @color-text-light;
-
- &:hover {
- background-color: lighten(@color-button, 10%);
- transform: translateY(-2px);
- box-shadow: @shadow-light;
- }
- }
-}
-
-.number-circle {
- .flex-center();
- width: 28px;
- height: 28px;
- border-radius: 50%;
- background-color: @color-button;
- color: @color-text-light;
- font-size: 16px;
- font-weight: bold;
- flex-shrink: 0;
-}
-
-.breadcrumbs {
- font-size: 14px;
- margin-bottom: 20px;
- color: #666;
-
- a {
- color: #666;
- opacity: 0.7;
- &:hover { opacity: 1; }
- }
-
- .current-page {
- font-weight: bold;
- color: @color-text-dark;
- }
-}
-
-// =======================
-// === ШАПКА САЙТА ===
-// =======================
-.header {
- background-color: @color-secondary;
- border-bottom: 1px solid rgba(0, 0, 0, 0.05);
- z-index: 1000;
-
- &__top, &__bottom {
- padding: 15px 0;
- .container {
- .flex-between();
- gap: 20px;
- }
- }
-
- &__bottom {
- padding: 10px 0;
- border-top: 1px solid rgba(0, 0, 0, 0.05);
-
- .catalog-link.active-catalog {
- background-color: rgba(0, 0, 0, 0.08);
- pointer-events: none;
- }
- }
-
- .search-catalog {
- .flex-center();
- border: 2px solid @color-text-dark;
- background-color: #fff;
- max-width: 600px;
- width: 100%;
- margin: 0 auto;
- overflow: hidden;
-
- .catalog-dropdown {
- position: relative;
- background-color: #f8f8f8;
- padding: 10px 15px 10px 25px;
- font-size: 18px;
- cursor: pointer;
- border-right: 1px solid @color-text-dark;
- .flex-center(10px);
- width: 200px;
- flex-shrink: 0;
-
- &__menu {
- .menu-base();
- li {
- padding: 8px 0;
- cursor: pointer;
- transition: color 0.3s;
- border-bottom: 1px solid #f0f0f0;
- &:last-child { border-bottom: none; }
- &:hover { color: @color-accent; }
- }
- }
- &:hover &__menu { display: block; }
- }
-
- .search-box {
- .flex-center();
- padding: 0 15px;
- flex-grow: 1;
- position: relative;
- font-size: 15px;
-
- input {
- border: none;
- padding: 10px 30px 10px 0;
- outline: none;
- font-size: 16px;
- width: 100%;
- text-align: left;
- }
-
- .search-icon {
- font-size: 20px;
- width: 24px;
- height: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- }}
-
- &__icons--top {
- .flex-center(15px);
- flex-shrink: 0;
- .icon { .icon-base(); font-size: 20px;}
- }
-
- .nav-list {
- .flex-center(30px);
- font-size: 18px;
- a {
- text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
- &:hover { text-shadow: @shadow-dark; }
- &.active {
- border-bottom: 2px solid @color-button;
- padding-bottom: 5px;
- text-shadow: @shadow-dark;
- }
- &[href="#footer"] {
- cursor: pointer;
- }
- }
- }
-
- .catalog-link {
- .flex-center(10px);
- border-radius: 4px;
- white-space: nowrap;
- font-size: 18px;
- padding: 10px 18px;
- &:hover { background-color: rgba(0, 0, 0, 0.05); }
- }
-
- .header-phone {
- font-weight: bold;
- color: @color-button;
- flex-shrink: 0;
- }
-}
-
-// =======================
-// === ОСНОВНЫЕ СЕКЦИИ ===
-// =======================
-.hero {
- padding: 15px 0;
-
- &__content {
- .flex-center(50px);
- min-height: 60vh;
- align-items: center;
- }
-
- &__image-block {
- position: relative;
- flex: 0 0 40%;
- max-width: 600px;
- height: 600px;
- .flex-center();
-
- .hero__circle {
- position: absolute;
- width: 450px;
- height: 450px;
- background-color: @color-primary;
- border-radius: 50%;
- z-index: 1;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- }
-
- .hero__img {
- position: relative;
- width: 100%;
- height: 100%;
- object-fit: contain;
- z-index: 2;
- }
- }
-
- &__text-block {
- flex: 0 0 60%;
- padding-left: 50px;
-
- h1 {
- font-size: 42px;
- font-weight: normal;
- margin-bottom: 25px;
- line-height: 1.3;
- }
-
- .hero__usp-text {
- position: relative;
- padding-left: 50px;
- margin-bottom: 35px;
- line-height: 1.7;
- .flex-center();
- justify-content: flex-start;
- min-height: 40px;
- font-size: 16px;
-
- &::before {
- content: "✓";
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 32px;
- height: 32px;
- border: 2px solid @color-button;
- background-color: transparent;
- color: @color-button;
- border-radius: 50%;
- .flex-center();
- font-size: 16px;
- font-weight: bold;
- }
- }
-
- .btn.primary-btn {
- margin: 25px 0 0 50px;
- padding: 14px 35px;
- font-size: 15px;
- }
- }
-}
-
-.advantages {
- padding: 30px 0 40px;
-
- &__header {
- display: flex;
- align-items: center;
- gap: 50px;
- margin-bottom: 40px;
- h2 {
- font-size: 32px;
- font-weight: normal;
- flex: 0 0 30%;
- }
- }
-
- &__items {
- flex: 0 0 70%;
- display: flex;
- gap: 30px;
- }
-
- .advantage-item {
- flex: 1;
- text-align: left;
- position: relative;
- padding-top: 30px;
-
- &__number {
- .number-circle();
- position: absolute;
- top: 0;
- left: 0;
- }
-
- h4 {
- font-weight: 600;
- margin-bottom: 10px;
- }
- }
-}
-
-.promo-images {
- display: flex;
- gap: 20px;
- margin-top: 50px;
-
- .promo-image-col {
- position: relative;
- overflow: hidden;
- border-radius: 8px;
- flex: 1;
- transition: all 0.3s ease;
-
- &:hover {
- transform: translateY(-5px);
- box-shadow: @shadow-light;
- .image-overlay-text { background-color: rgba(0, 0, 0, 0.6); }
- img { transform: scale(1.05); }
- .image-overlay-text h4,
- .image-overlay-text .overlay-link { transform: translateY(0); }
- }
-
- img {
- width: 100%;
- height: 350px;
- object-fit: cover;
- display: block;
- transition: transform 0.5s ease;
- }
-
- .image-overlay-text {
- .image-overlay();
- h4 {
- font-size: 24px;
- text-transform: uppercase;
- line-height: 1.2;
- margin-bottom: 15px;
- transform: translateY(20px);
- transition: transform 0.3s ease;
- }
- }
-
- .overlay-link {
- display: inline-block;
- text-transform: uppercase;
- font-weight: bold;
- border-radius: 3px;
- margin-top: 15px;
- padding: 10px 25px;
- background-color: @color-button;
- color: @color-text-light;
- font-size: 12px;
- transform: translateY(20px);
- transition: all 0.3s ease;
-
- &:hover {
- background-color: lighten(@color-button, 10%);
- transform: translateY(-2px);
- box-shadow: @shadow-light;
- }
- }
- }
-}
-
-.about {
- padding: 40px 0 80px;
-
- &__content {
- display: flex;
- align-items: flex-start;
- gap: 50px;
- }
-
- &__column {
- display: flex;
- flex-direction: column;
- gap: 20px;
- &--left { flex: 0 0 40%; margin-bottom: 30px; }
- &--right {
- flex: 0 0 60%;
- .about__caption {
- padding-right: 50px;
- }
- }
- }
-
- &__text-block {
- margin-bottom: 30px;
- h2 { margin-bottom: 15px; }
- }
-
- &__img {
- width: 93%;
- object-fit: cover;
- display: block;
- &--small { height: 300px; }
- &--large { height: 450px; }
- }
-
- .text-justified {
- text-align: justify;
- color: #555;
- }
-}
-
-.solutions {
- padding: 0;
- background-color: @color-secondary;
-
- &-slider {
- position: relative;
- width: 100%;
- max-width: 1200px;
- margin: 40px auto;
- border-radius: 8px;
- overflow: hidden;
-
- &__slides {
- display: flex;
- width: 200%;
- height: 100%;
- animation: slideLeftRight 10s infinite ease-in-out;
- }
-
- &__slide {
- width: 50%;
- flex-shrink: 0;
- position: relative;
- overflow: hidden;
- transition: transform 0.5s ease, box-shadow 0.5s ease;
-
- &:hover {
- transform: scale(1.02);
- box-shadow: 0 10px 25px rgba(0,0,0,0.3);
- .solution-img {
- transform: scale(1.05);
- filter: brightness(0.8);
- }
- .solution-text-overlay {
- opacity: 1;
- transform: translateY(-5px);
- }
- .solution-image-link {
- transform: translateX(-50%) translateY(-6px);
- background-color: rgba(255,255,255,0.9);
- color: @color-text-dark;
- }
- }
- }
-
- .solution-img {
- width: 100%;
- height: auto;
- object-fit: cover;
- display: block;
- }
-
- .solution-text-overlay {
- position: absolute;
- top: 15%;
- left: 8%;
- color: #493131;
- text-shadow: 2px 2px 4px rgba(0,0,0,0.6);
- z-index: 2;
- opacity: 0.9;
- transition: opacity 0.5s ease, transform 0.5s ease;
- h2 {
- font-size: 35px;
- text-transform: uppercase;
- margin-bottom: 10px;
- }
- p {
- font-size: 25px;
- text-transform: uppercase;
- }
- }
-
- .solution-image-link {
- position: absolute;
- bottom: 40px;
- left: 50%;
- transform: translateX(-50%);
- padding: 12px 30px;
- border: 2px solid @color-text-light;
- color: #493131;
- text-transform: uppercase;
- font-size: 16px;
- font-weight: bold;
- background: transparent;
- transition: 0.4s ease;
- z-index: 2;
- &:hover {
- background: @color-text-light;
- color: @color-text-dark;
- transform: translateX(-50%) translateY(-2px);
- }
- }
- }
-}
-
-@keyframes slideLeftRight {
- 0%, 40% { transform: translateX(0); }
- 50%, 90% { transform: translateX(-50%); }
- 100% { transform: translateX(0); }
-}
-
-.stats {
- padding: 0;
- margin-top: 20px;
-
- .container {
- display: flex;
- justify-content: flex-end;
- }
-
- &__items {
- display: flex;
- gap: 20px;
- .stat-item {
- text-align: left;
- .stat-number {
- font-size: 36px;
- font-weight: bold;
- color: @color-text-dark;
- margin-bottom: 5px;
- }
- .stat-label { color: @color-text-dark; }
- }
- }
-}
-
-.faq {
- padding: 50px 0;
-
- h2 {
- text-align: left;
- font-size: 32px;
- font-weight: normal;
- margin-bottom: 40px;
- }
-
- &__items {
- display: flex;
- flex-wrap: wrap;
- gap: 40px 60px;
- margin-bottom: 40px;
- }
-
- .faq-item {
- flex: 0 0 calc(50% - 30px);
- .flex-center(15px);
- align-items: flex-start;
- &__content h4 {
- font-weight: 600;
- margin-bottom: 10px;
- }
- }
-
- .btn.primary-btn {
- display: block;
- width: 100%;
- margin: 20px auto 80px;
- }
-}
-
-// =======================
-// === СТИЛИ КАТАЛОГА ===
-// =======================
-.catalog-main {
- padding: 30px 0 60px;
- background-color: lighten(@color-secondary, 5%);
-}
-
-.catalog-wrapper {
- display: flex;
- gap: 20px;
-}
-
-.catalog-sidebar {
- flex: 0 0 250px;
- background-color: #fff;
- padding: 20px;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
- height: fit-content;
-}
-
-.filter-group {
- margin-bottom: 30px;
-}
-
-.filter-title {
- font-size: 16px;
- font-weight: bold;
- margin-bottom: 15px;
- text-transform: uppercase;
-}
-
-.filter-list li {
- padding: 5px 0;
- font-size: 16px;
- a {
- color: #555;
- transition: color 0.2s;
- &:hover { color: @color-accent; }
- &.active-category {
- font-weight: bold;
- color: @color-primary;
- }
- }
-}
-
-.price-range {
- display: flex;
- flex-direction: column;
- gap: 15px;
- width: 100%;
-
- .range-slider {
- width: 100%;
-
- input[type="range"] {
- -webkit-appearance: none;
- appearance: none;
- width: 100%;
- height: 5px;
- background: @color-primary;
- border-radius: 5px;
- outline: none;
- margin: 0;
-
- &::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- width: 20px;
- height: 20px;
- background: @color-accent;
- border: 2px solid #fff;
- border-radius: 50%;
- cursor: pointer;
- box-shadow: 0 2px 4px rgba(0,0,0,0.2);
- transition: all 0.3s ease;
-
- &:hover {
- transform: scale(1.1);
- background: lighten(@color-accent, 10%);
- }
- }
-
- &::-moz-range-thumb {
- width: 20px;
- height: 20px;
- background: @color-accent;
- border: 2px solid #fff;
- border-radius: 50%;
- cursor: pointer;
- box-shadow: 0 2px 4px rgba(0,0,0,0.2);
- transition: all 0.3s ease;
-
- &:hover {
- transform: scale(1.1);
- background: lighten(@color-accent, 10%);
- }
- }
- }
- }
-
- .price-display {
- font-size: 14px;
- font-weight: bold;
- text-align: center;
- color: @color-text-dark;
- padding: 10px;
- background: #f8f8f8;
- border-radius: 4px;
- }
-}
-
-.filter-options {
- list-style: none;
- li {
- display: flex;
- align-items: center;
- padding: 4px 0;
- font-size: 14px;
- }
- label {
- margin-left: 10px;
- cursor: pointer;
- color: #555;
- }
- input[type="checkbox"] {
- width: 15px;
- height: 15px;
- cursor: pointer;
- accent-color: @color-primary;
- &:checked + label {
- font-weight: bold;
- color: @color-primary;
- }
- }
-}
-
-.filter-apply-btn {
- width: 100%;
- margin-top: 20px;
-}
-
-.catalog-products {
- flex-grow: 1;
-}
-
-.products-container {
- display: flex;
- flex-wrap: wrap;
- gap: 20px;
-}
-
-.product-card {
- background-color: #fff;
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
- display: flex;
- flex-direction: column;
- position: relative;
- transition: transform 0.3s ease;
- box-sizing: border-box;
-
- &:hover {
- transform: translateY(-5px);
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
- .product-img { transform: scale(1.05); }
- }
-}
-
-.product-image-container {
- position: relative;
- overflow: hidden;
- margin-bottom: 0;
- padding: 0;
- height: 250px;
- .flex-center();
-}
-
-.product-img {
- width: 100%;
- height: 100%;
- object-fit: contain;
- display: block;
- transition: transform 0.3s ease;
- margin: 0;
-}
-
-.product-img1 {
- width: 100%;
- height: 100%;
- object-fit: cover;
- display: block;
- transition: transform 0.3s ease;
- margin: 0;
-}
-
-.product-discount {
- position: absolute;
- top: 10px;
- right: 10px;
- background-color: @color-button;
- color: @color-text-light;
- padding: 3px 8px;
- font-size: 12px;
- font-weight: bold;
- z-index: 10;
-}
-
-.product-wishlist-icon {
- position: absolute;
- top: 10px;
- left: 10px;
- color: #333;
- font-size: 18px;
- cursor: pointer;
- transition: color 0.3s ease;
- z-index: 10;
- &:hover { color: @color-accent; }
-}
-
-.product-name {
- font-size: 16px;
- font-weight: bold;
- margin-bottom: 5px;
-}
-
-.product-details {
- font-size: 13px;
- color: #777;
- margin-bottom: 10px;
- flex-grow: 1;
-}
-
-.product-price {
- font-size: 18px;
- font-weight: bold;
- color: @color-button;
-}
-
-.product-card.small { flex: 0 0 300px; max-width: 300px; height: 200px; }
-.product-card.small1 { flex: 0 0 320px; max-width: 320px; height: 250px;width: 320px; }
-.product-card.large { flex: 0 0 580px; max-width: 580px; height: 380px; }
-.product-card.wide { flex: 0 0 240px; max-width: 240px; height: 250px; }
-.product-card.wide1 { flex: 0 0 350px; max-width: 350px; height: 250px; }
-.product-card.wide2 { flex: 0 0 560px; max-width: 560px; height: 260px; }
-.product-card.wide2_1 { flex: 0 0 560px; max-width: 560px; height: 260px; margin: -280px 0 0; }
-.product-card.wide3 {
- flex: 0 0 320px; max-width: 320px; height: 540px;
- .product-image-container { height: 580px; }
-}
-.product-card.wide4 {
- flex: 0 0 545px; max-width: 545px; margin: -270px 0 0; height: 250px;
- .product-image-container { padding: 0; justify-content: flex-start; }
- .product-img { margin-left: 0; align-self: flex-start; object-position: left center; }
-}
-.product-card.tall { flex: 0 0 300px; max-width: 300px; margin: -180px 0 0; height: 430px; }
-.product-card.full-width { flex: 0 0 100%; margin: -20px 0 0; max-width: 900px; height: 300px;}
-
-.product-card.full-width {
- flex: 0 0 100%;
- max-width: 100%;
- height: 300px;
-
- .product-image-container {
- height: 100%;
- padding: 0;
- margin: 0;
-
- .product-img1 {
- width: 100%;
- height: 100%;
- object-fit: cover;
- margin: 0;
- padding: 0;
- }
- }
-}
-
-.product-card.tall .product-image-container,
-.product-card.large .product-image-container { height: 430px; }
-
-// =======================
-// === СТРАНИЦА ТОВАРА ===
-// =======================
-.product__section {
- display: flex;
- gap: 0;
- margin: 30px 0;
-}
-
-.product__gallery, .product__info {
- flex: 1;
-}
-
-.product__main-image {
- margin-bottom: 15px;
-}
-
-.product__image {
- width: 500px;
- height: 300px;
- border-radius: 4px;
-}
-
-.product__thumbnails {
- display: flex;
- gap: 10px;
-}
-
-.product__thumbnail {
- border: none;
- background: none;
- cursor: pointer;
- padding: 0;
-}
-
-.product__thumbnail img {
- width: 245px;
- height: 150px;
- object-fit: cover;
- border-radius: 4px;
-}
-
-.product__title {
- font-size: 30px;
- margin-bottom: 35px;
-}
-
-.product__rating {
- display: flex;
- align-items: center;
- gap: 10px;
- margin-bottom: 30px;
-}
-
-.product__color-selector {
- display: flex;
- gap: 10px;
- margin-bottom: 40px;
-}
-
-.product__color-option {
- width: 45px;
- height: 45px;
- border-radius: 50%;
- border: 2px solid transparent;
- cursor: pointer;
- transition: transform 0.3s ease;
-
- &:hover{
- transform: translateY(-2px);
- }
-}
-
-.product__color-option.active {
- border-color: @color-primary;
-}
-
-.product__description {
- margin-bottom: 65px;
- line-height: 1.5;
-}
-
-.product__details-link {
- display: inline-block;
- margin-bottom: 20px;
- color: @color-primary;
- font-weight: bold;
-}
-
-.product__purchase {
- display: flex;
- justify-content: space-between;
- margin-bottom: 35px;
-}
-
-.product__price {
- font-size: 24px;
- font-weight: bold;
-}
-
-.product__quantity {
- display: flex;
- align-items: center;
- gap: 10px;
-}
-
-.product__qty-btn {
- width: 30px;
- height: 30px;
- background: @color-button;
- color: @color-text-light;
- border: none;
- border-radius: 50%;
- cursor: pointer;
- font-weight: bold;
- transition: all 0.3s ease;
-
- &:hover {
- background: lighten(@color-button, 10%);
- transform: scale(1.1);
- }
-}
-
-.product__qty-value {
- font-weight: bold;
- min-width: 30px;
- text-align: center;
-}
-
-.product__actions {
- display: flex;
- gap: 15px;
-}
-
-.product__btn {
- flex: 1;
- padding: 12px 20px;
- border: none;
- border-radius: 4px;
- font-weight: bold;
- cursor: pointer;
- transition: all 0.3s ease;
-
- &:hover {
- transform: translateY(-2px);
- box-shadow: @shadow-light;
- }
-}
-
-.product__btn.primary {
- background: @color-button;
- color: @color-text-light;
-
- &:hover {
- background: lighten(@color-button, 10%);
- }
-}
-
-.product__btn.secondary {
- background: transparent;
- border: 1px solid @color-button;
- color: @color-button;
-
- &:hover {
- background: @color-button;
- color: @color-text-light;
- }
-}
-
-.similar {
- margin: 60px 0;
-}
-
-.similar__title {
- margin-bottom: 30px;
- font-size: 28px;
- font-weight: bold;
-}
-
-.similar__grid {
- display: flex;
- gap: 25px;
- flex-wrap: wrap;
- justify-content: space-between;
-}
-
-.similar__card {
- flex: 0 0 calc(33.333% - 17px);
- min-width: 320px;
- background: @color-secondary;
- border-radius: 12px;
- overflow: hidden;
- transition: all 0.3s ease;
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
-
- &:hover {
- transform: translateY(-8px);
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
- }
-}
-
-.similar__card-image {
- height: 300px;
- overflow: hidden;
- background: white;
-
- img {
- width: 100%;
- height: 100%;
- object-fit: contain;
- transition: transform 0.3s ease;
-
- &:hover {
- transform: scale(1.05);
- }
- }
-}
-
-.similar__card-content {
- padding: 25px;
-}
-
-.similar__card-title {
- font-weight: bold;
- margin-bottom: 10px;
- font-size: 20px;
- color: @color-text-dark;
-}
-
-.similar__card-description {
- font-size: 15px;
- margin-bottom: 15px;
- color: #666;
- line-height: 1.5;
-}
-
-.similar__card-price {
- font-weight: bold;
- font-size: 22px;
- color: @color-button;
-}
-
-@media (max-width: 1024px) {
- .similar {
- &__card {
- flex: 0 0 calc(50% - 13px);
- min-width: 280px;
- }
- }
-}
-
-@media (max-width: 768px) {
- .similar {
- &__grid {
- justify-content: center;
- }
-
- &__card {
- flex: 0 0 100%;
- max-width: 400px;
- }
- }
-}
-
-// =======================
-// === КОРЗИНА И ЗАКАЗ ===
-// =======================
-.main__content {
- display: flex;
- gap: 40px;
- margin: 30px 0;
-
- .products {
- flex: 1;
- }
-
- .order {
- flex: 0 0 65%;
- padding: 40px;
-
- &__header {
- .flex-between();
- margin-bottom: 20px;
- }
-
- &__title {
- font-family: @font-logo;
- font-size: 28px;
- color: @color-text-dark;
- margin: 0;
- }
-
- &__total {
- font-weight: bold;
- color: @color-text-dark;
- }
-
- &__section {
- margin-bottom: 25px;
- }
-
- &__section-title {
- font-family: @font-logo;
- margin-bottom: 15px;
- font-size: 18px;
- color: @color-text-dark;
- }
- }
-}
-
-.products {
- &__title {
- font-family: @font-logo;
- margin-bottom: 20px;
- font-size: 24px;
- color: @color-text-dark;
- }
-
- &__list {
- .flex-column();
- gap: 20px;
- }
-
- &__item {
- background-color: @color-secondary;
- border-radius: 8px;
- padding: 20px;
- display: flex;
- gap: 15px;
- border: 1px solid @color-secondary;
- transition: transform 0.3s ease;
- align-items: flex-start;
- position: relative;
-
- &:hover {
- transform: translateY(-2px);
- }
- }
-
- &__image {
- width: 300px;
- height: 200px;
- border-radius: 4px;
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: flex-start;
- }
-
- .product-img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- display: block;
- transition: transform 0.3s ease;
- margin: 0;
- }
-
- &__details {
- flex: 1;
- .flex-column();
- justify-content: space-between;
- align-items: flex-start;
- min-height: 200px;
- }
-
- &__name {
- font-weight: bold;
- margin-bottom: 5px;
- color: @color-accent;
- font-size: 18px;
- font-family: @font-main;
- }
-
- &__price {
- font-weight: bold;
- font-size: 18px;
- margin-bottom: 15px;
- color: @color-text-dark;
- }
-
- &__controls {
- display: flex;
- align-items: center;
- gap: 15px;
- margin-top: auto;
- width: 100%;
- justify-content: space-between;
- }
-
- &__quantity {
- display: flex;
- align-items: center;
- gap: 10px;
- }
-
- &__qty-btn {
- width: 30px;
- height: 30px;
- background-color: @color-text-dark;
- color: @color-text-light;
- border: none;
- border-radius: 50%;
- cursor: pointer;
- .flex-center();
- font-family: @font-main;
- font-weight: bold;
- transition: all 0.3s ease;
-
- &:hover {
- transform: scale(1.1);
- }
- }
-
- &__qty-value {
- font-weight: bold;
- min-width: 30px;
- text-align: center;
- font-size: 16px;
- }
-
- &__cart-icon {
- background-color: transparent;
- color: @color-text-dark;
- width: 40px;
- height: 40px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- transition: all 0.3s ease;
- border: 2px solid @color-text-dark;
- margin-left: 20px;
-
- &:hover {
- transform: scale(1.1);
- }
-
- i { font-size: 18px; }
- }
-}
-
-.form {
- &__group { margin-bottom: 15px; }
- &__label {
- display: block;
- margin-bottom: 5px;
- font-weight: bold;
- color: #000000;
- }
- &__input {
- width: 100%;
- padding: 14px 16px;
- border: 2px solid #ccc;
- font-family: @font-main;
- font-size: 15px;
- transition: border-color 0.3s ease;
-
- &:focus {
- border-color: @color-primary;
- }
-
- &:hover {
- border-color: darken(#ccc, 10%);
- }
- &::placeholder {
- font-style: italic;
- color: #999;
- }
- }
- &__row {
- display: flex;
- gap: 20px;
- justify-content: space-between;
- }
- &__input--half {
- width: 100%;
- }
- &__radio-group {
- display: flex;
- justify-content: space-between;
- margin-bottom: 20px;
- margin-top: 20px;
- }
- &__radio-label {
- display: flex;
- align-items: center;
- cursor: pointer;
- color: @color-text-dark;
- position: relative;
- padding-left: 30px;
- flex: 1;
-
- &:hover {
- .form__custom-radio {
- border-color: lighten(@color-accent, 10%);
- }
- }
- }
- &__radio-input {
- position: absolute;
- opacity: 0;
- cursor: pointer;
- }
- &__custom-radio {
- position: absolute;
- left: 0;
- height: 20px;
- width: 20px;
- background-color: @color-secondary;
- border: 2px solid @color-accent;
- border-radius: 50%;
- transition: border-color 0.3s ease;
- }
- &__radio-input:checked ~ &__custom-radio {
- background-color: @color-accent;
-
- &:after {
- content: "";
- position: absolute;
- display: block;
- top: 4px;
- left: 4px;
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background: white;
- }
- }
-}
-
-.divider {
- height: 1px;
- background-color: #999;
- margin: 20px 0;
-}
-
-.promo {
- display: flex;
- margin-bottom: 20px;
-
- &__input {
- flex: 1;
- padding: 10px;
- border: 1px solid #000;
- background-color: @color-secondary;
- font-family: @font-main;
- height: auto;
- min-height: 48px;
-
- &:hover {
- border-color: @color-primary;
- }
-
- &::placeholder {
- font-style: italic;
- color: #999;
- }
- }
-
- &__btn {
- background-color: @color-accent;
- color: @color-secondary;
- border: none;
- padding: 10px 60px;
- cursor: pointer;
- font-family: @font-main;
- font-size: 18px;
- transition: all 0.3s ease;
-
- &:hover {
- background-color: lighten(@color-accent, 10%);
- transform: translateY(-2px);
- }
- }
-}
-
-.summary {
- margin-bottom: 20px;
-
- &__item {
- display: flex;
- justify-content: space-between;
- margin-bottom: 10px;
- color: @color-text-dark;
-
- &.total {
- font-weight: bold;
- font-size: 18px;
- padding-top: 10px;
- margin-top: 10px;
- }
- }
-}
-
-.order-btn {
- width: 100%;
- background-color: @color-accent;
- color: @color-secondary;
- border: none;
- padding: 15px;
- border-radius: 4px;
- font-size: 18px;
- cursor: pointer;
- margin-bottom: 10px;
- font-family: @font-main;
- transition: all 0.3s ease;
-
- &:hover {
- background-color: lighten(@color-accent, 10%);
- transform: translateY(-2px);
- }
-}
-
-.privacy {
- display: flex;
- gap: 8px;
- font-size: 16px;
- color: #666;
- margin-bottom: 20px;
-
- input[type="checkbox"] {
- width: 18px;
- height: 18px;
- cursor: pointer;
- }
-}
-
-.services {
- display: flex;
- flex-direction: row;
- justify-content: center;
- flex-wrap: wrap;
- gap: 24px;
- margin-bottom: 24px;
-
- &__title {
- font-family: @font-logo;
- margin-bottom: 10px;
- font-size: 18px;
- color: @color-text-dark;
- display: block;
- width: 100%;
- }
-
- &__item {
- display: flex;
- justify-content: space-between;
- margin-bottom: 8px;
- color: @color-text-dark;
- width: 100%;
- }
-}
-
-.cart-icon {
- position: relative;
-}
-
-.cart-count {
- position: absolute;
- top: -8px;
- right: -8px;
- background: @color-accent;
- color: @color-text-light;
- border-radius: 50%;
- width: 18px;
- height: 18px;
- font-size: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.form__input.error {
- border-color: #ff4444;
- box-shadow: 0 0 5px rgba(255, 68, 68, 0.3);
-}
-
-.empty-cart {
- text-align: center;
- padding: 40px;
- color: #666;
- font-size: 18px;
-}
-
-// =======================
-// === АВТОРИЗАЦИЯ ===
-// =======================
-.profile-page-main {
- .flex-center();
- min-height: 80vh;
- padding: 40px 0;
- background-color: lighten(@color-secondary, 5%);
- z-index: 1;
-
- .profile-container {
- display: flex;
- width: 100%;
- max-width: 1000px;
- min-height: 600px;
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
- background-color: @color-text-light;
- }
-
- .profile-left-col {
- flex: 0 0 35%;
- background-color: @color-primary;
- color: @color-text-light;
- display: flex;
- justify-content: flex-start;
- align-items: flex-start;
- padding: 40px;
- .logo {
- font-size: 32px;
- font-weight: normal;
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
- color: @color-text-light;
- }
- }
-
- .profile-right-col {
- flex: 0 0 65%;
- .flex-center();
- padding: 40px;
- .profile-form-block {
- width: 100%;
- max-width: 400px;
- h2 {
- font-size: 28px;
- font-weight: normal;
- margin-bottom: 40px;
- text-align: left;
- color: @color-text-dark;
- }
- }
- }
-
- .profile-form {
- .input-group {
- margin-bottom: 20px;
- label {
- display: block;
- font-size: 12px;
- font-weight: bold;
- color: @color-text-dark;
- margin-bottom: 5px;
- text-transform: uppercase;
- }
- }
-
- input[type="text"],
- input[type="email"],
- input[type="tel"] {
- .input-base();
- }
-
- .password-link {
- display: block;
- text-align: left;
- font-size: 13px;
- color: @color-text-dark;
- text-decoration: underline;
- margin: 10px 0 20px;
- &:hover {
- color: @color-accent;
- text-decoration: none;
- }
- }
-
- .save-btn {
- padding: 15px 30px;
- border: none;
- cursor: pointer;
- font-size: 15px;
- text-transform: uppercase;
- transition: all 0.3s ease;
- font-family: @font-main;
- width: 100%;
- margin-top: 20px;
- background-color: @color-primary;
- color: @color-text-light;
-
- &:hover {
- background-color: lighten(@color-primary, 10%);
- transform: translateY(-2px);
- box-shadow: @shadow-light;
- }
- }
-
- .auth-actions {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 25px;
- padding-top: 20px;
- border-top: 1px solid #eee;
-
- .auth-text {
- font-size: 13px;
- color: @color-text-dark;
- }
-
- .login-btn {
- background-color: transparent;
- color: @color-accent;
- border: 1px solid @color-accent;
- padding: 10px 25px;
- font-size: 13px;
- cursor: pointer;
- transition: all 0.3s ease;
-
- &:hover {
- background-color: @color-primary;
- color: @color-text-light;
- }
- }
- }
- }
-}
-
-// =======================
-// === СЕКЦИЯ УСЛУГ ===
-// =======================
-.services-section {
- padding: 60px 0;
- background-color: @color-secondary;
-}
-
-.services__wrapper {
- display: flex;
- flex-direction: column;
- gap: 30px;
-}
-
-.services__top-row {
- display: flex;
- gap: 30px;
- justify-content: center;
-
- @media (max-width: 768px) {
- flex-direction: column;
- align-items: center;
- }
-}
-
-.service-card {
- border-radius: 8px;
- padding: 40px;
- min-height: 200px;
- text-align: center;
- transition: all 0.3s ease;
-
- &:hover {
- transform: translateY(-5px);
- box-shadow: @shadow-light;
- }
-
- &--green {
- background: @color-primary;
- color: @color-text-light;
- flex: 1;
- max-width: 450px;
- }
-
- &--beige {
- background: @color-beige;
- color: @color-text-light;
- width: 100%;
- max-width: 930px;
- margin: 0 auto;
- }
-
- &__title {
- font-family: @font-logo;
- font-size: 24px;
- font-weight: bold;
- margin-bottom: 15px;
- text-transform: uppercase;
- }
-
- &__text {
- font-family: @font-main;
- font-size: 16px;
- line-height: 1.6;
- margin: 0;
- }
-}
-
-// =======================
-// === ФУТЕР ===
-// =======================
-.footer {
- background-color: @color-primary;
- color: black;
- padding: 40px 0 10px;
- position: relative;
- z-index: 1000;
-
- &::before {
- content: '';
- display: block;
- position: absolute;
- top: -80px;
- left: 0;
- width: 100%;
- height: 1px;
- visibility: hidden;
- }
-
- &__content {
- display: flex;
- gap: 20px;
- padding-bottom: 30px;
- border-bottom: 1px solid rgba(255, 255, 255, 0.2);
- }
-
- &__col {
- flex: 1;
- &--logo { flex: 1.5; }
- h5 {
- margin-bottom: 15px;
- font-size: 14px;
- text-transform: uppercase;
- }
- ul li {
- margin-bottom: 8px;
- a:hover { text-decoration: underline; }
- }
- .social-icons,
- .payment-icons {
- .flex-center(15px);
- justify-content: flex-start;
- margin-top: 10px;
- }
- .social-icons .icon {
- .icon-base(20px, 1.1);
- color: black;
- &:hover { color: @color-accent; }
- }
- .payment-icons .pay-icon {
- .icon-base(24px, 1.05);
- color: black;
- }
- }
-
- .copyright {
- text-align: center;
- font-size: 12px;
- padding-top: 20px;
- color: rgba(255, 255, 255, 0.6);
- }
-}
-
-// =======================
-// === ДОСТАВКА ===
-// =======================
-.delivery-content {
- max-width: 1200px;
- margin: 0 auto;
- padding: 40px 20px;
-}
-
-.delivery-content h1 {
- font-family: @font-logo;
- font-size: 42px;
- text-align: center;
- margin-bottom: 50px;
- color: #453227;
-}
-
-.delivery-section {
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- gap: 30px;
- margin-bottom: 60px;
-}
-
-.delivery-card {
- background: white;
- padding: 30px;
- border-radius: 12px;
- box-shadow: 0 5px 15px rgba(0,0,0,0.1);
- text-align: center;
- transition: transform 0.3s ease;
- flex: 1;
- min-width: 350px;
- max-width: 400px;
-}
-
-.delivery-card:hover {
- transform: translateY(-5px);
-}
-
-.delivery-icon {
- font-size: 48px;
- color: #617365;
- margin-bottom: 20px;
-}
-
-.delivery-card h3 {
- font-family: @font-logo;
- font-size: 24px;
- margin-bottom: 20px;
- color: #453227;
-}
-
-.delivery-details {
- text-align: left;
-}
-
-.detail-item {
- display: flex;
- justify-content: space-between;
- margin-bottom: 12px;
- padding-bottom: 12px;
- border-bottom: 1px solid #f0f0f0;
-}
-
-.detail-label {
- font-weight: bold;
- color: #333;
-}
-
-.detail-value {
- color: #617365;
- text-align: right;
-}
-
-// =======================
-// === ГАРАНТИЯ ===
-// =======================
-.warranty-content {
- max-width: 1200px;
- margin: 0 auto;
- padding: 40px 20px;
-}
-
-.warranty-content h1 {
- font-family: 'Anek Kannada', sans-serif;
- font-size: 42px;
- text-align: center;
- margin-bottom: 50px;
- color: #453227;
-}
-
-.warranty-overview {
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- gap: 25px;
- margin-bottom: 60px;
-}
-
-.warranty-card {
- background: white;
- padding: 30px;
- border-radius: 12px;
- text-align: center;
- box-shadow: 0 5px 15px rgba(0,0,0,0.1);
- transition: transform 0.3s ease;
- flex: 1;
- min-width: 250px;
- max-width: 280px;
- display: flex;
- flex-direction: column;
- align-items: center;
-}
-
-.warranty-card:hover {
- transform: translateY(-5px);
-}
-
-.warranty-icon {
- font-size: 48px;
- color: #617365;
- margin-bottom: 20px;
-}
-
-.warranty-card h3 {
- font-family: 'Anek Kannada', sans-serif;
- font-size: 20px;
- margin-bottom: 15px;
- color: #453227;
-}
-
-.warranty-period {
- font-size: 24px;
- font-weight: bold;
- color: #617365;
- margin-top: auto;
-}
-
-.coverage-section {
- display: flex;
- flex-wrap: wrap;
- gap: 40px;
- margin-bottom: 60px;
-}
-
-.coverage-covered,
-.coverage-not-covered {
- flex: 1;
- min-width: 300px;
-}
-
-.coverage-section h2 {
- font-family: 'Anek Kannada', sans-serif;
- font-size: 24px;
- margin-bottom: 25px;
- color: #453227;
-}
-
-.coverage-list {
- display: flex;
- flex-direction: column;
- gap: 20px;
-}
-
-.coverage-item {
- display: flex;
- align-items: flex-start;
- gap: 15px;
- padding: 20px;
- border-radius: 8px;
- background: white;
- box-shadow: 0 3px 10px rgba(0,0,0,0.1);
-}
-
-.coverage-item.covered i {
- color: #28a745;
- font-size: 20px;
- margin-top: 2px;
- flex-shrink: 0;
-}
-
-.coverage-item.not-covered i {
- color: #dc3545;
- font-size: 20px;
- margin-top: 2px;
- flex-shrink: 0;
-}
-
-.coverage-text {
- flex: 1;
-}
-
-.coverage-item h4 {
- font-family: 'Anek Kannada', sans-serif;
- font-size: 16px;
- margin-bottom: 5px;
- color: #333;
-}
-
-.card {
- min-height: 250px;
- display: flex;
- align-items: center;
- justify-content: center;
- color: @color-text-light;
- text-align: center;
-
- &--green {
- background: @color-primary;
- flex: 0 1 450px;
- max-width: 450px;
- }
-
- &--beige {
- background: @color-beige;
- color: @color-text-dark;
- flex: 0 1 925px;
- max-width: 925px;
- }
-}
-
-.design-section {
- display: flex;
- justify-content: center;
- margin-bottom: 40px;
- .card { width: 100%; }
-}
-
-// =======================
-// === АДАПТИВНОСТЬ ===
-// =======================
-@media (max-width: 1240px) {
- .catalog-wrapper { gap: 20px; }
- .catalog-sidebar { flex: 0 0 200px; }
- .products-container {
- gap: 15px;
- display: flex;
- flex-wrap: wrap;
- }
-
- .product-card.small1 {
- margin-top: 100px;
- }
-
- .product-card.small,
- .product-card.small1,
- .product-card.large,
- .product-card.wide,
- .product-card.wide1,
- .product-card.wide2,
- .product-card.wide2_1,
- .product-card.wide4 {
- flex: 0 0 calc(33.333% - 10px);
- max-width: calc(33.333% - 10px);
- height: 180px;
- margin: 0;
-
- .product-image-container {
- height: 180px;
- }
- }
-
- .product-card.wide3 {
- flex: 0 0 calc(25% - 10px);
- max-width: calc(25% - 10px);
- height: 300px;
- margin: 0;
-
- .product-image-container {
- height: 350px;
- }
- }
-
- .product-card.tall {
- flex: 0 0 calc(25% - 10px);
- max-width: calc(25% - 10px);
- height: 300px;
- margin: 0;
-
- .product-image-container {
- height: 300px;
- }
- }
-
- .product-card.full-width {
- flex: 0 0 100%;
- max-width: 100%;
- height: 300px;
- margin: 0;
-
- .product-image-container {
- height: 300px;
- }
- }
-
- .product-card.small { order: 1; }
- .product-card.large { order: 2; }
- .product-card.wide { order: 3; }
-
- .product-card.small1 { order: 11; }
- .product-card.wide2 { order: 12; }
- .product-card.wide2_1 { order: 13; }
-
- .product-card.wide3 { order: 21; }
- .product-card.tall { order: 22; }
-
- .product-card.wide3 { order: 31; }
-
- .product-card.full-width { order: 41; flex-basis: 100%; }
-
- .main__content {
- gap: 20px;
- .products {
- flex: 0 0 35%;
- .products__image {
- width: 250px;
- height: 180px;
- }
- }
- .order {
- flex: 0 0 60%;
- padding: 30px;
-
- .order__title {
- font-size: 24px;
- }
-
- .order__section-title {
- font-size: 16px;
- }
- }
- }
-
- .solutions-slider {
- &__slide {
- .solution-text-overlay {
- top: 10%;
- left: 5%;
- h2 {
- font-size: 26px;
- margin-bottom: 5px;
- line-height: 1.2;
- }
- p {
- font-size: 18px;
- line-height: 1.2;
- }
- }
- .solution-image-link {
- bottom: 70px;
- padding: 10px 25px;
- font-size: 14px;
- }
- }
- }
-
- .product__image {
- width: 350px;
- height: 250px;
- }
-
- .product__thumbnail img {
- width: 170px;
- height: 120px;
- }
-}
-
-@media (max-width: 1024px) {
- .main__content {
- gap: 25px;
- .products {
- flex: 0 0 30%;
- .products__image {
- width: 200px;
- height: 150px;
- }
-
- .products__name {
- font-size: 16px;
- }
-
- .products__price {
- font-size: 16px;
- }
- }
- .order {
- flex: 0 0 60%;
- padding: 25px;
-
- .order__title {
- font-size: 22px;
- }
-
- .form__input {
- padding: 12px 14px;
- font-size: 14px;
- }
-
- .promo__btn {
- padding: 10px 40px;
- font-size: 16px;
- }
- }
- }
-}
-
-@media (max-width: 768px) {
- .container { padding: 0 15px; }
-
- .delivery-section {
- flex-direction: column;
- align-items: center;
- }
-
- .delivery-card {
- min-width: 100%;
- max-width: 100%;
- }
-
- .delivery-content h1 {
- font-size: 32px;
- }
-
- .warranty-overview {
- flex-direction: column;
- align-items: center;
- }
-
- .warranty-card {
- max-width: 100%;
- width: 100%;
- }
-
- .coverage-section {
- flex-direction: column;
- }
-
- .warranty-content h1 {
- font-size: 32px;
- }
-
- .header__top .container,
- .header__bottom .container,
- .hero__content,
- .advantages__header,
- .about__content,
- .advantages__items,
- .promo-images,
- .stats__items,
- .faq__items,
- .catalog-wrapper,
- .main__content {
- flex-direction: column;
- gap: 30px;
- }
-
- .search-catalog {
- order: 3;
- width: 100%;
- max-width: 100%;
- }
-
- .nav-list {
- flex-wrap: wrap;
- justify-content: center;
- gap: 15px;
- }
-
- .hero {
- &__image-block {
- flex: none;
- max-width: 400px;
- height: 400px;
- }
- &__circle {
- width: 380px;
- height: 380px;
- }
- &__text-block {
- flex: none;
- padding-left: 0;
- text-align: center;
- h1 { font-size: 32px; }
- .hero__usp-text {
- padding-left: 0;
- justify-content: center;
- &::before { display: none; }
- }
- .btn.primary-btn { margin-left: 0; }
- }
- }
-
- .advantages__header h2,
- .faq h2 { font-size: 28px; }
-
- .faq-item,
- .stat-item {
- flex: none;
- .flex-center();
- text-align: center;
- }
-
- .stats .container { justify-content: center; }
- .catalog-dropdown__menu { width: 200px; }
-
- .catalog-sidebar { width: 100%; flex: none; }
- .products-container { gap: 15px; }
-
- .product-card.small,
- .product-card.small1,
- .product-card.large,
- .product-card.wide,
- .product-card.wide1,
- .product-card.wide2,
- .product-card.wide2_1,
- .product-card.wide3,
- .product-card.wide4,
- .product-card.tall,
- .product-card.full-width {
- flex: 0 0 100%;
- max-width: 100%;
- height: 250px;
- margin: 0;
-
- .product-image-container {
- height: 200px;
- }
- }
-
- .main__content {
- flex-direction: column;
- gap: 20px;
-
- .products,
- .order {
- flex: 0 0 100%;
- width: 100%;
- }
-
- .products {
- .products__item {
- flex-direction: column;
- text-align: center;
- gap: 15px;
- }
-
- .products__image {
- width: 100%;
- height: 200px;
- justify-content: center;
- }
-
- .products__details {
- min-height: auto;
- align-items: center;
- }
-
- .products__controls {
- justify-content: center;
- margin-top: 15px;
- }
-
- .products__cart-icon {
- margin-left: 0;
- }
- }
-
- .order {
- padding: 20px;
-
- .order__title {
- font-size: 20px;
- text-align: center;
- }
-
- .order__total {
- text-align: center;
- }
-
- .form__radio-group {
- flex-direction: column;
- gap: 15px;
- }
-
- .form__radio-label {
- flex: none;
- justify-content: flex-start;
- }
-
- .promo {
- flex-direction: column;
- gap: 10px;
-
- &__btn {
- width: 100%;
- padding: 12px;
- }
- }
-
- .order-btn {
- padding: 12px;
- font-size: 16px;
- }
-
- .services {
- flex-direction: column;
- align-items: center;
- }
- }
- }
-
- .product-image-container { height: 200px; }
- .product-card.tall .product-image-container,
- .product-card.large .product-image-container { height: 250px; }
-
- .profile-page-main {
- .profile-container {
- flex-direction: column;
- min-height: auto;
- max-width: 100%;
- box-shadow: none;
- }
- .profile-left-col {
- flex: none;
- width: 100%;
- height: 100px;
- .flex-center();
- padding: 0;
- }
- .profile-right-col {
- flex: none;
- width: 100%;
- padding: 30px 20px;
- }
- .profile-form-block { max-width: 100%; }
- }
-
- .form__row { flex-direction: column; }
- .form__input--half { flex: 0 0 100%; max-width: 100%; }
- .services { flex-direction: column; align-items: center; }
-
- .services-section {
- padding: 40px 0;
- }
-
- .service-card {
- padding: 30px 20px;
- min-height: 180px;
-
- &--green,
- &--beige {
- max-width: 100%;
- }
-
- &__title {
- font-size: 20px;
- }
-
- &__text {
- font-size: 14px;
- }
- }
- .solutions-slider {
- margin: 20px auto;
- &__slide {
- .solution-text-overlay {
- top: 8%;
- left: 4%;
- h2 {
- font-size: 20px;
- margin-bottom: 3px;
- line-height: 1.1;
- }
- p {
- font-size: 15px;
- line-height: 1.1;
- }
- }
- .solution-image-link {
- bottom: 90px;
- padding: 8px 20px;
- font-size: 13px;
- }
- }
- }
-}
-
-// Стили для ошибок полей
-.error-input {
- border-color: #ff4444 !important;
- box-shadow: 0 0 0 1px #ff4444;
-}
-
-.field-error {
- color: #ff4444;
- font-size: 12px;
- margin-top: 5px;
- margin-bottom: 10px;
-}
-
-// Стили для сообщений
-.message {
- padding: 15px;
- margin: 20px 0;
- border-radius: 5px;
- display: none;
-}
-
-.message.error {
- background-color: #ffebee;
- color: #c62828;
- border: 1px solid #ffcdd2;
-}
-
-.message.success {
- background-color: #e8f5e9;
- color: #2e7d32;
- border: 1px solid #c8e6c9;
-}
-
-// Добавьте в конец файла
-.access-denied {
- text-align: center;
- padding: 80px 20px;
- background: white;
- border-radius: 10px;
- box-shadow: 0 5px 15px rgba(0,0,0,0.1);
- margin: 50px 0;
-
- h2 {
- color: #dc3545;
- margin-bottom: 30px;
- font-size: 28px;
- }
-
- p {
- color: #666;
- margin-bottom: 40px;
- font-size: 18px;
- line-height: 1.6;
- }
-
- .btn {
- margin: 5px;
- min-width: 200px;
- }
-}
-// =======================
-// === ПРОФИЛЬ ПОЛЬЗОВАТЕЛЯ ===
-// =======================
-
-.user-profile-dropdown {
- position: relative;
- display: inline-block;
-
- &__toggle {
- display: flex;
- align-items: center;
- gap: 8px;
- cursor: pointer;
- padding: 8px 12px;
- border-radius: 4px;
- transition: all 0.3s ease;
-
- &:hover {
- background-color: rgba(0, 0, 0, 0.05);
- }
-
- .user-avatar {
- width: 32px;
- height: 32px;
- border-radius: 50%;
- background-color: @color-primary;
- color: @color-text-light;
- .flex-center();
- font-weight: bold;
- }
-
- .user-info {
- display: flex;
- flex-direction: column;
-
- .user-email {
- font-size: 12px;
- color: #666;
- max-width: 150px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .user-status {
- font-size: 10px;
- padding: 2px 6px;
- border-radius: 10px;
- text-transform: uppercase;
-
- &.admin {
- background-color: #617365;
- color: white;
- }
-
- &.user {
- background-color: #28a745;
- color: white;
- }
- }
- }
- }
-
- &__menu {
- .menu-base();
- width: 220px;
- top: 100%;
- right: 0;
-
- .user-details {
- padding: 15px;
- border-bottom: 1px solid #eee;
-
- .user-name {
- font-weight: bold;
- margin-bottom: 5px;
- }
-
- .user-registered {
- font-size: 11px;
- color: #999;
- }
- }
-
- ul {
- padding: 10px 0;
-
- li {
- padding: 8px 15px;
- cursor: pointer;
- display: flex;
- align-items: center;
- gap: 10px;
- transition: background-color 0.3s ease;
-
- &:hover {
- background-color: #f5f5f5;
- }
-
- &.logout {
- color: #dc3545;
- border-top: 1px solid #eee;
- margin-top: 5px;
- padding-top: 12px;
-
- &:hover {
- background-color: #ffe6e6;
- }
- }
- }
- }
- }
-
- &:hover &__menu {
- display: block;
- }
-}
-
-// =======================
-// === КАРТОЧКА ТОВАРА ===
-// =======================
-
-.product-image-container {
- position: relative;
- overflow: hidden;
- margin-bottom: 0;
- padding: 0;
- height: 250px;
- .flex-center();
-
- &:hover {
- .product-overlay-info {
- opacity: 1;
- transform: translateY(0);
- }
- }
-}
-
-.product-overlay-info {
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
- color: white;
- padding: 15px;
- opacity: 0;
- transform: translateY(10px);
- transition: all 0.3s ease;
-
- .product-overlay-name {
- font-weight: bold;
- font-size: 14px;
- margin-bottom: 5px;
- }
-
- .product-overlay-price {
- font-size: 16px;
- font-weight: bold;
-
- .old-price {
- text-decoration: line-through;
- font-size: 12px;
- color: #ccc;
- margin-right: 5px;
- }
-
- .current-price {
- color: #ffd700;
- }
- }
-
- .product-overlay-category {
- font-size: 11px;
- opacity: 0.8;
- margin-top: 3px;
- }
-
- .product-overlay-stock {
- font-size: 11px;
- margin-top: 5px;
-
- &.out-of-stock {
- color: #ff6b6b;
- }
-
- i {
- margin-right: 5px;
- }
- }
-}
-
-.product-card-details {
- padding: 15px;
- background: white;
- flex-grow: 1;
- display: flex;
- flex-direction: column;
-
- .product-card-name {
- font-weight: bold;
- font-size: 16px;
- margin-bottom: 8px;
- color: @color-text-dark;
- }
-
- .product-card-description {
- font-size: 13px;
- color: #777;
- margin-bottom: 10px;
- flex-grow: 1;
- }
-
- .product-card-attributes {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-bottom: 10px;
-
- .attribute {
- font-size: 11px;
- background: #f5f5f5;
- padding: 3px 8px;
- border-radius: 12px;
- color: #666;
-
- i {
- margin-right: 3px;
- }
- }
- }
-
- .product-card-price {
- margin-bottom: 10px;
-
- .old-price {
- text-decoration: line-through;
- font-size: 14px;
- color: #999;
- margin-right: 8px;
- }
-
- .current-price {
- font-size: 18px;
- font-weight: bold;
- color: @color-button;
- }
- }
-
- .add-to-cart-btn {
- width: 100%;
- padding: 8px;
- font-size: 14px;
- }
-
- .admin-actions {
- display: flex;
- gap: 5px;
- margin-top: 10px;
-
- .admin-btn {
- flex: 1;
- font-size: 12px;
- padding: 6px;
-
- &.delete-btn {
- background: #dc3545;
-
- &:hover {
- background: #c82333;
- }
- }
- }
- }
-}
-
-// =======================
-// === ПРОФИЛЬ ПОЛЬЗОВАТЕЛЯ ===
-// =======================
-
-.user-profile-dropdown {
- position: relative;
- display: inline-block;
-
- .user-profile-toggle {
- display: flex;
- align-items: center;
- gap: 10px;
- cursor: pointer;
- padding: 8px 12px;
- border-radius: 4px;
- transition: all 0.3s ease;
-
- &:hover {
- background-color: rgba(0, 0, 0, 0.05);
-
- .dropdown-arrow {
- transform: rotate(180deg);
- }
- }
-
- .user-avatar {
- width: 36px;
- height: 36px;
- border-radius: 50%;
- background: linear-gradient(135deg, #617365 0%, #453227 100%);
- color: @color-text-light;
- .flex-center();
- font-weight: bold;
- font-size: 16px;
- box-shadow: 0 2px 5px rgba(0,0,0,0.2);
- }
-
- .user-info {
- display: flex;
- flex-direction: column;
-
- .user-email {
- font-size: 12px;
- color: #666;
- max-width: 120px;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
- .user-status {
- font-size: 10px;
- padding: 2px 8px;
- border-radius: 12px;
- text-transform: uppercase;
- font-weight: bold;
- text-align: center;
- margin-top: 2px;
-
- &.admin {
- background-color: #617365;
- color: white;
- border: 1px solid #617365;
- }
-
- &.user {
- background-color: #28a745;
- color: white;
- border: 1px solid #28a745;
- }
- }
- }
-
- .dropdown-arrow {
- font-size: 10px;
- color: #666;
- transition: transform 0.3s ease;
- }
- }
-
- .user-profile-menu {
- .menu-base();
- width: 280px;
- top: 100%;
- right: 0;
- margin-top: 10px;
- padding: 0;
- overflow: hidden;
-
- .user-profile-header {
- padding: 20px;
- background: linear-gradient(135deg, #617365 0%, #453227 100%);
- color: white;
-
- .user-profile-name {
- font-weight: bold;
- margin-bottom: 8px;
- display: flex;
- align-items: center;
- gap: 10px;
- font-size: 16px;
- }
-
- .user-profile-details {
- small {
- display: block;
- opacity: 0.8;
- margin-bottom: 5px;
- font-size: 11px;
-
- i {
- margin-right: 5px;
- width: 14px;
- text-align: center;
- }
- }
- }
- }
-
- .user-profile-links {
- list-style: none;
- padding: 10px 0;
-
- li {
- border-bottom: 1px solid #f0f0f0;
-
- &:last-child {
- border-bottom: none;
- }
-
- a {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 12px 20px;
- color: #333;
- transition: all 0.3s ease;
-
- &:hover {
- background-color: #f8f9fa;
- color: @color-primary;
- text-decoration: none;
-
- i {
- transform: scale(1.1);
- }
- }
-
- i {
- width: 20px;
- text-align: center;
- font-size: 14px;
- color: #617365;
- transition: transform 0.3s ease;
- }
-
- span {
- flex-grow: 1;
- }
- }
- }
-
- .logout-item {
- border-top: 2px solid #f0f0f0;
- margin-top: 5px;
-
- a {
- color: #dc3545;
-
- &:hover {
- background-color: #ffe6e6;
- color: #c82333;
-
- i {
- color: #dc3545;
- }
- }
- }
- }
- }
- }
-
- &:hover .user-profile-menu {
- display: block;
- animation: fadeIn 0.3s ease;
- }
-}
-
-@keyframes fadeIn {
- from {
- opacity: 0;
- transform: translateY(-10px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-// Для мобильных устройств
-@media (max-width: 768px) {
- .user-profile-dropdown {
- .user-profile-toggle {
- .user-info {
- display: none;
- }
-
- .dropdown-arrow {
- display: none;
- }
- }
-
- .user-profile-menu {
- width: 250px;
- right: -50px;
- }
- }
-}
-// Добавьте в конец файла
-.unavailable-product {
- position: relative;
- opacity: 0.6;
- filter: grayscale(0.7);
-
- &::before {
- content: "ТОВАР ЗАКОНЧИЛСЯ";
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- background: rgba(0, 0, 0, 0.85);
- color: white;
- padding: 15px 25px;
- border-radius: 5px;
- font-weight: bold;
- font-size: 16px;
- text-align: center;
- z-index: 100;
- white-space: nowrap;
- pointer-events: none;
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
- }
-
- .product-name-overlay {
- .name, .price {
- color: #999 !important;
- text-shadow: none !important;
- }
- }
-
- .add-to-cart-btn {
- display: none !important;
- }
-
- &:hover {
- transform: none !important;
- cursor: not-allowed;
- }
-}
-
-.out-of-stock-badge {
- position: absolute;
- top: 10px;
- left: 10px;
- background: #6c757d;
- color: white;
- padding: 5px 10px;
- border-radius: 4px;
- font-size: 12px;
- font-weight: bold;
- z-index: 10;
-}
-
-// Для админ-таблицы
-.admin-table tr.unavailable {
- background-color: #f8f9fa !important;
- opacity: 0.7;
-
- td {
- color: #999;
- }
-}
-
-.status-unavailable {
- background-color: #6c757d !important;
- color: white !important;
+@import "mixins.less";
+@import "стили_оформления.less";
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+html, body {
+ height: 100%;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ font-family: @font-main;
+ background-color: @color-secondary;
+ color: @color-text-dark;
+ line-height: 1.6;
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+}
+
+.container {
+ max-width: 1210px;
+ margin: 0 auto;
+ padding: 0 20px;
+}
+
+ul {
+ list-style: none;
+}
+
+a {
+ text-decoration: none;
+ color: inherit;
+ transition: all 0.3s ease;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-family: @font-heading;
+ margin: 0;
+}
+
+p, li, span {
+ font-family: @font-main;
+}
+
+.logo, .footer-logo {
+ font: bold 32px/1 @font-logo;
+ letter-spacing: 2px;
+ text-shadow: @shadow-dark;
+ flex-shrink: 0;
+}
+
+.btn {
+ padding: 12px 30px;
+ border: none;
+ cursor: pointer;
+ font-size: 14px;
+ text-transform: uppercase;
+ transition: all 0.3s ease;
+ font-family: @font-main;
+
+ &.primary-btn {
+ background-color: @color-button;
+ color: @color-text-light;
+
+ &:hover {
+ background-color: lighten(@color-button, 10%);
+ transform: translateY(-2px);
+ box-shadow: @shadow-light;
+ }
+ }
+}
+
+.number-circle {
+ .flex-center();
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ background-color: @color-button;
+ color: @color-text-light;
+ font-size: 16px;
+ font-weight: bold;
+ flex-shrink: 0;
+}
+
+.breadcrumbs {
+ font-size: 14px;
+ margin-bottom: 20px;
+ color: #666;
+
+ a {
+ color: #666;
+ opacity: 0.7;
+ &:hover { opacity: 1; }
+ }
+
+ .current-page {
+ font-weight: bold;
+ color: @color-text-dark;
+ }
+}
+
+.header {
+ background-color: @color-secondary;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+ z-index: 1000;
+
+ &__top, &__bottom {
+ padding: 15px 0;
+ .container {
+ .flex-between();
+ gap: 20px;
+ }
+ }
+
+ &__bottom {
+ padding: 10px 0;
+ border-top: 1px solid rgba(0, 0, 0, 0.05);
+
+ .catalog-link.active-catalog {
+ background-color: rgba(0, 0, 0, 0.08);
+ pointer-events: none;
+ }
+ }
+
+ .search-catalog {
+ .flex-center();
+ border: 2px solid @color-text-dark;
+ background-color: #fff;
+ max-width: 600px;
+ width: 100%;
+ margin: 0 auto;
+ overflow: hidden;
+
+ .catalog-dropdown {
+ position: relative;
+ background-color: #f8f8f8;
+ padding: 10px 15px 10px 25px;
+ font-size: 18px;
+ cursor: pointer;
+ border-right: 1px solid @color-text-dark;
+ .flex-center(10px);
+ width: 200px;
+ flex-shrink: 0;
+
+ &__menu {
+ .menu-base();
+ li {
+ padding: 8px 0;
+ cursor: pointer;
+ transition: color 0.3s;
+ border-bottom: 1px solid #f0f0f0;
+ &:last-child { border-bottom: none; }
+ &:hover { color: @color-accent; }
+ }
+ }
+ &:hover &__menu { display: block; }
+ }
+
+ .search-box {
+ .flex-center();
+ padding: 0 15px;
+ flex-grow: 1;
+ position: relative;
+ font-size: 15px;
+
+ input {
+ border: none;
+ padding: 10px 30px 10px 0;
+ outline: none;
+ font-size: 16px;
+ width: 100%;
+ text-align: left;
+ }
+
+ .search-icon {
+ font-size: 20px;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ }}
+
+ &__icons--top {
+ .flex-center(15px);
+ flex-shrink: 0;
+ .icon { .icon-base(); font-size: 20px;}
+ }
+
+ .nav-list {
+ .flex-center(30px);
+ font-size: 18px;
+ a {
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
+ &:hover { text-shadow: @shadow-dark; }
+ &.active {
+ border-bottom: 2px solid @color-button;
+ padding-bottom: 5px;
+ text-shadow: @shadow-dark;
+ }
+ &[href="#footer"] {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .catalog-link {
+ .flex-center(10px);
+ border-radius: 4px;
+ white-space: nowrap;
+ font-size: 18px;
+ padding: 10px 18px;
+ &:hover { background-color: rgba(0, 0, 0, 0.05); }
+ }
+
+ .header-phone {
+ font-weight: bold;
+ color: @color-button;
+ flex-shrink: 0;
+ }
+}
+
+.hero {
+ padding: 15px 0;
+
+ &__content {
+ .flex-center(50px);
+ min-height: 60vh;
+ align-items: center;
+ }
+
+ &__image-block {
+ position: relative;
+ flex: 0 0 40%;
+ max-width: 600px;
+ height: 600px;
+ .flex-center();
+
+ .hero__circle {
+ position: absolute;
+ width: 450px;
+ height: 450px;
+ background-color: @color-primary;
+ border-radius: 50%;
+ z-index: 1;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+
+ .hero__img {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ z-index: 2;
+ }
+ }
+
+ &__text-block {
+ flex: 0 0 60%;
+ padding-left: 50px;
+
+ h1 {
+ font-size: 42px;
+ font-weight: normal;
+ margin-bottom: 25px;
+ line-height: 1.3;
+ }
+
+ .hero__usp-text {
+ position: relative;
+ padding-left: 50px;
+ margin-bottom: 35px;
+ line-height: 1.7;
+ .flex-center();
+ justify-content: flex-start;
+ min-height: 40px;
+ font-size: 16px;
+
+ &::before {
+ content: "✓";
+ position: absolute;
+ left: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 32px;
+ height: 32px;
+ border: 2px solid @color-button;
+ background-color: transparent;
+ color: @color-button;
+ border-radius: 50%;
+ .flex-center();
+ font-size: 16px;
+ font-weight: bold;
+ }
+ }
+
+ .btn.primary-btn {
+ margin: 25px 0 0 50px;
+ padding: 14px 35px;
+ font-size: 15px;
+ }
+ }
+}
+
+.advantages {
+ padding: 30px 0 40px;
+
+ &__header {
+ display: flex;
+ align-items: center;
+ gap: 50px;
+ margin-bottom: 40px;
+ h2 {
+ font-size: 32px;
+ font-weight: normal;
+ flex: 0 0 30%;
+ }
+ }
+
+ &__items {
+ flex: 0 0 70%;
+ display: flex;
+ gap: 30px;
+ }
+
+ .advantage-item {
+ flex: 1;
+ text-align: left;
+ position: relative;
+ padding-top: 30px;
+
+ &__number {
+ .number-circle();
+ position: absolute;
+ top: 0;
+ left: 0;
+ }
+
+ h4 {
+ font-weight: 600;
+ margin-bottom: 10px;
+ }
+ }
+}
+
+.promo-images {
+ display: flex;
+ gap: 20px;
+ margin-top: 50px;
+
+ .promo-image-col {
+ position: relative;
+ overflow: hidden;
+ border-radius: 8px;
+ flex: 1;
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: translateY(-5px);
+ box-shadow: @shadow-light;
+ .image-overlay-text { background-color: rgba(0, 0, 0, 0.6); }
+ img { transform: scale(1.05); }
+ .image-overlay-text h4,
+ .image-overlay-text .overlay-link { transform: translateY(0); }
+ }
+
+ img {
+ width: 100%;
+ height: 350px;
+ object-fit: cover;
+ display: block;
+ transition: transform 0.5s ease;
+ }
+
+ .image-overlay-text {
+ .image-overlay();
+ h4 {
+ font-size: 24px;
+ text-transform: uppercase;
+ line-height: 1.2;
+ margin-bottom: 15px;
+ transform: translateY(20px);
+ transition: transform 0.3s ease;
+ }
+ }
+
+ .overlay-link {
+ display: inline-block;
+ text-transform: uppercase;
+ font-weight: bold;
+ border-radius: 3px;
+ margin-top: 15px;
+ padding: 10px 25px;
+ background-color: @color-button;
+ color: @color-text-light;
+ font-size: 12px;
+ transform: translateY(20px);
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: lighten(@color-button, 10%);
+ transform: translateY(-2px);
+ box-shadow: @shadow-light;
+ }
+ }
+ }
+}
+
+.about {
+ padding: 40px 0 80px;
+
+ &__content {
+ display: flex;
+ align-items: flex-start;
+ gap: 50px;
+ }
+
+ &__column {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ &--left { flex: 0 0 40%; margin-bottom: 30px; }
+ &--right {
+ flex: 0 0 60%;
+ .about__caption {
+ padding-right: 50px;
+ }
+ }
+ }
+
+ &__text-block {
+ margin-bottom: 30px;
+ h2 { margin-bottom: 15px; }
+ }
+
+ &__img {
+ width: 93%;
+ object-fit: cover;
+ display: block;
+ &--small { height: 300px; }
+ &--large { height: 450px; }
+ }
+
+ .text-justified {
+ text-align: justify;
+ color: #555;
+ }
+}
+
+.solutions {
+ padding: 0;
+ background-color: @color-secondary;
+
+ &-slider {
+ position: relative;
+ width: 100%;
+ max-width: 1200px;
+ margin: 40px auto;
+ border-radius: 8px;
+ overflow: hidden;
+
+ &__slides {
+ display: flex;
+ width: 200%;
+ height: 100%;
+ animation: slideLeftRight 10s infinite ease-in-out;
+ }
+
+ &__slide {
+ width: 50%;
+ flex-shrink: 0;
+ position: relative;
+ overflow: hidden;
+ transition: transform 0.5s ease, box-shadow 0.5s ease;
+
+ &:hover {
+ transform: scale(1.02);
+ box-shadow: 0 10px 25px rgba(0,0,0,0.3);
+ .solution-img {
+ transform: scale(1.05);
+ filter: brightness(0.8);
+ }
+ .solution-text-overlay {
+ opacity: 1;
+ transform: translateY(-5px);
+ }
+ .solution-image-link {
+ transform: translateX(-50%) translateY(-6px);
+ background-color: rgba(255,255,255,0.9);
+ color: @color-text-dark;
+ }
+ }
+ }
+
+ .solution-img {
+ width: 100%;
+ height: auto;
+ object-fit: cover;
+ display: block;
+ }
+
+ .solution-text-overlay {
+ position: absolute;
+ top: 15%;
+ left: 8%;
+ color: #493131;
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.6);
+ z-index: 2;
+ opacity: 0.9;
+ transition: opacity 0.5s ease, transform 0.5s ease;
+ h2 {
+ font-size: 35px;
+ text-transform: uppercase;
+ margin-bottom: 10px;
+ }
+ p {
+ font-size: 25px;
+ text-transform: uppercase;
+ }
+ }
+
+ .solution-image-link {
+ position: absolute;
+ bottom: 40px;
+ left: 50%;
+ transform: translateX(-50%);
+ padding: 12px 30px;
+ border: 2px solid @color-text-light;
+ color: #493131;
+ text-transform: uppercase;
+ font-size: 16px;
+ font-weight: bold;
+ background: transparent;
+ transition: 0.4s ease;
+ z-index: 2;
+ &:hover {
+ background: @color-text-light;
+ color: @color-text-dark;
+ transform: translateX(-50%) translateY(-2px);
+ }
+ }
+ }
+}
+
+@keyframes slideLeftRight {
+ 0%, 40% { transform: translateX(0); }
+ 50%, 90% { transform: translateX(-50%); }
+ 100% { transform: translateX(0); }
+}
+
+.stats {
+ padding: 0;
+ margin-top: 20px;
+
+ .container {
+ display: flex;
+ justify-content: flex-end;
+ }
+
+ &__items {
+ display: flex;
+ gap: 20px;
+ .stat-item {
+ text-align: left;
+ .stat-number {
+ font-size: 36px;
+ font-weight: bold;
+ color: @color-text-dark;
+ margin-bottom: 5px;
+ }
+ .stat-label { color: @color-text-dark; }
+ }
+ }
+}
+
+.faq {
+ padding: 50px 0;
+
+ h2 {
+ text-align: left;
+ font-size: 32px;
+ font-weight: normal;
+ margin-bottom: 40px;
+ }
+
+ &__items {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 40px 60px;
+ margin-bottom: 40px;
+ }
+
+ .faq-item {
+ flex: 0 0 calc(50% - 30px);
+ .flex-center(15px);
+ align-items: flex-start;
+ &__content h4 {
+ font-weight: 600;
+ margin-bottom: 10px;
+ }
+ }
+
+ .btn.primary-btn {
+ display: block;
+ width: 100%;
+ margin: 20px auto 80px;
+ }
+}
+
+.catalog-main {
+ padding: 30px 0 60px;
+ background-color: lighten(@color-secondary, 5%);
+}
+
+.catalog-wrapper {
+ display: flex;
+ gap: 20px;
+}
+
+.catalog-sidebar {
+ flex: 0 0 250px;
+ background-color: #fff;
+ padding: 20px;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+ height: fit-content;
+}
+
+.filter-group {
+ margin-bottom: 30px;
+}
+
+.filter-title {
+ font-size: 16px;
+ font-weight: bold;
+ margin-bottom: 15px;
+ text-transform: uppercase;
+}
+
+.filter-list li {
+ padding: 5px 0;
+ font-size: 16px;
+ a {
+ color: #555;
+ transition: color 0.2s;
+ &:hover { color: @color-accent; }
+ &.active-category {
+ font-weight: bold;
+ color: @color-primary;
+ }
+ }
+}
+
+.price-range {
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ width: 100%;
+
+ .range-slider {
+ width: 100%;
+
+ input[type="range"] {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 100%;
+ height: 5px;
+ background: @color-primary;
+ border-radius: 5px;
+ outline: none;
+ margin: 0;
+
+ &::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 20px;
+ height: 20px;
+ background: @color-accent;
+ border: 2px solid #fff;
+ border-radius: 50%;
+ cursor: pointer;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: scale(1.1);
+ background: lighten(@color-accent, 10%);
+ }
+ }
+
+ &::-moz-range-thumb {
+ width: 20px;
+ height: 20px;
+ background: @color-accent;
+ border: 2px solid #fff;
+ border-radius: 50%;
+ cursor: pointer;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: scale(1.1);
+ background: lighten(@color-accent, 10%);
+ }
+ }
+ }
+ }
+
+ .price-display {
+ font-size: 14px;
+ font-weight: bold;
+ text-align: center;
+ color: @color-text-dark;
+ padding: 10px;
+ background: #f8f8f8;
+ border-radius: 4px;
+ }
+}
+
+.filter-options {
+ list-style: none;
+ li {
+ display: flex;
+ align-items: center;
+ padding: 4px 0;
+ font-size: 14px;
+ }
+ label {
+ margin-left: 10px;
+ cursor: pointer;
+ color: #555;
+ }
+ input[type="checkbox"] {
+ width: 15px;
+ height: 15px;
+ cursor: pointer;
+ accent-color: @color-primary;
+ &:checked + label {
+ font-weight: bold;
+ color: @color-primary;
+ }
+ }
+}
+
+.filter-apply-btn {
+ width: 100%;
+ margin-top: 20px;
+}
+
+.catalog-products {
+ flex-grow: 1;
+}
+
+.products-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 20px;
+}
+
+.product-card {
+ background-color: #fff;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ transition: transform 0.3s ease;
+ box-sizing: border-box;
+
+ &:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
+ .product-img { transform: scale(1.05); }
+ }
+}
+
+.product-image-container {
+ position: relative;
+ overflow: hidden;
+ margin-bottom: 0;
+ padding: 0;
+ height: 250px;
+ .flex-center();
+}
+
+.product-img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+ transition: transform 0.3s ease;
+ margin: 0;
+}
+
+.product-img1 {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ transition: transform 0.3s ease;
+ margin: 0;
+}
+
+.product-discount {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ background-color: @color-button;
+ color: @color-text-light;
+ padding: 3px 8px;
+ font-size: 12px;
+ font-weight: bold;
+ z-index: 10;
+}
+
+.product-wishlist-icon {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ color: #333;
+ font-size: 18px;
+ cursor: pointer;
+ transition: color 0.3s ease;
+ z-index: 10;
+ &:hover { color: @color-accent; }
+}
+
+.product-name {
+ font-size: 16px;
+ font-weight: bold;
+ margin-bottom: 5px;
+}
+
+.product-details {
+ font-size: 13px;
+ color: #777;
+ margin-bottom: 10px;
+ flex-grow: 1;
+}
+
+.product-price {
+ font-size: 18px;
+ font-weight: bold;
+ color: @color-button;
+}
+
+.product-card.small { flex: 0 0 300px; max-width: 300px; height: 200px; }
+.product-card.small1 { flex: 0 0 320px; max-width: 320px; height: 250px;width: 320px; }
+.product-card.large { flex: 0 0 580px; max-width: 580px; height: 380px; }
+.product-card.wide { flex: 0 0 240px; max-width: 240px; height: 250px; }
+.product-card.wide1 { flex: 0 0 350px; max-width: 350px; height: 250px; }
+.product-card.wide2 { flex: 0 0 560px; max-width: 560px; height: 260px; }
+.product-card.wide2_1 { flex: 0 0 560px; max-width: 560px; height: 260px; margin: -280px 0 0; }
+.product-card.wide3 {
+ flex: 0 0 320px; max-width: 320px; height: 540px;
+ .product-image-container { height: 580px; }
+}
+.product-card.wide4 {
+ flex: 0 0 545px; max-width: 545px; margin: -270px 0 0; height: 250px;
+ .product-image-container { padding: 0; justify-content: flex-start; }
+ .product-img { margin-left: 0; align-self: flex-start; object-position: left center; }
+}
+.product-card.tall { flex: 0 0 300px; max-width: 300px; margin: -180px 0 0; height: 430px; }
+.product-card.full-width { flex: 0 0 100%; margin: -20px 0 0; max-width: 900px; height: 300px;}
+
+.product-card.full-width {
+ flex: 0 0 100%;
+ max-width: 100%;
+ height: 300px;
+
+ .product-image-container {
+ height: 100%;
+ padding: 0;
+ margin: 0;
+
+ .product-img1 {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ margin: 0;
+ padding: 0;
+ }
+ }
+}
+
+.product-card.tall .product-image-container,
+.product-card.large .product-image-container { height: 430px; }
+
+.product__section {
+ display: grid;
+ grid-template-columns: 350px 1fr;
+ gap: 30px;
+ margin: 40px 0;
+ padding: 20px 0;
+}
+
+.product__gallery {
+ position: relative;
+ width: 350px !important;
+ max-width: 350px !important;
+}
+
+.product__main-image {
+ width: 350px !important;
+ height: 350px !important;
+ min-width: 350px !important;
+ min-height: 350px !important;
+ max-width: 350px !important;
+ max-height: 350px !important;
+ background: #f8f9fa;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 15px;
+ border: 1px solid #e9ecef;
+ flex-shrink: 0;
+
+ img {
+ width: 100% !important;
+ height: 100% !important;
+ max-width: 350px !important;
+ max-height: 350px !important;
+ object-fit: contain !important;
+ transition: transform 0.3s ease;
+
+ &:hover {
+ transform: scale(1.05);
+ }
+ }
+}
+
+.product__thumbnails {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 8px;
+}
+
+.product__thumbnail {
+ height: 70px;
+ background: #f8f9fa;
+ border: 2px solid #e9ecef;
+ border-radius: 6px;
+ overflow: hidden;
+ cursor: pointer;
+ padding: 0;
+ transition: all 0.3s ease;
+
+ &:hover {
+ border-color: #453227;
+ box-shadow: 0 2px 8px rgba(69, 50, 39, 0.2);
+ }
+
+ &.active {
+ border-color: #453227;
+ box-shadow: 0 0 0 2px rgba(69, 50, 39, 0.1);
+ }
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+}
+
+.product__info {
+ padding: 0;
+
+ h1 {
+ font-size: 32px;
+ font-weight: 600;
+ color: #212529;
+ margin-bottom: 20px;
+ line-height: 1.3;
+ }
+}
+
+.product__rating {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 25px;
+ padding-bottom: 25px;
+ border-bottom: 1px solid #e9ecef;
+
+ .stars {
+ display: flex;
+ gap: 4px;
+
+ .star {
+ font-size: 18px;
+ color: #ffc107;
+
+ &.filled {
+ color: #ffc107;
+ }
+ }
+ }
+
+ .rating-value {
+ font-weight: 600;
+ font-size: 16px;
+ color: #212529;
+ }
+
+ .reviews-count {
+ color: #6c757d;
+ font-size: 14px;
+ }
+}
+
+.product__price {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ margin-bottom: 25px;
+ flex-wrap: wrap;
+
+ .current-price {
+ font-size: 36px;
+ font-weight: 700;
+ color: #453227;
+ }
+
+ .old-price {
+ font-size: 24px;
+ color: #6c757d;
+ text-decoration: line-through;
+ }
+
+ .discount-badge {
+ background: #dc3545;
+ color: white;
+ padding: 6px 12px;
+ border-radius: 6px;
+ font-size: 14px;
+ font-weight: 600;
+ }
+}
+
+.product__color-selector {
+ display: flex;
+ gap: 10px;
+ margin-bottom: 40px;
+}
+
+.product__color-option {
+ width: 45px;
+ height: 45px;
+ border-radius: 50%;
+ border: 2px solid transparent;
+ cursor: pointer;
+ transition: transform 0.3s ease;
+
+ &:hover{
+ transform: translateY(-2px);
+ }
+}
+
+.product__color-option.active {
+ border-color: @color-primary;
+}
+
+.product__description {
+ margin-bottom: 65px;
+ line-height: 1.5;
+}
+
+.product__details-link {
+ display: inline-block;
+ margin-bottom: 20px;
+ color: @color-primary;
+ font-weight: bold;
+}
+
+.product__purchase {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 35px;
+}
+
+.product__price {
+ font-size: 24px;
+ font-weight: bold;
+}
+
+.product__quantity {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.product__qty-btn {
+ width: 30px;
+ height: 30px;
+ background: @color-button;
+ color: @color-text-light;
+ border: none;
+ border-radius: 50%;
+ cursor: pointer;
+ font-weight: bold;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background: lighten(@color-button, 10%);
+ transform: scale(1.1);
+ }
+}
+
+.product__qty-value {
+ font-weight: bold;
+ min-width: 30px;
+ text-align: center;
+}
+
+.product__actions {
+ display: flex;
+ gap: 15px;
+}
+
+.product__btn {
+ flex: 1;
+ padding: 12px 20px;
+ border: none;
+ border-radius: 4px;
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: @shadow-light;
+ }
+}
+
+.product__btn.primary {
+ background: @color-button;
+ color: @color-text-light;
+
+ &:hover {
+ background: lighten(@color-button, 10%);
+ }
+}
+
+.product__btn.secondary {
+ background: transparent;
+ border: 1px solid @color-button;
+ color: @color-button;
+
+ &:hover {
+ background: @color-button;
+ color: @color-text-light;
+ }
+}
+
+.similar {
+ margin: 60px 0;
+}
+
+.similar__title {
+ margin-bottom: 30px;
+ font-size: 28px;
+ font-weight: bold;
+}
+
+.similar__grid {
+ display: flex;
+ gap: 25px;
+ flex-wrap: wrap;
+ justify-content: space-between;
+}
+
+.similar__card {
+ flex: 0 0 calc(33.333% - 17px);
+ min-width: 320px;
+ background: @color-secondary;
+ border-radius: 12px;
+ overflow: hidden;
+ transition: all 0.3s ease;
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
+
+ &:hover {
+ transform: translateY(-8px);
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+ }
+}
+
+.similar__card-image {
+ height: 300px;
+ overflow: hidden;
+ background: white;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ transition: transform 0.3s ease;
+
+ &:hover {
+ transform: scale(1.05);
+ }
+ }
+}
+
+.similar__card-content {
+ padding: 25px;
+}
+
+.similar__card-title {
+ font-weight: bold;
+ margin-bottom: 10px;
+ font-size: 20px;
+ color: @color-text-dark;
+}
+
+.similar__card-description {
+ font-size: 15px;
+ margin-bottom: 15px;
+ color: #666;
+ line-height: 1.5;
+}
+
+.similar__card-price {
+ font-weight: bold;
+ font-size: 22px;
+ color: @color-button;
+}
+
+@media (max-width: 1024px) {
+ .similar {
+ &__card {
+ flex: 0 0 calc(50% - 13px);
+ min-width: 280px;
+ }
+ }
+}
+
+@media (max-width: 768px) {
+ .similar {
+ &__grid {
+ justify-content: center;
+ }
+
+ &__card {
+ flex: 0 0 100%;
+ max-width: 400px;
+ }
+ }
+}
+
+.main__content {
+ display: flex;
+ gap: 40px;
+ margin: 30px 0;
+
+ .products {
+ flex: 1;
+ }
+
+ .order {
+ flex: 0 0 65%;
+ padding: 40px;
+
+ &__header {
+ .flex-between();
+ margin-bottom: 20px;
+ }
+
+ &__title {
+ font-family: @font-logo;
+ font-size: 28px;
+ color: @color-text-dark;
+ margin: 0;
+ }
+
+ &__total {
+ font-weight: bold;
+ color: @color-text-dark;
+ }
+
+ &__section {
+ margin-bottom: 25px;
+ }
+
+ &__section-title {
+ font-family: @font-logo;
+ margin-bottom: 15px;
+ font-size: 18px;
+ color: @color-text-dark;
+ }
+ }
+}
+
+.products {
+ &__title {
+ font-family: @font-logo;
+ margin-bottom: 20px;
+ font-size: 24px;
+ color: @color-text-dark;
+ }
+
+ &__list {
+ .flex-column();
+ gap: 20px;
+ }
+
+ &__item {
+ background-color: @color-secondary;
+ border-radius: 8px;
+ padding: 20px;
+ display: flex;
+ gap: 15px;
+ border: 1px solid @color-secondary;
+ transition: transform 0.3s ease;
+ align-items: flex-start;
+ position: relative;
+
+ &:hover {
+ transform: translateY(-2px);
+ }
+ }
+
+ &__image {
+ width: 300px;
+ height: 200px;
+ border-radius: 4px;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ }
+
+ .product-img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ transition: transform 0.3s ease;
+ margin: 0;
+ }
+
+ &__details {
+ flex: 1;
+ .flex-column();
+ justify-content: space-between;
+ align-items: flex-start;
+ min-height: 200px;
+ }
+
+ &__name {
+ font-weight: bold;
+ margin-bottom: 5px;
+ color: @color-accent;
+ font-size: 18px;
+ font-family: @font-main;
+ }
+
+ &__price {
+ font-weight: bold;
+ font-size: 18px;
+ margin-bottom: 15px;
+ color: @color-text-dark;
+ }
+
+ &__controls {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ margin-top: auto;
+ width: 100%;
+ justify-content: space-between;
+ }
+
+ &__quantity {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ }
+
+ &__qty-btn {
+ width: 30px;
+ height: 30px;
+ background-color: @color-text-dark;
+ color: @color-text-light;
+ border: none;
+ border-radius: 50%;
+ cursor: pointer;
+ .flex-center();
+ font-family: @font-main;
+ font-weight: bold;
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: scale(1.1);
+ }
+ }
+
+ &__qty-value {
+ font-weight: bold;
+ min-width: 30px;
+ text-align: center;
+ font-size: 16px;
+ }
+
+ &__cart-icon {
+ background-color: transparent;
+ color: @color-text-dark;
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ border: 2px solid @color-text-dark;
+ margin-left: 20px;
+
+ &:hover {
+ transform: scale(1.1);
+ }
+
+ i { font-size: 18px; }
+ }
+}
+
+.form {
+ &__group { margin-bottom: 15px; }
+ &__label {
+ display: block;
+ margin-bottom: 5px;
+ font-weight: bold;
+ color: #000000;
+ }
+ &__input {
+ width: 100%;
+ padding: 14px 16px;
+ border: 2px solid #ccc;
+ font-family: @font-main;
+ font-size: 15px;
+ transition: border-color 0.3s ease;
+
+ &:focus {
+ border-color: @color-primary;
+ }
+
+ &:hover {
+ border-color: darken(#ccc, 10%);
+ }
+ &::placeholder {
+ font-style: italic;
+ color: #999;
+ }
+ }
+ &__row {
+ display: flex;
+ gap: 20px;
+ justify-content: space-between;
+ }
+ &__input--half {
+ width: 100%;
+ }
+ &__radio-group {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 20px;
+ margin-top: 20px;
+ }
+ &__radio-label {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ color: @color-text-dark;
+ position: relative;
+ padding-left: 30px;
+ flex: 1;
+
+ &:hover {
+ .form__custom-radio {
+ border-color: lighten(@color-accent, 10%);
+ }
+ }
+ }
+ &__radio-input {
+ position: absolute;
+ opacity: 0;
+ cursor: pointer;
+ }
+ &__custom-radio {
+ position: absolute;
+ left: 0;
+ height: 20px;
+ width: 20px;
+ background-color: @color-secondary;
+ border: 2px solid @color-accent;
+ border-radius: 50%;
+ transition: border-color 0.3s ease;
+ }
+ &__radio-input:checked ~ &__custom-radio {
+ background-color: @color-accent;
+
+ &:after {
+ content: "";
+ position: absolute;
+ display: block;
+ top: 4px;
+ left: 4px;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: white;
+ }
+ }
+}
+
+.divider {
+ height: 1px;
+ background-color: #999;
+ margin: 20px 0;
+}
+
+.promo {
+ display: flex;
+ margin-bottom: 20px;
+
+ &__input {
+ flex: 1;
+ padding: 10px;
+ border: 1px solid #000;
+ background-color: @color-secondary;
+ font-family: @font-main;
+ height: auto;
+ min-height: 48px;
+
+ &:hover {
+ border-color: @color-primary;
+ }
+
+ &::placeholder {
+ font-style: italic;
+ color: #999;
+ }
+ }
+
+ &__btn {
+ background-color: @color-accent;
+ color: @color-secondary;
+ border: none;
+ padding: 10px 60px;
+ cursor: pointer;
+ font-family: @font-main;
+ font-size: 18px;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: lighten(@color-accent, 10%);
+ transform: translateY(-2px);
+ }
+ }
+}
+
+.summary {
+ margin-bottom: 20px;
+
+ &__item {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+ color: @color-text-dark;
+
+ &.total {
+ font-weight: bold;
+ font-size: 18px;
+ padding-top: 10px;
+ margin-top: 10px;
+ }
+ }
+}
+
+.order-btn {
+ width: 100%;
+ background-color: @color-accent;
+ color: @color-secondary;
+ border: none;
+ padding: 15px;
+ border-radius: 4px;
+ font-size: 18px;
+ cursor: pointer;
+ margin-bottom: 10px;
+ font-family: @font-main;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: lighten(@color-accent, 10%);
+ transform: translateY(-2px);
+ }
+}
+
+.privacy {
+ display: flex;
+ gap: 8px;
+ font-size: 16px;
+ color: #666;
+ margin-bottom: 20px;
+
+ input[type="checkbox"] {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+ }
+}
+
+.services {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ flex-wrap: wrap;
+ gap: 24px;
+ margin-bottom: 24px;
+
+ &__title {
+ font-family: @font-logo;
+ margin-bottom: 10px;
+ font-size: 18px;
+ color: @color-text-dark;
+ display: block;
+ width: 100%;
+ }
+
+ &__item {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 8px;
+ color: @color-text-dark;
+ width: 100%;
+ }
+}
+
+.cart-icon {
+ position: relative;
+}
+
+.cart-count {
+ position: absolute;
+ top: -8px;
+ right: -8px;
+ background: @color-accent;
+ color: @color-text-light;
+ border-radius: 50%;
+ width: 18px;
+ height: 18px;
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.form__input.error {
+ border-color: #ff4444;
+ box-shadow: 0 0 5px rgba(255, 68, 68, 0.3);
+}
+
+.empty-cart {
+ text-align: center;
+ padding: 40px;
+ color: #666;
+ font-size: 18px;
+}
+
+.profile-page-main {
+ .flex-center();
+ min-height: 80vh;
+ padding: 40px 0;
+ background-color: lighten(@color-secondary, 5%);
+ z-index: 1;
+
+ .profile-container {
+ display: flex;
+ width: 100%;
+ max-width: 1000px;
+ min-height: 600px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+ background-color: @color-text-light;
+ }
+
+ .profile-left-col {
+ flex: 0 0 35%;
+ background-color: @color-primary;
+ color: @color-text-light;
+ display: flex;
+ justify-content: flex-start;
+ align-items: flex-start;
+ padding: 40px;
+ .logo {
+ font-size: 32px;
+ font-weight: normal;
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4);
+ color: @color-text-light;
+ }
+ }
+
+ .profile-right-col {
+ flex: 0 0 65%;
+ .flex-center();
+ padding: 40px;
+ .profile-form-block {
+ width: 100%;
+ max-width: 400px;
+ h2 {
+ font-size: 28px;
+ font-weight: normal;
+ margin-bottom: 40px;
+ text-align: left;
+ color: @color-text-dark;
+ }
+ }
+ }
+
+ .profile-form {
+ .input-group {
+ margin-bottom: 20px;
+ label {
+ display: block;
+ font-size: 12px;
+ font-weight: bold;
+ color: @color-text-dark;
+ margin-bottom: 5px;
+ text-transform: uppercase;
+ }
+ }
+
+ input[type="text"],
+ input[type="email"],
+ input[type="tel"] {
+ .input-base();
+ }
+
+ .password-link {
+ display: block;
+ text-align: left;
+ font-size: 13px;
+ color: @color-text-dark;
+ text-decoration: underline;
+ margin: 10px 0 20px;
+ &:hover {
+ color: @color-accent;
+ text-decoration: none;
+ }
+ }
+
+ .save-btn {
+ padding: 15px 30px;
+ border: none;
+ cursor: pointer;
+ font-size: 15px;
+ text-transform: uppercase;
+ transition: all 0.3s ease;
+ font-family: @font-main;
+ width: 100%;
+ margin-top: 20px;
+ background-color: @color-primary;
+ color: @color-text-light;
+
+ &:hover {
+ background-color: lighten(@color-primary, 10%);
+ transform: translateY(-2px);
+ box-shadow: @shadow-light;
+ }
+ }
+
+ .auth-actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 25px;
+ padding-top: 20px;
+ border-top: 1px solid #eee;
+
+ .auth-text {
+ font-size: 13px;
+ color: @color-text-dark;
+ }
+
+ .login-btn {
+ background-color: transparent;
+ color: @color-accent;
+ border: 1px solid @color-accent;
+ padding: 10px 25px;
+ font-size: 13px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: @color-primary;
+ color: @color-text-light;
+ }
+ }
+ }
+ }
+}
+
+.services-section {
+ padding: 60px 0;
+ background-color: @color-secondary;
+}
+
+.services__wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: 30px;
+}
+
+.services__top-row {
+ display: flex;
+ gap: 30px;
+ justify-content: center;
+
+ @media (max-width: 768px) {
+ flex-direction: column;
+ align-items: center;
+ }
+}
+
+.service-card {
+ border-radius: 8px;
+ padding: 40px;
+ min-height: 200px;
+ text-align: center;
+ transition: all 0.3s ease;
+
+ &:hover {
+ transform: translateY(-5px);
+ box-shadow: @shadow-light;
+ }
+
+ &--green {
+ background: @color-primary;
+ color: @color-text-light;
+ flex: 1;
+ max-width: 450px;
+ }
+
+ &--beige {
+ background: @color-beige;
+ color: @color-text-light;
+ width: 100%;
+ max-width: 930px;
+ margin: 0 auto;
+ }
+
+ &__title {
+ font-family: @font-logo;
+ font-size: 24px;
+ font-weight: bold;
+ margin-bottom: 15px;
+ text-transform: uppercase;
+ }
+
+ &__text {
+ font-family: @font-main;
+ font-size: 16px;
+ line-height: 1.6;
+ margin: 0;
+ }
+}
+
+.footer {
+ background-color: @color-primary;
+ color: black;
+ padding: 40px 0 10px;
+ position: relative;
+ z-index: 1000;
+
+ &::before {
+ content: '';
+ display: block;
+ position: absolute;
+ top: -80px;
+ left: 0;
+ width: 100%;
+ height: 1px;
+ visibility: hidden;
+ }
+
+ &__content {
+ display: flex;
+ gap: 20px;
+ padding-bottom: 30px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+ }
+
+ &__col {
+ flex: 1;
+ &--logo { flex: 1.5; }
+ h5 {
+ margin-bottom: 15px;
+ font-size: 14px;
+ text-transform: uppercase;
+ }
+ ul li {
+ margin-bottom: 8px;
+ a:hover { text-decoration: underline; }
+ }
+ .social-icons,
+ .payment-icons {
+ .flex-center(15px);
+ justify-content: flex-start;
+ margin-top: 10px;
+ }
+ .social-icons .icon {
+ .icon-base(20px, 1.1);
+ color: black;
+ &:hover { color: @color-accent; }
+ }
+ .payment-icons .pay-icon {
+ .icon-base(24px, 1.05);
+ color: black;
+ }
+ }
+
+ .copyright {
+ text-align: center;
+ font-size: 12px;
+ padding-top: 20px;
+ color: rgba(255, 255, 255, 0.6);
+ }
+}
+
+.delivery-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 40px 20px;
+}
+
+.delivery-content h1 {
+ font-family: @font-logo;
+ font-size: 42px;
+ text-align: center;
+ margin-bottom: 50px;
+ color: #453227;
+}
+
+.delivery-section {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 30px;
+ margin-bottom: 60px;
+}
+
+.delivery-card {
+ background: white;
+ padding: 30px;
+ border-radius: 12px;
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
+ text-align: center;
+ transition: transform 0.3s ease;
+ flex: 1;
+ min-width: 350px;
+ max-width: 400px;
+}
+
+.delivery-card:hover {
+ transform: translateY(-5px);
+}
+
+.delivery-icon {
+ font-size: 48px;
+ color: #617365;
+ margin-bottom: 20px;
+}
+
+.delivery-card h3 {
+ font-family: @font-logo;
+ font-size: 24px;
+ margin-bottom: 20px;
+ color: #453227;
+}
+
+.delivery-details {
+ text-align: left;
+}
+
+.detail-item {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 12px;
+ padding-bottom: 12px;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.detail-label {
+ font-weight: bold;
+ color: #333;
+}
+
+.detail-value {
+ color: #617365;
+ text-align: right;
+}
+
+.warranty-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 40px 20px;
+}
+
+.warranty-content h1 {
+ font-family: 'Anek Kannada', sans-serif;
+ font-size: 42px;
+ text-align: center;
+ margin-bottom: 50px;
+ color: #453227;
+}
+
+.warranty-overview {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 25px;
+ margin-bottom: 60px;
+}
+
+.warranty-card {
+ background: white;
+ padding: 30px;
+ border-radius: 12px;
+ text-align: center;
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
+ transition: transform 0.3s ease;
+ flex: 1;
+ min-width: 250px;
+ max-width: 280px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.warranty-card:hover {
+ transform: translateY(-5px);
+}
+
+.warranty-icon {
+ font-size: 48px;
+ color: #617365;
+ margin-bottom: 20px;
+}
+
+.warranty-card h3 {
+ font-family: 'Anek Kannada', sans-serif;
+ font-size: 20px;
+ margin-bottom: 15px;
+ color: #453227;
+}
+
+.warranty-period {
+ font-size: 24px;
+ font-weight: bold;
+ color: #617365;
+ margin-top: auto;
+}
+
+.coverage-section {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 40px;
+ margin-bottom: 60px;
+}
+
+.coverage-covered,
+.coverage-not-covered {
+ flex: 1;
+ min-width: 300px;
+}
+
+.coverage-section h2 {
+ font-family: 'Anek Kannada', sans-serif;
+ font-size: 24px;
+ margin-bottom: 25px;
+ color: #453227;
+}
+
+.coverage-list {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.coverage-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 15px;
+ padding: 20px;
+ border-radius: 8px;
+ background: white;
+ box-shadow: 0 3px 10px rgba(0,0,0,0.1);
+}
+
+.coverage-item.covered i {
+ color: #28a745;
+ font-size: 20px;
+ margin-top: 2px;
+ flex-shrink: 0;
+}
+
+.coverage-item.not-covered i {
+ color: #dc3545;
+ font-size: 20px;
+ margin-top: 2px;
+ flex-shrink: 0;
+}
+
+.coverage-text {
+ flex: 1;
+}
+
+.coverage-item h4 {
+ font-family: 'Anek Kannada', sans-serif;
+ font-size: 16px;
+ margin-bottom: 5px;
+ color: #333;
+}
+
+.card {
+ min-height: 250px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: @color-text-light;
+ text-align: center;
+
+ &--green {
+ background: @color-primary;
+ flex: 0 1 450px;
+ max-width: 450px;
+ }
+
+ &--beige {
+ background: @color-beige;
+ color: @color-text-dark;
+ flex: 0 1 925px;
+ max-width: 925px;
+ }
+}
+
+.design-section {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 40px;
+ .card { width: 100%; }
+}
+
+@media (max-width: 1240px) {
+ .catalog-wrapper { gap: 20px; }
+ .catalog-sidebar { flex: 0 0 200px; }
+ .products-container {
+ gap: 15px;
+ display: flex;
+ flex-wrap: wrap;
+ }
+
+ .product-card.small1 {
+ margin-top: 100px;
+ }
+
+ .product-card.small,
+ .product-card.small1,
+ .product-card.large,
+ .product-card.wide,
+ .product-card.wide1,
+ .product-card.wide2,
+ .product-card.wide2_1,
+ .product-card.wide4 {
+ flex: 0 0 calc(33.333% - 10px);
+ max-width: calc(33.333% - 10px);
+ height: 180px;
+ margin: 0;
+
+ .product-image-container {
+ height: 180px;
+ }
+ }
+
+ .product-card.wide3 {
+ flex: 0 0 calc(25% - 10px);
+ max-width: calc(25% - 10px);
+ height: 300px;
+ margin: 0;
+
+ .product-image-container {
+ height: 350px;
+ }
+ }
+
+ .product-card.tall {
+ flex: 0 0 calc(25% - 10px);
+ max-width: calc(25% - 10px);
+ height: 300px;
+ margin: 0;
+
+ .product-image-container {
+ height: 300px;
+ }
+ }
+
+ .product-card.full-width {
+ flex: 0 0 100%;
+ max-width: 100%;
+ height: 300px;
+ margin: 0;
+
+ .product-image-container {
+ height: 300px;
+ }
+ }
+
+ .product-card.small { order: 1; }
+ .product-card.large { order: 2; }
+ .product-card.wide { order: 3; }
+
+ .product-card.small1 { order: 11; }
+ .product-card.wide2 { order: 12; }
+ .product-card.wide2_1 { order: 13; }
+
+ .product-card.wide3 { order: 21; }
+ .product-card.tall { order: 22; }
+
+ .product-card.wide3 { order: 31; }
+
+ .product-card.full-width { order: 41; flex-basis: 100%; }
+
+ .main__content {
+ gap: 20px;
+ .products {
+ flex: 0 0 35%;
+ .products__image {
+ width: 250px;
+ height: 180px;
+ }
+ }
+ .order {
+ flex: 0 0 60%;
+ padding: 30px;
+
+ .order__title {
+ font-size: 24px;
+ }
+
+ .order__section-title {
+ font-size: 16px;
+ }
+ }
+ }
+
+ .solutions-slider {
+ &__slide {
+ .solution-text-overlay {
+ top: 10%;
+ left: 5%;
+ h2 {
+ font-size: 26px;
+ margin-bottom: 5px;
+ line-height: 1.2;
+ }
+ p {
+ font-size: 18px;
+ line-height: 1.2;
+ }
+ }
+ .solution-image-link {
+ bottom: 70px;
+ padding: 10px 25px;
+ font-size: 14px;
+ }
+ }
+ }
+
+ .product__main-image {
+ width: 350px;
+ height: 350px;
+ }
+
+ .product__thumbnail {
+ height: 80px;
+ }
+}
+
+@media (max-width: 1024px) {
+ .main__content {
+ gap: 25px;
+ .products {
+ flex: 0 0 30%;
+ .products__image {
+ width: 200px;
+ height: 150px;
+ }
+
+ .products__name {
+ font-size: 16px;
+ }
+
+ .products__price {
+ font-size: 16px;
+ }
+ }
+ .order {
+ flex: 0 0 60%;
+ padding: 25px;
+
+ .order__title {
+ font-size: 22px;
+ }
+
+ .form__input {
+ padding: 12px 14px;
+ font-size: 14px;
+ }
+
+ .promo__btn {
+ padding: 10px 40px;
+ font-size: 16px;
+ }
+ }
+ }
+}
+
+@media (max-width: 768px) {
+ .container { padding: 0 15px; }
+
+ .delivery-section {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .delivery-card {
+ min-width: 100%;
+ max-width: 100%;
+ }
+
+ .delivery-content h1 {
+ font-size: 32px;
+ }
+
+ .warranty-overview {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .warranty-card {
+ max-width: 100%;
+ width: 100%;
+ }
+
+ .coverage-section {
+ flex-direction: column;
+ }
+
+ .warranty-content h1 {
+ font-size: 32px;
+ }
+
+ .header__top .container,
+ .header__bottom .container,
+ .hero__content,
+ .advantages__header,
+ .about__content,
+ .advantages__items,
+ .promo-images,
+ .stats__items,
+ .faq__items,
+ .catalog-wrapper,
+ .main__content {
+ flex-direction: column;
+ gap: 30px;
+ }
+
+ .search-catalog {
+ order: 3;
+ width: 100%;
+ max-width: 100%;
+ }
+
+ .nav-list {
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 15px;
+ }
+
+ .hero {
+ &__image-block {
+ flex: none;
+ max-width: 400px;
+ height: 400px;
+ }
+ &__circle {
+ width: 380px;
+ height: 380px;
+ }
+ &__text-block {
+ flex: none;
+ padding-left: 0;
+ text-align: center;
+ h1 { font-size: 32px; }
+ .hero__usp-text {
+ padding-left: 0;
+ justify-content: center;
+ &::before { display: none; }
+ }
+ .btn.primary-btn { margin-left: 0; }
+ }
+ }
+
+ .advantages__header h2,
+ .faq h2 { font-size: 28px; }
+
+ .faq-item,
+ .stat-item {
+ flex: none;
+ .flex-center();
+ text-align: center;
+ }
+
+ .stats .container { justify-content: center; }
+ .catalog-dropdown__menu { width: 200px; }
+
+ .catalog-sidebar { width: 100%; flex: none; }
+ .products-container { gap: 15px; }
+
+ .product-card.small,
+ .product-card.small1,
+ .product-card.large,
+ .product-card.wide,
+ .product-card.wide1,
+ .product-card.wide2,
+ .product-card.wide2_1,
+ .product-card.wide3,
+ .product-card.wide4,
+ .product-card.tall,
+ .product-card.full-width {
+ flex: 0 0 100%;
+ max-width: 100%;
+ height: 250px;
+ margin: 0;
+
+ .product-image-container {
+ height: 200px;
+ }
+ }
+
+ .main__content {
+ flex-direction: column;
+ gap: 20px;
+
+ .products,
+ .order {
+ flex: 0 0 100%;
+ width: 100%;
+ }
+
+ .products {
+ .products__item {
+ flex-direction: column;
+ text-align: center;
+ gap: 15px;
+ }
+
+ .products__image {
+ width: 100%;
+ height: 200px;
+ justify-content: center;
+ }
+
+ .products__details {
+ min-height: auto;
+ align-items: center;
+ }
+
+ .products__controls {
+ justify-content: center;
+ margin-top: 15px;
+ }
+
+ .products__cart-icon {
+ margin-left: 0;
+ }
+ }
+
+ .order {
+ padding: 20px;
+
+ .order__title {
+ font-size: 20px;
+ text-align: center;
+ }
+
+ .order__total {
+ text-align: center;
+ }
+
+ .form__radio-group {
+ flex-direction: column;
+ gap: 15px;
+ }
+
+ .form__radio-label {
+ flex: none;
+ justify-content: flex-start;
+ }
+
+ .promo {
+ flex-direction: column;
+ gap: 10px;
+
+ &__btn {
+ width: 100%;
+ padding: 12px;
+ }
+ }
+
+ .order-btn {
+ padding: 12px;
+ font-size: 16px;
+ }
+
+ .services {
+ flex-direction: column;
+ align-items: center;
+ }
+ }
+ }
+
+ .product-image-container { height: 200px; }
+ .product-card.tall .product-image-container,
+ .product-card.large .product-image-container { height: 250px; }
+
+ .profile-page-main {
+ .profile-container {
+ flex-direction: column;
+ min-height: auto;
+ max-width: 100%;
+ box-shadow: none;
+ }
+ .profile-left-col {
+ flex: none;
+ width: 100%;
+ height: 100px;
+ .flex-center();
+ padding: 0;
+ }
+ .profile-right-col {
+ flex: none;
+ width: 100%;
+ padding: 30px 20px;
+ }
+ .profile-form-block { max-width: 100%; }
+ }
+
+ .form__row { flex-direction: column; }
+ .form__input--half { flex: 0 0 100%; max-width: 100%; }
+ .services { flex-direction: column; align-items: center; }
+
+ .services-section {
+ padding: 40px 0;
+ }
+
+ .service-card {
+ padding: 30px 20px;
+ min-height: 180px;
+
+ &--green,
+ &--beige {
+ max-width: 100%;
+ }
+
+ &__title {
+ font-size: 20px;
+ }
+
+ &__text {
+ font-size: 14px;
+ }
+ }
+ .solutions-slider {
+ margin: 20px auto;
+ &__slide {
+ .solution-text-overlay {
+ top: 8%;
+ left: 4%;
+ h2 {
+ font-size: 20px;
+ margin-bottom: 3px;
+ line-height: 1.1;
+ }
+ p {
+ font-size: 15px;
+ line-height: 1.1;
+ }
+ }
+ .solution-image-link {
+ bottom: 90px;
+ padding: 8px 20px;
+ font-size: 13px;
+ }
+ }
+ }
+}
+
+.error-input {
+ border-color: #ff4444 !important;
+ box-shadow: 0 0 0 1px #ff4444;
+}
+
+.field-error {
+ color: #ff4444;
+ font-size: 12px;
+ margin-top: 5px;
+ margin-bottom: 10px;
+}
+
+.message {
+ padding: 15px;
+ margin: 20px 0;
+ border-radius: 5px;
+ display: none;
+}
+
+.message.error {
+ background-color: #ffebee;
+ color: #c62828;
+ border: 1px solid #ffcdd2;
+}
+
+.message.success {
+ background-color: #e8f5e9;
+ color: #2e7d32;
+ border: 1px solid #c8e6c9;
+}
+
+.access-denied {
+ text-align: center;
+ padding: 80px 20px;
+ background: white;
+ border-radius: 10px;
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
+ margin: 50px 0;
+
+ h2 {
+ color: #dc3545;
+ margin-bottom: 30px;
+ font-size: 28px;
+ }
+
+ p {
+ color: #666;
+ margin-bottom: 40px;
+ font-size: 18px;
+ line-height: 1.6;
+ }
+
+ .btn {
+ margin: 5px;
+ min-width: 200px;
+ }
+}
+
+.user-profile-dropdown {
+ position: relative;
+ display: inline-block;
+
+ &__toggle {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+ padding: 8px 12px;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.05);
+ }
+
+ .user-avatar {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ background-color: @color-primary;
+ color: @color-text-light;
+ .flex-center();
+ font-weight: bold;
+ }
+
+ .user-info {
+ display: flex;
+ flex-direction: column;
+
+ .user-email {
+ font-size: 12px;
+ color: #666;
+ max-width: 150px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .user-status {
+ font-size: 10px;
+ padding: 2px 6px;
+ border-radius: 10px;
+ text-transform: uppercase;
+
+ &.admin {
+ background-color: #617365;
+ color: white;
+ }
+
+ &.user {
+ background-color: #28a745;
+ color: white;
+ }
+ }
+ }
+ }
+
+ &__menu {
+ .menu-base();
+ width: 220px;
+ top: 100%;
+ right: 0;
+
+ .user-details {
+ padding: 15px;
+ border-bottom: 1px solid #eee;
+
+ .user-name {
+ font-weight: bold;
+ margin-bottom: 5px;
+ }
+
+ .user-registered {
+ font-size: 11px;
+ color: #999;
+ }
+ }
+
+ ul {
+ padding: 10px 0;
+
+ li {
+ padding: 8px 15px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ transition: background-color 0.3s ease;
+
+ &:hover {
+ background-color: #f5f5f5;
+ }
+
+ &.logout {
+ color: #dc3545;
+ border-top: 1px solid #eee;
+ margin-top: 5px;
+ padding-top: 12px;
+
+ &:hover {
+ background-color: #ffe6e6;
+ }
+ }
+ }
+ }
+ }
+
+ &:hover &__menu {
+ display: block;
+ }
+}
+
+.product-image-container {
+ position: relative;
+ overflow: hidden;
+ margin-bottom: 0;
+ padding: 0;
+ height: 250px;
+ .flex-center();
+
+ &:hover {
+ .product-overlay-info {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+}
+
+.product-overlay-info {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
+ color: white;
+ padding: 15px;
+ opacity: 0;
+ transform: translateY(10px);
+ transition: all 0.3s ease;
+
+ .product-overlay-name {
+ font-weight: bold;
+ font-size: 14px;
+ margin-bottom: 5px;
+ }
+
+ .product-overlay-price {
+ font-size: 16px;
+ font-weight: bold;
+
+ .old-price {
+ text-decoration: line-through;
+ font-size: 12px;
+ color: #ccc;
+ margin-right: 5px;
+ }
+
+ .current-price {
+ color: #ffd700;
+ }
+ }
+
+ .product-overlay-category {
+ font-size: 11px;
+ opacity: 0.8;
+ margin-top: 3px;
+ }
+
+ .product-overlay-stock {
+ font-size: 11px;
+ margin-top: 5px;
+
+ &.out-of-stock {
+ color: #ff6b6b;
+ }
+
+ i {
+ margin-right: 5px;
+ }
+ }
+}
+
+.product-card-details {
+ padding: 15px;
+ background: white;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+
+ .product-card-name {
+ font-weight: bold;
+ font-size: 16px;
+ margin-bottom: 8px;
+ color: @color-text-dark;
+ }
+
+ .product-card-description {
+ font-size: 13px;
+ color: #777;
+ margin-bottom: 10px;
+ flex-grow: 1;
+ }
+
+ .product-card-attributes {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin-bottom: 10px;
+
+ .attribute {
+ font-size: 11px;
+ background: #f5f5f5;
+ padding: 3px 8px;
+ border-radius: 12px;
+ color: #666;
+
+ i {
+ margin-right: 3px;
+ }
+ }
+ }
+
+ .product-card-price {
+ margin-bottom: 10px;
+
+ .old-price {
+ text-decoration: line-through;
+ font-size: 14px;
+ color: #999;
+ margin-right: 8px;
+ }
+
+ .current-price {
+ font-size: 18px;
+ font-weight: bold;
+ color: @color-button;
+ }
+ }
+
+ .add-to-cart-btn {
+ width: 100%;
+ padding: 8px;
+ font-size: 14px;
+ }
+
+ .admin-actions {
+ display: flex;
+ gap: 5px;
+ margin-top: 10px;
+
+ .admin-btn {
+ flex: 1;
+ font-size: 12px;
+ padding: 6px;
+
+ &.delete-btn {
+ background: #dc3545;
+
+ &:hover {
+ background: #c82333;
+ }
+ }
+ }
+ }
+}
+
+.user-profile-dropdown {
+ position: relative;
+ display: inline-block;
+
+ .user-profile-toggle {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ cursor: pointer;
+ padding: 8px 12px;
+ border-radius: 4px;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.05);
+
+ .dropdown-arrow {
+ transform: rotate(180deg);
+ }
+ }
+
+ .user-avatar {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #617365 0%, #453227 100%);
+ color: @color-text-light;
+ .flex-center();
+ font-weight: bold;
+ font-size: 16px;
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
+ }
+
+ .user-info {
+ display: flex;
+ flex-direction: column;
+
+ .user-email {
+ font-size: 12px;
+ color: #666;
+ max-width: 120px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .user-status {
+ font-size: 10px;
+ padding: 2px 8px;
+ border-radius: 12px;
+ text-transform: uppercase;
+ font-weight: bold;
+ text-align: center;
+ margin-top: 2px;
+
+ &.admin {
+ background-color: #617365;
+ color: white;
+ border: 1px solid #617365;
+ }
+
+ &.user {
+ background-color: #28a745;
+ color: white;
+ border: 1px solid #28a745;
+ }
+ }
+ }
+
+ .dropdown-arrow {
+ font-size: 10px;
+ color: #666;
+ transition: transform 0.3s ease;
+ }
+ }
+
+ .user-profile-menu {
+ .menu-base();
+ width: 280px;
+ top: 100%;
+ right: 0;
+ margin-top: 10px;
+ padding: 0;
+ overflow: hidden;
+
+ .user-profile-header {
+ padding: 20px;
+ background: linear-gradient(135deg, #617365 0%, #453227 100%);
+ color: white;
+
+ .user-profile-name {
+ font-weight: bold;
+ margin-bottom: 8px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 16px;
+ }
+
+ .user-profile-details {
+ small {
+ display: block;
+ opacity: 0.8;
+ margin-bottom: 5px;
+ font-size: 11px;
+
+ i {
+ margin-right: 5px;
+ width: 14px;
+ text-align: center;
+ }
+ }
+ }
+ }
+
+ .user-profile-links {
+ list-style: none;
+ padding: 10px 0;
+
+ li {
+ border-bottom: 1px solid #f0f0f0;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ a {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 20px;
+ color: #333;
+ transition: all 0.3s ease;
+
+ &:hover {
+ background-color: #f8f9fa;
+ color: @color-primary;
+ text-decoration: none;
+
+ i {
+ transform: scale(1.1);
+ }
+ }
+
+ i {
+ width: 20px;
+ text-align: center;
+ font-size: 14px;
+ color: #617365;
+ transition: transform 0.3s ease;
+ }
+
+ span {
+ flex-grow: 1;
+ }
+ }
+ }
+
+ .logout-item {
+ border-top: 2px solid #f0f0f0;
+ margin-top: 5px;
+
+ a {
+ color: #dc3545;
+
+ &:hover {
+ background-color: #ffe6e6;
+ color: #c82333;
+
+ i {
+ color: #dc3545;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ &:hover .user-profile-menu {
+ display: block;
+ animation: fadeIn 0.3s ease;
+ }
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@media (max-width: 768px) {
+ .user-profile-dropdown {
+ .user-profile-toggle {
+ .user-info {
+ display: none;
+ }
+
+ .dropdown-arrow {
+ display: none;
+ }
+ }
+
+ .user-profile-menu {
+ width: 250px;
+ right: -50px;
+ }
+ }
+}
+.unavailable-product {
+ position: relative;
+ opacity: 0.6;
+ filter: grayscale(0.7);
+
+ &::before {
+ content: "ТОВАР ЗАКОНЧИЛСЯ";
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: rgba(0, 0, 0, 0.85);
+ color: white;
+ padding: 15px 25px;
+ border-radius: 5px;
+ font-weight: bold;
+ font-size: 16px;
+ text-align: center;
+ z-index: 100;
+ white-space: nowrap;
+ pointer-events: none;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
+ }
+
+ .product-name-overlay {
+ .name, .price {
+ color: #999 !important;
+ text-shadow: none !important;
+ }
+ }
+
+ .add-to-cart-btn {
+ display: none !important;
+ }
+
+ &:hover {
+ transform: none !important;
+ cursor: not-allowed;
+ }
+}
+
+.out-of-stock-badge {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ background: #6c757d;
+ color: white;
+ padding: 5px 10px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: bold;
+ z-index: 10;
+}
+
+.admin-table tr.unavailable {
+ background-color: #f8f9fa !important;
+ opacity: 0.7;
+
+ td {
+ color: #999;
+ }
+}
+
+.status-unavailable {
+ background-color: #6c757d !important;
+ color: white !important;
+}
+
+.similar-products {
+ margin: 40px 0;
+
+ h2 {
+ font-size: 24px;
+ margin-bottom: 20px;
+ color: #453227;
+ }
+}
+
+.products-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 20px;
+}
+
+.product-card {
+ background: white;
+ border-radius: 8px;
+ overflow: hidden;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
+
+ &:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
+ }
+}
+
+.product-image {
+ width: 100%;
+ height: 250px;
+ overflow: hidden;
+ background: #f5f5f5;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ transition: transform 0.3s ease;
+ }
+
+ &:hover img {
+ transform: scale(1.05);
+ }
+}
+
+.product-info {
+ padding: 15px;
+
+ h3 {
+ font-size: 16px;
+ margin: 0 0 10px;
+ color: #333;
+ min-height: 40px;
+ }
+
+ .product-price {
+ font-size: 20px;
+ font-weight: bold;
+ color: #453227;
+ margin: 10px 0;
+ }
+
+ .btn {
+ width: 100%;
+ padding: 10px;
+ text-align: center;
+ margin-top: 10px;
+ }
+}
+
+@media (max-width: 1200px) {
+ .product__section {
+ grid-template-columns: 330px 1fr;
+ gap: 25px;
+ }
+
+ .product__main-image {
+ width: 330px;
+ height: 330px;
+ }
+}
+
+@media (max-width: 992px) {
+ .product__section {
+ grid-template-columns: 1fr;
+ gap: 25px;
+ }
+
+ .product__main-image {
+ width: 100%;
+ max-width: 400px;
+ height: 350px;
+ margin: 0 auto;
+ }
+
+ .product__thumbnails {
+ grid-template-columns: repeat(4, 1fr);
+ }
+}
+
+@media (max-width: 576px) {
+ .product__main-image {
+ width: 100%;
+ height: 300px;
+ }
+
+ .product__thumbnails {
+ grid-template-columns: repeat(3, 1fr);
+ }
+
+ .product__info h1 {
+ font-size: 24px;
+ }
+
+ .product__price .current-price {
+ font-size: 28px;
+ }
}
\ No newline at end of file
diff --git a/uploads/products/.gitkeep b/public/uploads/products/.gitkeep
similarity index 100%
rename from uploads/products/.gitkeep
rename to public/uploads/products/.gitkeep
diff --git a/public/uploads/products/product_1765745390_2854.png b/public/uploads/products/product_1765745390_2854.png
new file mode 100644
index 0000000..55ab5bb
Binary files /dev/null and b/public/uploads/products/product_1765745390_2854.png differ
diff --git a/public/uploads/products/product_1765750758_4966.jpg b/public/uploads/products/product_1765750758_4966.jpg
new file mode 100644
index 0000000..e59b3b1
Binary files /dev/null and b/public/uploads/products/product_1765750758_4966.jpg differ
diff --git a/Гарантия.php b/public/warranty.php
similarity index 91%
rename from Гарантия.php
rename to public/warranty.php
index 4730164..95464a5 100644
--- a/Гарантия.php
+++ b/public/warranty.php
@@ -1,206 +1,202 @@
-
-
-
-
-
- AETERNA - Гарантия
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
ГАРАНТИЙНЫЕ ОБЯЗАТЕЛЬСТВА
-
-
-
-
-
-
-
Мягкая мебель
-
18 месяцев
-
-
-
-
-
-
-
Корпусная мебель
-
24 месяца
-
-
-
-
-
-
-
Элементы освещения
-
12 месяцев
-
-
-
-
-
-
-
Фурнитура и механизмы
-
36 месяцев
-
-
-
-
-
-
Что покрывается гарантией
-
-
-
-
-
Производственные дефекты
-
Трещины, сколы, брак материалов
-
-
-
-
-
-
Неисправности механизмов
-
Трансформации, выдвижные системы
-
-
-
-
-
-
Проблемы с фурнитурой
-
Ручки, петли, направляющие
-
-
-
-
-
-
Дефекты покрытия
-
Отслоение шпона, краски, ламинации
-
-
-
-
-
-
-
Что не покрывается гарантией
-
-
-
-
-
Механические повреждения
-
Царапины, вмятины от неправильной эксплуатации
-
-
-
-
-
-
Следы износа
-
Естественное старение материалов
-
-
-
-
-
-
Неправильная сборка
-
Последствия самостоятельного ремонта
-
-
-
-
-
-
Внешние воздействия
-
Повреждения от жидкостей, солнечных лучей
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ AETERNA - Гарантия
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ГАРАНТИЙНЫЕ ОБЯЗАТЕЛЬСТВА
+
+
+
+
+
+
+
Мягкая мебель
+
18 месяцев
+
+
+
+
+
+
+
Корпусная мебель
+
24 месяца
+
+
+
+
+
+
+
Элементы освещения
+
12 месяцев
+
+
+
+
+
+
+
Фурнитура и механизмы
+
36 месяцев
+
+
+
+
+
+
Что покрывается гарантией
+
+
+
+
+
Производственные дефекты
+
Трещины, сколы, брак материалов
+
+
+
+
+
+
Неисправности механизмов
+
Трансформации, выдвижные системы
+
+
+
+
+
+
Проблемы с фурнитурой
+
Ручки, петли, направляющие
+
+
+
+
+
+
Дефекты покрытия
+
Отслоение шпона, краски, ламинации
+
+
+
+
+
+
+
Что не покрывается гарантией
+
+
+
+
+
Механические повреждения
+
Царапины, вмятины от неправильной эксплуатации
+
+
+
+
+
+
Следы износа
+
Естественное старение материалов
+
+
+
+
+
+
Неправильная сборка
+
Последствия самостоятельного ремонта
+
+
+
+
+
+
Внешние воздействия
+
Повреждения от жидкостей, солнечных лучей
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/стили_оформления.less b/public/стили_оформления.less
similarity index 78%
rename from стили_оформления.less
rename to public/стили_оформления.less
index fb07c54..a6fe073 100644
--- a/стили_оформления.less
+++ b/public/стили_оформления.less
@@ -1,142 +1,137 @@
-.error-message {
- color: #ff0000;
- font-size: 12px;
- margin-top: 5px;
- display: none;
-}
-
-.form__input.error {
- border-color: #ff0000;
-}
-
-.form__group {
- position: relative;
- margin-bottom: 15px;
-}
-
-/* Стили для сообщений внизу страницы */
-.page-messages {
- position: fixed;
- bottom: 20px;
- left: 50%;
- transform: translateX(-50%);
- z-index: 1000;
- width: 90%;
- max-width: 500px;
-}
-
-.message {
- padding: 15px;
- margin: 10px 0;
- border-radius: 5px;
- text-align: center;
- font-weight: bold;
- display: none;
-}
-
-.message.error {
- background-color: #ffebee;
- color: #c62828;
- border: 1px solid #ffcdd2;
-}
-
-.message.success {
- background-color: #e8f5e9;
- color: #453227;
- border: 1px solid #c8e6c9;
-}
-
-.message.warning {
- background-color: #fff3e0;
- color: #ef6c00;
- border: 1px solid #ffe0b2;
-}
-
-.privacy-error {
- color: #ff0000;
- font-size: 12px;
- margin-top: 5px;
- display: none;
- text-align: center;
-}
-
-/* Дополнительные стили для формы регистрации */
-.input-group {
- position: relative;
- margin-bottom: 20px;
-}
-
-.profile-form input.error {
- border-color: #ff0000;
- background-color: #fff5f5;
-}
-
-.privacy-checkbox {
- margin: 20px 0;
- text-align: center;
-}
-
-.privacy-checkbox label {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 8px;
- cursor: pointer;
- font-size: 14px;
-}
-
-.privacy-checkbox input[type="checkbox"] {
- margin: 0;
-}
-
-/* Исправление отступов для страницы регистрации */
-.profile-page-main {
- padding: 40px 0;
- min-height: calc(100vh - 200px);
-}
-
-/* Убедимся, что контейнер не перекрывает шапку и футер */
-.profile-container {
- margin: 0 auto;
- position: relative;
- z-index: 1;
-}
-
-.input-hint {
- font-size: 12px;
- color: #666;
- margin-top: 5px;
-}
-
-/* Стили для страницы входа */
-.form-options {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin: 20px 0;
-}
-
-.remember-me {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 14px;
- color: #453227;
-}
-
-.remember-me input[type="checkbox"] {
- width: 16px;
- height: 16px;
- cursor: pointer;
-}
-
-.forgot-password {
- font-size: 14px;
- color: #453227;
- text-decoration: underline;
-}
-
-.forgot-password:hover {
- color: #617365;
- text-decoration: none;
+.error-message {
+ color: #ff0000;
+ font-size: 12px;
+ margin-top: 5px;
+ display: none;
+}
+
+.form__input.error {
+ border-color: #ff0000;
+}
+
+.form__group {
+ position: relative;
+ margin-bottom: 15px;
+}
+
+.page-messages {
+ position: fixed;
+ bottom: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 1000;
+ width: 90%;
+ max-width: 500px;
+}
+
+.message {
+ padding: 15px;
+ margin: 10px 0;
+ border-radius: 5px;
+ text-align: center;
+ font-weight: bold;
+ display: none;
+}
+
+.message.error {
+ background-color: #ffebee;
+ color: #c62828;
+ border: 1px solid #ffcdd2;
+}
+
+.message.success {
+ background-color: #e8f5e9;
+ color: #453227;
+ border: 1px solid #c8e6c9;
+}
+
+.message.warning {
+ background-color: #fff3e0;
+ color: #ef6c00;
+ border: 1px solid #ffe0b2;
+}
+
+.privacy-error {
+ color: #ff0000;
+ font-size: 12px;
+ margin-top: 5px;
+ display: none;
+ text-align: center;
+}
+
+.input-group {
+ position: relative;
+ margin-bottom: 20px;
+}
+
+.profile-form input.error {
+ border-color: #ff0000;
+ background-color: #fff5f5;
+}
+
+.privacy-checkbox {
+ margin: 20px 0;
+ text-align: center;
+}
+
+.privacy-checkbox label {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ cursor: pointer;
+ font-size: 14px;
+}
+
+.privacy-checkbox input[type="checkbox"] {
+ margin: 0;
+}
+
+.profile-page-main {
+ padding: 40px 0;
+ min-height: calc(100vh - 200px);
+}
+
+.profile-container {
+ margin: 0 auto;
+ position: relative;
+ z-index: 1;
+}
+
+.input-hint {
+ font-size: 12px;
+ color: #666;
+ margin-top: 5px;
+}
+
+.form-options {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin: 20px 0;
+}
+
+.remember-me {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ color: #453227;
+}
+
+.remember-me input[type="checkbox"] {
+ width: 16px;
+ height: 16px;
+ cursor: pointer;
+}
+
+.forgot-password {
+ font-size: 14px;
+ color: #453227;
+ text-decoration: underline;
+}
+
+.forgot-password:hover {
+ color: #617365;
+ text-decoration: none;
}
\ No newline at end of file
diff --git a/register.php b/register.php
deleted file mode 100644
index a5d967b..0000000
--- a/register.php
+++ /dev/null
@@ -1,87 +0,0 @@
-getConnection();
-
- try {
- // Проверяем существование email
- $check_stmt = $db->prepare("SELECT user_id FROM users WHERE email = ?");
- $check_stmt->execute([$email]);
-
- if ($check_stmt->fetch()) {
- $errors[] = 'Пользователь с таким email уже существует';
- } else {
- // Хэшируем пароль
- $password_hash = password_hash($password, PASSWORD_DEFAULT);
-
- // Создаем пользователя
- $stmt = $db->prepare("
- INSERT INTO users (email, password_hash, full_name, phone, is_active)
- VALUES (?, ?, ?, ?, TRUE)
- RETURNING user_id
- ");
-
- $stmt->execute([$email, $password_hash, $full_name, $phone]);
- $user_id = $stmt->fetchColumn();
-
- if ($user_id) {
- // Автоматически входим
- $_SESSION['user_id'] = $user_id;
- $_SESSION['user_email'] = $email;
- $_SESSION['full_name'] = $full_name;
- $_SESSION['isLoggedIn'] = true;
- $_SESSION['isAdmin'] = false; // Обычный пользователь
-
- // Успешная регистрация
- $_SESSION['registration_success'] = true;
- header('Location: catalog.php');
- exit();
- } else {
- $errors[] = 'Ошибка при создании пользователя';
- }
- }
- } catch (PDOException $e) {
- error_log("Ошибка регистрации: " . $e->getMessage());
- $errors[] = 'Ошибка базы данных. Попробуйте позже.';
- }
- }
-
- // Если есть ошибки, сохраняем их
- if (!empty($errors)) {
- $_SESSION['registration_errors'] = $errors;
- $_SESSION['old_input'] = [
- 'email' => $email,
- 'full_name' => $full_name,
- 'phone' => $phone
- ];
- header('Location: профиль.php');
- exit();
- }
-} else {
- header('Location: профиль.php');
- exit();
-}
-?>
\ No newline at end of file
diff --git a/setup.sh b/setup.sh
new file mode 100755
index 0000000..03cd61a
--- /dev/null
+++ b/setup.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+#
+# AETERNA - Скрипт настройки проекта
+# Запуск: ./setup.sh
+#
+
+echo "==========================================="
+echo " AETERNA - Настройка проекта"
+echo "==========================================="
+echo ""
+
+PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
+cd "$PROJECT_DIR"
+
+# Проверяем наличие PHP
+if ! command -v php &> /dev/null; then
+ echo "[ERROR] PHP не установлен!"
+ exit 1
+fi
+
+echo "[1/3] Проверка структуры проекта..."
+REQUIRED_DIRS=("public" "admin" "api" "includes" "config" "migrations" "assets" "uploads")
+for dir in "${REQUIRED_DIRS[@]}"; do
+ if [ ! -d "$dir" ]; then
+ mkdir -p "$dir"
+ echo " Создана папка: $dir"
+ fi
+done
+echo " ✓ Структура OK"
+
+echo ""
+echo "[2/3] Запуск миграций базы данных..."
+if [ -f "migrations/migrate.php" ]; then
+ php migrations/migrate.php
+else
+ echo " ⚠ Файл миграций не найден"
+fi
+
+echo ""
+echo "[3/3] Загрузка начальных данных..."
+echo " Загрузить тестовые данные (админ, категории, товары)? (y/n)"
+read -r answer
+if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then
+ php migrations/migrate.php --seed
+ echo " ✓ Данные загружены"
+else
+ echo " Пропущено"
+fi
+
+echo ""
+echo "==========================================="
+echo " Готово!"
+echo "==========================================="
+echo ""
+echo "Тестовые аккаунты:"
+echo " Админ: admin@aeterna.ru / admin123"
+echo " Пользователь: user@test.com / user123"
+echo ""
+echo "Запуск сервера:"
+echo " php -S localhost:8000 -t public"
+echo ""
diff --git a/test_add_simple.php b/test_add_simple.php
deleted file mode 100644
index b68e64d..0000000
--- a/test_add_simple.php
+++ /dev/null
@@ -1,131 +0,0 @@
-getConnection();
-
-// Обработка формы
-if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- $action = $_POST['action'] ?? '';
-
- if ($action === 'add_category') {
- $name = $_POST['name'] ?? '';
- $slug = preg_replace('/[^a-z0-9]/i', '-', strtolower($name));
-
- try {
- $stmt = $db->prepare("INSERT INTO categories (name, slug) VALUES (?, ?)");
- $result = $stmt->execute([$name, $slug]);
-
- if ($result) {
- $message = "✅ Категория '$name' успешно добавлена!";
- $message_class = "success";
- } else {
- $message = "❌ Ошибка при добавлении категории";
- $message_class = "error";
- }
- } catch (PDOException $e) {
- $message = "❌ Ошибка БД: " . $e->getMessage();
- $message_class = "error";
- }
- }
-
- if ($action === 'add_product') {
- $name = $_POST['product_name'] ?? '';
- $price = floatval($_POST['price'] ?? 0);
- $category_id = intval($_POST['category_id'] ?? 1);
- $slug = preg_replace('/[^a-z0-9]/i', '-', strtolower($name));
-
- try {
- $stmt = $db->prepare("INSERT INTO products (category_id, name, slug, price, stock_quantity, is_available) VALUES (?, ?, ?, ?, ?, ?)");
- $result = $stmt->execute([$category_id, $name, $slug, $price, 10, true]);
-
- if ($result) {
- $message = "✅ Товар '$name' успешно добавлен!";
- $message_class = "success";
- } else {
- $message = "❌ Ошибка при добавлении товара";
- $message_class = "error";
- }
- } catch (PDOException $e) {
- $message = "❌ Ошибка БД: " . $e->getMessage();
- $message_class = "error";
- }
- }
-}
-
-// Получить категории для выпадающего списка
-$categories = [];
-try {
- $categories = $db->query("SELECT * FROM categories")->fetchAll();
-} catch (Exception $e) {
- $categories_error = "Ошибка получения категорий: " . $e->getMessage();
-}
-
-?>
-
-
-
-
- Тестовое добавление
-
-
-
- Тестовое добавление
-
-
- = $message ?>
-
-
-
- Добавить категорию
-
-
- Добавить категорию
-
-
-
- Добавить товар
-
-
-
-
-
-
-
- = htmlspecialchars($cat['name']) ?>
-
-
- Нет категорий (используется ID 1)
-
-
-
- Добавить товар
-
-
-
-
- Существующие категории (= count($categories) ?>):
-
-
- ID: = $cat['category_id'] ?> - = htmlspecialchars($cat['name']) ?>
-
-
-
-
- = $categories_error ?>
-
-
-
\ No newline at end of file
diff --git a/страница_товара.php b/страница_товара.php
deleted file mode 100644
index 21058b5..0000000
--- a/страница_товара.php
+++ /dev/null
@@ -1,237 +0,0 @@
-
-
-
-
-
- AETERNA - Стул Ender
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Стул ENDER
-
-
- ★
- 9/10
- 54 отзывов
-
-
-
-
-
-
-
-
-
-
- Стулья с закрытой обивкой созданы в стилистике практичности и комфортабельности,
- придает помещению, эффектно выделяющимися средствами.
-
-
-
-
-
16 999 ₽
-
- −
- 1
- +
-
-
-
-
- В КОРЗИНУ
- КУПИТЬ
-
-
-
-
-
- Похожие
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file