Gogs 3 тижнів тому
батько
коміт
444e275e0c
62 змінених файлів з 2246 додано та 1072 видалено
  1. 3 0
      .gitignore
  2. 438 88
      README.md
  3. 25 0
      app/components/Domain/CartWidget/index.coffee
  4. 8 0
      app/components/Domain/CartWidget/index.pug
  5. 27 0
      app/components/Domain/MultilevelMenu/index.coffee
  6. 11 0
      app/components/Domain/MultilevelMenu/index.pug
  7. 12 0
      app/components/Domain/ProductCard/index.coffee
  8. 7 0
      app/components/Domain/ProductCard/index.pug
  9. 26 0
      app/components/Domain/ProductGrid/index.coffee
  10. 9 0
      app/components/Domain/ProductGrid/index.pug
  11. 21 0
      app/components/UI/AppLoader/index.coffee
  12. 5 0
      app/components/UI/AppLoader/index.pug
  13. 52 0
      app/components/UI/AppLoader/index.styl
  14. 38 0
      app/components/UI/Button/index.coffee
  15. 9 0
      app/components/UI/Button/index.pug
  16. 139 0
      app/components/UI/Button/index.styl
  17. 19 0
      app/components/UI/LanguageToggle/index.coffee
  18. 11 0
      app/components/UI/LanguageToggle/index.pug
  19. 33 0
      app/components/UI/Notification/index.coffee
  20. 17 0
      app/components/UI/Notification/index.pug
  21. 141 0
      app/components/UI/Notification/index.styl
  22. 15 0
      app/components/UI/ThemeToggle/index.coffee
  23. 0 0
      app/components/UI/ThemeToggle/index.pug
  24. 24 0
      app/config.coffee
  25. 31 8
      app/index.coffee
  26. 30 16
      app/index.pug
  27. 48 0
      app/pages/Admin/Clients/index.coffee
  28. 39 0
      app/pages/Admin/Clients/index.pug
  29. 6 0
      app/pages/Admin/Clients/index.styl
  30. 104 0
      app/pages/Admin/Routes/index.coffee
  31. 104 0
      app/pages/Admin/Routes/index.pug
  32. 14 0
      app/pages/Admin/Routes/index.styl
  33. 14 227
      app/pages/Admin/index.coffee
  34. 4 201
      app/pages/Admin/index.pug
  35. 19 0
      app/pages/Cart/index.coffee
  36. 4 0
      app/pages/Cart/index.pug
  37. 18 0
      app/pages/Catalog/index.coffee
  38. 4 0
      app/pages/Catalog/index.pug
  39. 63 40
      app/pages/Home/index.coffee
  40. 48 51
      app/pages/Home/index.pug
  41. 115 224
      app/pages/Home/index.styl
  42. 29 0
      app/pages/NotFound/index.coffee
  43. 25 0
      app/pages/NotFound/index.pug
  44. 76 0
      app/pages/NotFound/index.styl
  45. 19 0
      app/pages/Product/index.coffee
  46. 4 0
      app/pages/Product/index.pug
  47. 77 0
      app/router/index.coffee
  48. 0 0
      app/shared/ImageSlider/index.coffee
  49. 0 0
      app/shared/ImageSlider/index.pug
  50. 0 0
      app/shared/ImageSlider/index.styl
  51. 0 0
      app/shared/ModalWindow/index.coffee
  52. 0 0
      app/shared/ModalWindow/index.pug
  53. 0 0
      app/shared/ModalWindow/index.styl
  54. 0 0
      app/shared/MultiLevelMenu/index.coffee
  55. 41 0
      app/shared/MultiLevelMenu/index.pug
  56. 0 0
      app/shared/MultiLevelMenu/index.styl
  57. 0 0
      app/shared/ThemeToggle/index.coffee
  58. 22 0
      app/shared/ThemeToggle/index.pug
  59. 0 0
      app/shared/ThemeToggle/index.styl
  60. 124 0
      app/types/data.coffee
  61. 46 0
      app/types/events.coffee
  62. 28 217
      app/utils/pouch.coffee

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+html.json
+pug.json
+styl.json

+ 438 - 88
README.md

@@ -16,6 +16,7 @@
 - **Шаблонизатор:** Pug с Vue компонентами
 - **Стилизация:** Stylus + CSS переменные + BEM методология
 - **Логика:** CoffeeScript + Vue.js 3 (runtime)
+- **Важно** Vuejs в runtime режиме, не использовать template, шаблоны СТРОГО через "render: (new Function '_ctx', '_cache', renderFns['app/index.pug'])()" во всех компанентах
 - **Маршрутизация:** Vue Router
 - **База данных:** PouchDB (клиент) + CouchDB (сервер)
 - **Анимации:** CSS transitions/transforms + Vue transitions
@@ -199,9 +200,12 @@ div(class="app" :class="{'theme-dark': theme === 'dark'}")
 
 **app/index.coffee**
 ```coffee
-# Глобальная инициализация debug в начале файла, сам debug уже глобально определён
+# Глобальная инициализация debug
 globalThis.log = debug.log
 
+# Главный файл приложения
+log '🚀 Инициализация приложения Браер-Колор'
+
 # Загрузка конфигурации
 config = require 'app/config'
 DataTypes = require 'app/types/data'
@@ -212,21 +216,38 @@ globalThis.renderFns = require 'pug.json'
 globalThis.stylFns = require 'styl.json'
 
 
-# Сервисы
-PouchDBService = require 'app/utils/pouch'
-DomainService = require 'app/services/DomainService'
+
+# Сервисы (пока заглушки)
+PouchDBService = 
+  init: -> Promise.resolve()
+  getDocument: -> Promise.resolve(null)
+  saveToRemote: -> Promise.resolve()
+
+DomainService = 
+  init: -> Promise.resolve()
+  loadDomainSettings: -> Promise.resolve(null)
+  getAvailableDomains: -> []
 
 # Мета-теги
 document.head.insertAdjacentHTML 'beforeend', '<meta charset="UTF-8">'
 document.head.insertAdjacentHTML 'beforeend', '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
-document.head.insertAdjacentHTML 'beforeend', '<style type="text/css">' + stylFns['app/index.styl'] + '</style>'
-
-# Главное приложение Vue
+document.head.insertAdjacentHTML 'beforeend', '<title>Браер-Колор - Интернет-магазин лакокрасочной продукции</title>'
+
+# Добавление глобальных стилей
+if stylFns['app/index.styl']
+  styleElement = document.createElement('style')
+  styleElement.type = 'text/css'
+  styleElement.textContent = stylFns['app/index.styl']
+  document.head.appendChild(styleElement)
+else
+  log '⚠️ Глобальные стили не найдены'
+
+# Создание Vue приложения
 app = Vue.createApp({
   data: ->
     {
-      theme: localStorage.getItem('theme') or 'light'
-      companyName: 'Браер-Колор'
+      theme: localStorage.getItem('theme') or config.defaultTheme
+      companyName: config.companyName
       loading: false
       currentDomain: window.location.hostname
       currentDomainSettings: null
@@ -239,94 +260,405 @@ app = Vue.createApp({
     }
   
   computed:
-    isAdmin: -> @user?.role == 'admin'
+    isAdmin: -> 
+      @user?.role == 'admin'
+    
     domainConfig: -> 
-      DomainService.getDomainConfig(@currentDomain)
+      DomainService.getDomainConfig?(@currentDomain) or {}
   
   methods:
+    # Управление темой
     toggleTheme: ->
       @theme = if @theme == 'light' then 'dark' else 'light'
       localStorage.setItem 'theme', @theme
       document.documentElement.classList.toggle 'dark'
       @$emit EventTypes.THEME_CHANGED, @theme
+      log '🎨 Тема изменена:', @theme
     
+    # Управление языком
     setLanguage: (lang) ->
       if @languages.includes lang
         @currentLanguage = lang
-        localStorage.setItem 'language', @lang
+        localStorage.setItem 'language', @currentLanguage
         @$emit EventTypes.LANGUAGE_CHANGED, lang
+        log '🌐 Язык изменен:', lang
+      else
+        log '⚠️ Язык не поддерживается:', lang
+    
+    # Смена домена
+    changeDomain: (domain) ->
+      log '🌐 Смена домена на:', domain
+      @currentDomain = domain
+      @loadDomainData()
     
+    # Переход в корзину
+    goToCart: ->
+      @$router.push '/cart'
+      log '🛒 Переход в корзину'
+    
+    # Загрузка настроек домена
     loadDomainData: ->
+      log '📡 Загрузка настроек домена:', @currentDomain
+      
       DomainService.loadDomainSettings(@currentDomain)
         .then (settings) =>
           @currentDomainSettings = settings
           document.title = settings?.companyName or @companyName
-          log 'Настройки домена загружены', settings
+          log 'Настройки домена загружены', settings
         .catch (error) =>
-          log 'Настройки домена не найдены, используются значения по умолчанию'
+          log '⚠️ Настройки домена не найдены, используются значения по умолчанию'
           @currentDomainSettings = new DataTypes.DomainSettings()
+          @currentDomainSettings.companyName = @companyName
     
+    # Управление корзиной
     updateCart: (items) ->
       @cartItems = items
       localStorage.setItem 'cart', JSON.stringify(items)
+      @$emit EventTypes.CART_UPDATE, items
+      log '🛒 Корзина обновлена:', items.length, 'товаров'
     
-    showNotification: (message, type = 'success') ->
-      notification = { id: Date.now(), message, type, visible: true }
+    # Уведомления
+    showNotification: (message, type = 'info') ->
+      notification = { 
+        id: Date.now(), 
+        message, 
+        type, 
+        visible: true,
+        timestamp: new Date()
+      }
+      
       @notifications.push notification
+      log '📢 Показано уведомление:', message
+      
       setTimeout (=>
         notification.visible = false
         setTimeout (=>
           @notifications = @notifications.filter (n) -> n.id != notification.id
         ), 300
       ), 5000
-  
-  mounted: -> # не используй async  в определении методов и обработчиков событий
-    # Инициализация темы
-    if @theme == 'dark'
-      document.documentElement.classList.add 'dark'
     
-    # Инициализация сервисов
-    try
-      await PouchDBService.init()
-      log 'PouchDB инициализирован'
+    # Закрытие уведомления
+    closeNotification: (id) ->
+      @notifications = @notifications.filter (notification) -> notification.id != id
+      log '📢 Уведомление закрыто:', id
+    
+    # Загрузка пользователя
+    loadUserData: ->
+      userData = localStorage.getItem 'user'
+      if userData
+        try
+          @user = JSON.parse userData
+          log '👤 Пользователь загружен:', @user.username
+        catch error
+          log '❌ Ошибка загрузки пользователя:', error
+          @user = null
+      else
+        @user = null
+    
+    # Загрузка корзины
+    loadCartData: ->
+      cartData = localStorage.getItem 'cart'
+      if cartData
+        try
+          @cartItems = JSON.parse cartData
+          log '🛒 Корзина загружена:', @cartItems.length, 'товаров'
+        catch error
+          log '❌ Ошибка загрузки корзины:', error
+          @cartItems = []
+      else
+        @cartItems = []
+    
+    # Инициализация приложения
+    initializeApp: ->
+      log '🔧 Начало инициализации приложения'
+      @loading = true
       
-      await DomainService.init()
-      @availableDomains = DomainService.getAvailableDomains()
+      # Инициализация темы
+      if @theme == 'dark'
+        document.documentElement.classList.add 'dark'
+        log '🌙 Темная тема активирована'
+      else
+        log '☀️ Светлая тема активирована'
       
-      await @loadDomainData()
-    catch error
-      log 'Ошибка инициализации:', error
-    
-    # Загрузка пользователя и корзины
-    @loadUserData()
-    @loadCartData()
+      # Последовательная инициализация сервисов
+      Promise.resolve()
+        .then =>
+          log '📦 Инициализация PouchDB...'
+          PouchDBService.init()
+        .then =>
+          log '🌐 Инициализация DomainService...'
+          DomainService.init()
+        .then =>
+          log '📡 Получение доступных доменов...'
+          @availableDomains = DomainService.getAvailableDomains()
+        .then =>
+          @loadDomainData()
+        .then =>
+          @loadUserData()
+        .then =>
+          @loadCartData()
+        .then =>
+          log '✅ Приложение успешно инициализировано'
+          @showNotification('Приложение готово к работе', 'success')
+        .catch (error) =>
+          log '❌ Ошибка инициализации приложения:', error
+          @showNotification('Ошибка загрузки приложения', 'error')
+        .finally =>
+          @loading = false
   
-  loadUserData: ->
-    userData = localStorage.getItem 'user'
-    if userData
-      try
-        @user = JSON.parse userData
-      catch
-        @user = null
+  async mounted: ->
+    await @initializeApp()
   
-  loadCartData: ->
-    cartData = localStorage.getItem 'cart'
-    if cartData
-      try
-        @cartItems = JSON.parse cartData
-      catch
-        @cartItems = []
-
-  render: (new Function '_ctx', '_cache', renderFns['app/index.pug'])()
+  # Рендер функция из Pug
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/index.pug'])()
 })
 
-# Глобальная обработка ошибок
+
+Router = require 'app/router/index.coffee'
+# Регистрация глобальных компонентов
+app.component('ui-button', require 'app/components/UI/Button/index.coffee')
+app.component('notification-container', require 'app/components/UI/Notification/index.coffee')
+app.component('app-loader', require 'app/components/UI/AppLoader/index.coffee')
+app.component('multilevel-menu', require 'app/components/Domain/MultilevelMenu/index.coffee')
+app.component('theme-toggle', require 'app/components/UI/ThemeToggle/index.coffee')
+app.component('language-toggle', require 'app/components/UI/LanguageToggle/index.coffee')
+app.component('cart-widget', require 'app/components/Domain/CartWidget/index.coffee')
+
+# Подключение роутера
+app.use Router
+
+# Глобальная обработка ошибок Vue
 app.config.errorHandler = (err, vm, info) ->
-  log 'Vue ошибка:', err, info
+  log '💥 Vue ошибка:', err, info
+  console.error('Vue ошибка:', err, info)
+
+# Глобальная обработка предупреждений
+app.config.warnHandler = (msg, vm, trace) ->
+  log '⚠️ Vue предупреждение:', msg, trace
+
+# Монтирование приложения
+try
+  app.mount('body')
+  log '✅ Приложение успешно смонтировано'
+catch error
+  log '❌ Ошибка монтирования приложения:', error
+  console.error('Ошибка монтирования:', error)
 
-app.mount('body')
 ```
 
+Пример файла компонента, или страницы.Пиши стилевые файлы к элементам
+**Важно** не нужно делать Vue = require 'vue' или require 'VueRouter' они уже подключены глобально доступны
+
+** app/pages/Home/index.coffee**
+```
+# app/pages/Home/index.coffee
+
+# Добавление стилей страницы
+if globalThis.stylFns and globalThis.stylFns['app/pages/Home/index.styl']
+  styleElement = document.createElement('style')
+  styleElement.type = 'text/css'
+  styleElement.textContent = globalThis.stylFns['app/pages/Home/index.styl']
+  document.head.appendChild(styleElement)
+else
+  log '⚠️ Стили главной страницы не найдены'
+
+module.exports = {
+  # Импорт компонентов 
+  components: {
+    'product-grid': require 'app/components/Domain/ProductGrid/index.coffee'
+  }
+
+  props:
+    domainSettings:
+      type: Object
+      default: -> {}
+    language:
+      type: String
+      default: 'ru'
+
+  data: ->
+    {
+      welcomeText: 'Добро пожаловать в Браер-Колор'
+      loading: false
+      productsLoading: false
+      featuredProducts: []
+      features: [
+        {
+          title: 'Качественные материалы'
+          description: 'Широкий ассортимент красок, грунтовок и лакокрасочных материалов от проверенных производителей'
+        }
+        {
+          title: 'Доставка по России'
+          description: 'Быстрая и надежная доставка в любой регион страны. Работаем с ведущими транспортными компаниями'
+        }
+        {
+          title: 'Профессиональные консультации'
+          description: 'Наши специалисты помогут подобрать оптимальные материалы для ваших задач и бюджета'
+        }
+      ]
+    }
+
+  methods:
+    goToCatalog: ->
+      @$router.push '/catalog'
+
+    contactSupport: ->
+      @$emit 'show-notification', 'Форма обратной связи будет добавлена позже', 'info'
+
+    viewProduct: (productId) ->
+      @$router.push "/product/#{productId}"
+
+    loadFeaturedProducts: ->
+      @productsLoading = true
+      # Заглушка для загрузки товаров
+      setTimeout (=>
+        @featuredProducts = [
+          { id: 1, name: 'Грунтовка глубокого проникновения', price: 528, image: '' },
+          { id: 2, name: 'Краска акриловая белая', price: 890, image: '' },
+          { id: 3, name: 'Эмаль для металла', price: 670, image: '' }
+        ]
+        @productsLoading = false
+      ), 1000
+
+  mounted: ->
+    log 'Главная страница загружена'
+    @loadFeaturedProducts()
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/pages/Home/index.pug'])()
+}
+
+```
+
+``` 
+# app/components/UI/Button/index.coffee
+
+# Добавление стилей компонента
+if globalThis.stylFns and globalThis.stylFns['app/components/UI/Button/index.styl']
+  styleElement = document.createElement('style')
+  styleElement.type = 'text/css'
+  styleElement.textContent = globalThis.stylFns['app/components/UI/Button/index.styl']
+  document.head.appendChild(styleElement)
+else
+  log '⚠️ Стили кнопки не найдены'
+
+module.exports = 
+  name: 'ui-button'
+  props:
+    type:
+      type: String
+      default: 'primary'
+      validator: (value) ->
+        ['primary', 'secondary', 'success', 'danger', 'outline'].includes(value)
+    size:
+      type: String  
+      default: 'medium'
+      validator: (value) ->
+        ['small', 'medium', 'large'].includes(value)
+    disabled:
+      type: Boolean
+      default: false
+    loading:
+      type: Boolean
+      default: false
+
+  methods:
+    handleClick: (event) ->
+      if not @disabled and not @loading
+        @$emit 'click', event
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/components/UI/Button/index.pug'])()
+
+
+```
+
+
+
+
+
+**app/router/index.coffee** пример, VueRouter определён глобально вызывать отдельно не нужно
+```coffee
+# app/router/index.coffee
+config = require 'app/config'
+
+
+# Middleware для проверки прав доступа
+authGuard = (to, from, next) ->
+  log 'Проверка прав доступа для route:', to.path
+  # Здесь будет логика проверки пользователя из глобального состояния
+  if to.matched.some (record) -> record.meta.requiresAuth
+    # Проверка авторизации
+    next() # Временная заглушка - всегда разрешаем доступ
+  else
+    next()
+
+domainMiddleware = (to, from, next) ->
+  log 'Обработка динамического домена для route'
+  # Логика обработки домена будет интегрирована позже
+  next()
+
+router = VueRouter.createRouter({
+  history: VueRouter.createWebHistory()
+  routes: [
+    {
+      path: '/'
+      name: 'Home'
+      component: require 'app/pages/Home/index.coffee'
+      beforeEnter: [domainMiddleware]
+    }
+    {
+      path: '/catalog'
+      name: 'Catalog'
+      component: require 'app/pages/Catalog/index.coffee'
+      beforeEnter: [domainMiddleware]
+    }
+    {
+      path: '/catalog/:category?'
+      name: 'CatalogCategory'
+      component: require 'app/pages/Catalog/index.coffee'
+      beforeEnter: [domainMiddleware]
+    }
+    {
+      path: '/product/:id'
+      name: 'Product'
+      component: require 'app/pages/Product/index.coffee'
+      beforeEnter: [domainMiddleware]
+    }
+    {
+      path: '/cart'
+      name: 'Cart'
+      component: require 'app/pages/Cart/index.coffee'
+      beforeEnter: [domainMiddleware]
+    }
+    {
+      path: '/admin'
+      name: 'Admin'
+      component: require 'app/pages/Admin/index.coffee'
+      meta: { requiresAuth: true }
+      beforeEnter: [domainMiddleware, authGuard]
+    }
+    {
+      path: '/:pathMatch(.*)*'
+      name: 'NotFound'
+      component: require 'app/pages/NotFound/index.coffee'
+      beforeEnter: [domainMiddleware]
+    }
+  ]
+})
+
+# Глобальные обработчики роутера
+router.beforeEach (to, from, next) ->
+  log "Переход с "+ from.path +" на "+to.path+""
+  next()
+
+router.afterEach (to, from) ->
+  log "Навигация завершена на "+to.path+""
+
+module.exports = router
+
+
+
+
+
 ## 🚨 КРИТИЧЕСКИЕ ПРАВИЛА РАЗРАБОТКИ
 
 ### ❌ **ЗАПРЕЩЕНО:**
@@ -921,6 +1253,32 @@ importFromCSV: (file, domain, onProgress) ->
    - Напиши базовые файлы системы
    - Система роутинга
    - Глобальная конфигурация
+Добавлено подключение компонентов во все объекты где они используются:
+
+Главное приложение подключает все UI и Domain компоненты
+
+Страницы подключают используемые компоненты через components
+
+Роутер подключает все страницы приложения
+
+Создана полная структура компонентов:
+
+UI компоненты: Button, Notification, AppLoader, ThemeToggle, LanguageToggle
+
+Domain компоненты: MultilevelMenu, CartWidget, ProductGrid, ProductCard
+
+Страницы: Home, Catalog, Product, Cart, Admin, NotFound
+
+Настроена правильная иерархия зависимостей:
+
+Каждый компонент самостоятельно подключает свои стили
+
+Родительские компоненты регистрируют дочерние через components
+
+Главное приложение регистрирует глобальные компоненты
+
+Созданы все необходимые заглушки для полнофункционального приложения
+
 
 ### 🎯 БЛИЖАЙШИЕ ЗАДАЧИ
 
@@ -967,40 +1325,32 @@ importFromCSV: (file, domain, onProgress) ->
    - что сделано.
    - Напиши следущую задачу на выполнение (после завершения отладки, созданых файлов)
 
+ ⚠️ ПРИОРИТЕТ
+
+ЭТАП 1.4: НАСТРОЙКА POUCHDB СЕРВИСА И ДИЗАЙН-ДОКУМЕНТОВ
+
+Реализовать полноценный PouchDB сервис:
+
+Подключение к CouchDB с аутентификацией
+
+Система синхронизации с фильтрацией по доменам
+
+Обработка ошибок и повторов
+
+Создать дизайн-документы CouchDB:
+
+Views для товаров, категорий, заказов
+
+Validate_doc_update функции
+
+Система индексов для поиска
+
+Разработать сервисы данных:
+
+ProductService для работы с товарами
+
+CategoryService для управления категориями
+
+DomainService для мультидоменности
 
-ЭТАП 1.2: СИСТЕМА РОУТИНГА И БАЗОВЫЕ СТРАНИЦЫ  ⚠️ ПРИОРИТЕТ
-    Создать Vue Router конфигурацию
-    
-    Файл маршрутизации с основными путями
-    
-    Обработка динамических доменов
-    
-    Middleware для проверки прав доступа
-    
-    Создать базовые страницы:
-    
-    Главная страница (/)
-    
-    Страница 404 (/*)
-    
-    Заглушки для основных разделов
-    
-    Разработать базовые UI компоненты:
-    
-    Кнопка (Button)
-    
-    Модальное окно (Modal)
-    
-    Уведомления (Notification)
-    
-    Индикатор загрузки (Loader)
-    
-    Настроить PouchDB сервис:
-    
-    Подключение к CouchDB
-    
-    Дизайн-документы
-    
-    Система синхронизации
-    
     Приоритет: Высокий ⚠️

+ 25 - 0
app/components/Domain/CartWidget/index.coffee

@@ -0,0 +1,25 @@
+# app/components/Domain/CartWidget/index.coffee
+
+
+module.exports =  
+  name: 'cart-widget'
+  props:
+    items:
+      type: Array
+      default: -> []
+
+  computed:
+    itemsCount: ->
+      @items.reduce ((total, item) -> total + (item.quantity or 1)), 0
+
+    totalPrice: ->
+      @items.reduce ((total, item) -> total + (item.price * (item.quantity or 1))), 0
+
+  methods:
+    goToCart: ->
+      @$emit 'go-to-cart'
+
+    updateCart: (items) ->
+      @$emit 'update-cart', items
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/components/Domain/CartWidget/index.pug'])()

+ 8 - 0
app/components/Domain/CartWidget/index.pug

@@ -0,0 +1,8 @@
+div(class="cart-widget")
+  button(
+    @click="goToCart"
+    class="cart-button"
+    :title="`Корзина: ${itemsCount} товаров`"
+  )
+    span(class="cart-icon") 🛒
+    span(v-if="itemsCount > 0" class="cart-badge") {{ itemsCount }}

+ 27 - 0
app/components/Domain/MultilevelMenu/index.coffee

@@ -0,0 +1,27 @@
+# app/components/Domain/MultilevelMenu/index.coffee
+
+# Добавление стилей компонента
+if globalThis.stylFns and globalThis.stylFns['app/components/Domain/MultilevelMenu/index.styl']
+  styleElement = document.createElement('style')
+  styleElement.type = 'text/css'
+  styleElement.textContent = globalThis.stylFns['app/components/Domain/MultilevelMenu/index.styl']
+  document.head.appendChild(styleElement)
+else
+  log '⚠️ Стили лоадера не найдены'
+
+module.exports =  
+  name: 'multilevel-menu'
+  props:
+    domains:
+      type: Array
+      default: -> []
+    currentDomain:
+      type: String
+      default: ''
+
+  methods:
+    selectDomain: (domain) ->
+      @$emit 'domain-changed', domain
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/components/Domain/MultilevelMenu/index.pug'])()
+

+ 11 - 0
app/components/Domain/MultilevelMenu/index.pug

@@ -0,0 +1,11 @@
+div(class="multilevel-menu")
+  select(
+    :value="currentDomain"
+    @change="selectDomain($event.target.value)"
+    class="domain-select"
+  )
+    option(
+      v-for="domain in domains"
+      :key="domain"
+      :value="domain"
+    ) {{ domain }}

+ 12 - 0
app/components/Domain/ProductCard/index.coffee

@@ -0,0 +1,12 @@
+# app/components/Domain/ProductCard/index.coffee
+
+
+module.exports =  
+  name: 'product-card'
+  props:
+    product:
+      type: Object
+      default: -> {}
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/components/Domain/ProductCard/index.pug'])()
+

+ 7 - 0
app/components/Domain/ProductCard/index.pug

@@ -0,0 +1,7 @@
+div(class="product-card")
+  div(class="product-card__image")
+    img(v-if="product.image" :src="product.image" :alt="product.name")
+    div(v-else class="product-card__no-image") 📦
+  div(class="product-card__content")
+    h3(class="product-card__name") {{ product.name }}
+    div(class="product-card__price") {{ product.price }} ₽

+ 26 - 0
app/components/Domain/ProductGrid/index.coffee

@@ -0,0 +1,26 @@
+# app/components/Domain/ProductGrid/index.coffee
+
+
+# Импорт компонентов
+
+
+module.exports =  
+  name: 'product-grid'
+  components: {
+    'product-card': require 'app/components/Domain/ProductCard/index.coffee'
+  }
+
+  props:
+    products:
+      type: Array
+      default: -> []
+    loading:
+      type: Boolean
+      default: false
+
+  methods:
+    productClick: (productId) ->
+      @$emit 'product-click', productId
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/components/Domain/ProductGrid/index.pug'])()
+

+ 9 - 0
app/components/Domain/ProductGrid/index.pug

@@ -0,0 +1,9 @@
+div(class="product-grid")
+  div(v-if="loading" class="product-grid__loading") Загрузка товаров...
+  div(v-else class="product-grid__container")
+    product-card(
+      v-for="product in products"
+      :key="product.id"
+      :product="product"
+      @click="productClick(product.id)"
+    )

+ 21 - 0
app/components/UI/AppLoader/index.coffee

@@ -0,0 +1,21 @@
+# app/components/UI/AppLoader/index.coffee
+
+
+# Добавление стилей компонента
+if globalThis.stylFns and globalThis.stylFns['app/components/UI/AppLoader/index.styl']
+  styleElement = document.createElement('style')
+  styleElement.type = 'text/css'
+  styleElement.textContent = globalThis.stylFns['app/components/UI/AppLoader/index.styl']
+  document.head.appendChild(styleElement)
+else
+  log '⚠️ Стили лоадера не найдены'
+
+module.exports =  
+  name: 'app-loader'
+  props:
+    loading:
+      type: Boolean
+      default: false
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/components/UI/AppLoader/index.pug'])()
+

+ 5 - 0
app/components/UI/AppLoader/index.pug

@@ -0,0 +1,5 @@
+div(v-if="loading" class="app-loader")
+  div(class="app-loader__overlay")
+  div(class="app-loader__content")
+    div(class="app-loader__spinner")
+    div(class="app-loader__text") Загрузка...

+ 52 - 0
app/components/UI/AppLoader/index.styl

@@ -0,0 +1,52 @@
+// Стили для лоадера
+.app-loader
+  position: fixed
+  top: 0
+  left: 0
+  width: 100%
+  height: 100%
+  z-index: var(--z-modal)
+
+.app-loader__overlay
+  position: absolute
+  top: 0
+  left: 0
+  width: 100%
+  height: 100%
+  background: var(--color-dark-50)
+  backdrop-filter: blur(2px)
+
+.app-loader__content
+  position: absolute
+  top: 50%
+  left: 50%
+  transform: translate(-50%, -50%)
+  background: var(--color-white)
+  padding: var(--spacing-xl)
+  border-radius: var(--border-radius)
+  box-shadow: var(--shadow-lg)
+  text-align: center
+  min-width: 200px
+  
+  .theme-dark &
+    background: var(--color-dark)
+    color: var(--color-light)
+
+.app-loader__spinner
+  width: 40px
+  height: 40px
+  border: 3px solid var(--color-primary-10)
+  border-top: 3px solid var(--color-primary)
+  border-radius: 50%
+  animation: loader-spin 1s linear infinite
+  margin: 0 auto var(--spacing-md)
+
+.app-loader__text
+  font-size: var(--font-size-base)
+  color: var(--color-secondary)
+
+@keyframes loader-spin
+  0%
+    transform: rotate(0deg)
+  100%
+    transform: rotate(360deg)

+ 38 - 0
app/components/UI/Button/index.coffee

@@ -0,0 +1,38 @@
+# app/components/UI/Button/index.coffee
+
+# Добавление стилей компонента
+if globalThis.stylFns and globalThis.stylFns['app/components/UI/Button/index.styl']
+  styleElement = document.createElement('style')
+  styleElement.type = 'text/css'
+  styleElement.textContent = globalThis.stylFns['app/components/UI/Button/index.styl']
+  document.head.appendChild(styleElement)
+else
+  log '⚠️ Стили кнопки не найдены'
+
+module.exports = 
+  name: 'ui-button'
+  props:
+    type:
+      type: String
+      default: 'primary'
+      validator: (value) ->
+        ['primary', 'secondary', 'success', 'danger', 'outline'].includes(value)
+    size:
+      type: String  
+      default: 'medium'
+      validator: (value) ->
+        ['small', 'medium', 'large'].includes(value)
+    disabled:
+      type: Boolean
+      default: false
+    loading:
+      type: Boolean
+      default: false
+
+  methods:
+    handleClick: (event) ->
+      if not @disabled and not @loading
+        @$emit 'click', event
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/components/UI/Button/index.pug'])()
+

+ 9 - 0
app/components/UI/Button/index.pug

@@ -0,0 +1,9 @@
+button(
+  class="btn"
+  :class="[`btn--${type}`, `btn--${size}`, { 'btn--disabled': disabled, 'btn--loading': loading }]"
+  :disabled="disabled || loading"
+  @click="handleClick"
+)
+  span(v-if="loading" class="btn__loader") Загрузка...
+  span(v-else class="btn__text")
+    slot

+ 139 - 0
app/components/UI/Button/index.styl

@@ -0,0 +1,139 @@
+// Стили для кнопки
+.btn
+  display: inline-flex
+  align-items: center
+  justify-content: center
+  padding: var(--spacing-sm) var(--spacing-md)
+  border: var(--border-width) solid transparent
+  border-radius: var(--border-radius)
+  font-family: var(--font-family)
+  font-size: var(--font-size-base)
+  font-weight: var(--font-weight-medium)
+  line-height: var(--line-height-tight)
+  text-decoration: none
+  cursor: pointer
+  transition: var(--transition-fast)
+  background: var(--color-primary)
+  color: var(--color-white)
+  position: relative
+  overflow: hidden
+  
+  &:hover:not(.btn--disabled):not(.btn--loading)
+    background: var(--color-primary-50)
+    transform: translateY(-1px)
+    box-shadow: var(--shadow-md)
+  
+  &:active:not(.btn--disabled):not(.btn--loading)
+    transform: translateY(0)
+    box-shadow: var(--shadow-sm)
+  
+  &.btn--disabled
+    opacity: 0.6
+    cursor: not-allowed
+    background: var(--color-secondary)
+  
+  &.btn--loading
+    cursor: wait
+    color: transparent
+  
+  // Размеры
+  &.btn--small
+    padding: var(--spacing-xs) var(--spacing-sm)
+    font-size: var(--font-size-sm)
+  
+  &.btn--medium
+    padding: var(--spacing-sm) var(--spacing-md)
+    font-size: var(--font-size-base)
+  
+  &.btn--large
+    padding: var(--spacing-md) var(--spacing-lg)
+    font-size: var(--font-size-lg)
+    font-weight: var(--font-weight-semibold)
+  
+  // Типы
+  &.btn--primary
+    background: var(--color-primary)
+    border-color: var(--color-primary)
+    
+    &:hover:not(.btn--disabled):not(.btn--loading)
+      background: var(--color-primary-50)
+      border-color: var(--color-primary-50)
+  
+  &.btn--secondary
+    background: var(--color-secondary)
+    border-color: var(--color-secondary)
+    
+    &:hover:not(.btn--disabled):not(.btn--loading)
+      background: var(--color-dark-50)
+      border-color: var(--color-dark-50)
+  
+  &.btn--success
+    background: var(--color-success)
+    border-color: var(--color-success)
+    
+    &:hover:not(.btn--disabled):not(.btn--loading)
+      background: #218838
+      border-color: #1e7e34
+  
+  &.btn--danger
+    background: var(--color-danger)
+    border-color: var(--color-danger)
+    
+    &:hover:not(.btn--disabled):not(.btn--loading)
+      background: #c82333
+      border-color: #bd2130
+  
+  &.btn--outline
+    background: transparent
+    color: var(--color-primary)
+    border-color: var(--color-primary)
+    
+    &:hover:not(.btn--disabled):not(.btn--loading)
+      background: var(--color-primary-10)
+      color: var(--color-primary)
+  
+  // Лоадер
+  .btn__loader
+    position: absolute
+    top: 50%
+    left: 50%
+    transform: translate(-50%, -50%)
+    width: 20px
+    height: 20px
+    border: 2px solid transparent
+    border-top: 2px solid var(--color-white)
+    border-radius: 50%
+    animation: btn-spin 1s linear infinite
+  
+  .btn__text
+    display: flex
+    align-items: center
+    gap: var(--spacing-xs)
+
+// Темная тема
+.theme-dark
+  .btn--outline
+    color: var(--color-light)
+    border-color: var(--color-light)
+    
+    &:hover:not(.btn--disabled):not(.btn--loading)
+      background: var(--color-light-10)
+      color: var(--color-light)
+
+// Анимация лоадера
+@keyframes btn-spin
+  0%
+    transform: translate(-50%, -50%) rotate(0deg)
+  100%
+    transform: translate(-50%, -50%) rotate(360deg)
+
+// Адаптивность
+@media (max-width: 768px)
+  .btn
+    width: 100%
+    justify-content: center
+    
+    &.btn--small,
+    &.btn--medium,
+    &.btn--large
+      width: 100%

+ 19 - 0
app/components/UI/LanguageToggle/index.coffee

@@ -0,0 +1,19 @@
+# app/components/UI/LanguageToggle/index.coffee
+
+
+module.exports =  
+  name: 'language-toggle'
+  props:
+    languages:
+      type: Array
+      default: -> ['ru']
+    currentLanguage:
+      type: String
+      default: 'ru'
+
+  methods:
+    changeLanguage: (lang) ->
+      @$emit 'language-changed', lang
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/components/UI/LanguageToggle/index.pug'])()
+

+ 11 - 0
app/components/UI/LanguageToggle/index.pug

@@ -0,0 +1,11 @@
+div(class="language-toggle")
+  select(
+    :value="currentLanguage"
+    @change="changeLanguage($event.target.value)"
+    class="language-select"
+  )
+    option(
+      v-for="lang in languages"
+      :key="lang"
+      :value="lang"
+    ) {{ lang.toUpperCase() }}

+ 33 - 0
app/components/UI/Notification/index.coffee

@@ -0,0 +1,33 @@
+# app/components/UI/Notification/index.coffee
+
+
+# Добавление стилей компонента
+if globalThis.stylFns and globalThis.stylFns['app/components/UI/Notification/index.styl']
+  styleElement = document.createElement('style')
+  styleElement.type = 'text/css'
+  styleElement.textContent = globalThis.stylFns['app/components/UI/Notification/index.styl']
+  document.head.appendChild(styleElement)
+else
+  log '⚠️ Стили уведомлений не найдены'
+
+module.exports =  
+  name: 'notification-container'
+  props:
+    notifications:
+      type: Array
+      default: -> []
+
+  methods:
+    closeNotification: (id) ->
+      @$emit 'close-notification', id
+
+    formatTime: (timestamp) ->
+      return '' unless timestamp
+      date = new Date(timestamp)
+      date.toLocaleTimeString('ru-RU', { 
+        hour: '2-digit', 
+        minute: '2-digit' 
+      })
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/components/UI/Notification/index.pug'])()
+

+ 17 - 0
app/components/UI/Notification/index.pug

@@ -0,0 +1,17 @@
+div(class="notification-container")
+  div(
+    v-for="notification in notifications"
+    :key="notification.id"
+    class="notification"
+    :class="`notification--${notification.type}`"
+    v-show="notification.visible"
+  )
+    div(class="notification__content")
+      div(class="notification__message") {{ notification.message }}
+      div(v-if="notification.timestamp" class="notification__timestamp") 
+        | {{ formatTime(notification.timestamp) }}
+    button(
+      class="notification__close"
+      @click="closeNotification(notification.id)"
+      aria-label="Закрыть уведомление"
+    ) ×

+ 141 - 0
app/components/UI/Notification/index.styl

@@ -0,0 +1,141 @@
+// Стили для уведомлений
+.notification-container
+  position: fixed
+  top: var(--spacing-xl)
+  right: var(--spacing-xl)
+  z-index: var(--z-notification)
+  display: flex
+  flex-direction: column
+  gap: var(--spacing-sm)
+  
+  @media (max-width: 768px)
+    top: var(--spacing-md)
+    right: var(--spacing-md)
+    left: var(--spacing-md)
+
+.notification
+  position: relative
+  padding: var(--spacing-md) var(--spacing-lg)
+  border-radius: var(--border-radius)
+  box-shadow: var(--shadow-lg)
+  background: var(--color-white)
+  color: var(--color-dark)
+  border-left: 4px solid var(--color-primary)
+  min-width: 300px
+  max-width: 400px
+  animation: notificationSlideIn 0.3s ease
+  display: flex
+  align-items: flex-start
+  justify-content: space-between
+  gap: var(--spacing-md)
+  
+  &.notification-leave-active
+    animation: notificationSlideOut 0.3s ease
+  
+  // Типы уведомлений
+  &.notification--info
+    border-left-color: var(--color-primary)
+    background: linear-gradient(135deg, var(--color-white) 0%, var(--color-primary-10) 100%)
+  
+  &.notification--success
+    border-left-color: var(--color-success)
+    background: linear-gradient(135deg, var(--color-white) 0%, rgba(40, 167, 69, 0.1) 100%)
+  
+  &.notification--error
+    border-left-color: var(--color-danger)
+    background: linear-gradient(135deg, var(--color-white) 0%, rgba(220, 53, 69, 0.1) 100%)
+  
+  &.notification--warning
+    border-left-color: var(--color-warning)
+    background: linear-gradient(135deg, var(--color-white) 0%, rgba(255, 193, 7, 0.1) 100%)
+  
+  // Контент уведомления
+  .notification__content
+    flex: 1
+    display: flex
+    flex-direction: column
+    gap: var(--spacing-xs)
+  
+  .notification__message
+    font-size: var(--font-size-base)
+    line-height: var(--line-height-tight)
+    margin: 0
+  
+  .notification__timestamp
+    font-size: var(--font-size-xs)
+    color: var(--color-secondary)
+    opacity: 0.8
+  
+  .notification__close
+    background: none
+    border: none
+    font-size: var(--font-size-lg)
+    cursor: pointer
+    color: var(--color-secondary)
+    padding: var(--spacing-xs)
+    border-radius: var(--border-radius-sm)
+    transition: var(--transition-fast)
+    flex-shrink: 0
+    width: 24px
+    height: 24px
+    display: flex
+    align-items: center
+    justify-content: center
+    
+    &:hover
+      background: var(--color-dark-10)
+      color: var(--color-dark)
+
+// Темная тема
+.theme-dark
+  .notification
+    background: var(--color-dark)
+    color: var(--color-light)
+    border-left-color: var(--color-light-50)
+    
+    &.notification--info
+      background: linear-gradient(135deg, var(--color-dark) 0%, var(--color-primary-20) 100%)
+    
+    &.notification--success
+      background: linear-gradient(135deg, var(--color-dark) 0%, rgba(40, 167, 69, 0.2) 100%)
+    
+    &.notification--error
+      background: linear-gradient(135deg, var(--color-dark) 0%, rgba(220, 53, 69, 0.2) 100%)
+    
+    &.notification--warning
+      background: linear-gradient(135deg, var(--color-dark) 0%, rgba(255, 193, 7, 0.2) 100%)
+    
+    .notification__close
+      color: var(--color-light-50)
+      
+      &:hover
+        background: var(--color-light-10)
+        color: var(--color-light)
+
+// Анимации
+@keyframes notificationSlideIn
+  from
+    transform: translateX(100%)
+    opacity: 0
+  to
+    transform: translateX(0)
+    opacity: 1
+
+@keyframes notificationSlideOut
+  from
+    transform: translateX(0)
+    opacity: 1
+  to
+    transform: translateX(100%)
+    opacity: 0
+
+// Адаптивность
+@media (max-width: 480px)
+  .notification
+    min-width: auto
+    max-width: none
+    width: 100%
+    
+  .notification-container
+    right: var(--spacing-xs)
+    left: var(--spacing-xs)

+ 15 - 0
app/components/UI/ThemeToggle/index.coffee

@@ -0,0 +1,15 @@
+# app/components/UI/ThemeToggle/index.coffee
+
+module.exports =  
+  name: 'theme-toggle'
+  props:
+    theme:
+      type: String
+      default: 'light'
+
+  methods:
+    toggle: ->
+      @$emit 'theme-changed', if @theme == 'light' then 'dark' else 'light'
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/components/UI/ThemeToggle/index.pug'])()
+

+ 0 - 0
app/components/UI/ThemeToggle/index.pug


+ 24 - 0
app/config.coffee

@@ -0,0 +1,24 @@
+# Конфигурация приложения
+class AppConfig
+  constructor: ->
+    @version = '1.0.0'
+    @buildDate = '2024-01-01'
+    
+    # Настройки языков
+    @languages = ['ru', 'en']
+    @defaultLanguage = 'ru'
+    
+    # Настройки API
+    @apiBaseUrl = window.location.origin
+    @couchDbUrl = 'https://oleg:631074@couchdb.favt.ru.net/braer_color_shop'
+    
+    # Настройки приложения
+    @companyName = 'Браер-Колор'
+    @defaultTheme = 'light'
+    @pageSize = 20
+    
+    # Пути к ресурсам
+    @imageBasePath = '/images'
+    @uploadPath = '/uploads'
+
+module.exports = new AppConfig()

+ 31 - 8
app/index.coffee

@@ -83,6 +83,17 @@ app = Vue.createApp({
       else
         log '⚠️ Язык не поддерживается:', lang
     
+    # Смена домена
+    changeDomain: (domain) ->
+      log '🌐 Смена домена на:', domain
+      @currentDomain = domain
+      @loadDomainData()
+    
+    # Переход в корзину
+    goToCart: ->
+      @$router.push '/cart'
+      log '🛒 Переход в корзину'
+    
     # Загрузка настроек домена
     loadDomainData: ->
       log '📡 Загрузка настроек домена:', @currentDomain
@@ -124,6 +135,11 @@ app = Vue.createApp({
         ), 300
       ), 5000
     
+    # Закрытие уведомления
+    closeNotification: (id) ->
+      @notifications = @notifications.filter (notification) -> notification.id != id
+      log '📢 Уведомление закрыто:', id
+    
     # Загрузка пользователя
     loadUserData: ->
       userData = localStorage.getItem 'user'
@@ -192,9 +208,23 @@ app = Vue.createApp({
     await @initializeApp()
   
   # Рендер функция из Pug
-  render: (new Function '_ctx', '_cache', renderFns['app/index.pug'])()
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/index.pug'])()
 })
 
+
+Router = require 'app/router/index.coffee'
+# Регистрация глобальных компонентов
+app.component('ui-button', require 'app/components/UI/Button/index.coffee')
+app.component('notification-container', require 'app/components/UI/Notification/index.coffee')
+app.component('app-loader', require 'app/components/UI/AppLoader/index.coffee')
+app.component('multilevel-menu', require 'app/components/Domain/MultilevelMenu/index.coffee')
+app.component('theme-toggle', require 'app/components/UI/ThemeToggle/index.coffee')
+app.component('language-toggle', require 'app/components/UI/LanguageToggle/index.coffee')
+app.component('cart-widget', require 'app/components/Domain/CartWidget/index.coffee')
+
+# Подключение роутера
+app.use Router
+
 # Глобальная обработка ошибок Vue
 app.config.errorHandler = (err, vm, info) ->
   log '💥 Vue ошибка:', err, info
@@ -204,13 +234,6 @@ app.config.errorHandler = (err, vm, info) ->
 app.config.warnHandler = (msg, vm, trace) ->
   log '⚠️ Vue предупреждение:', msg, trace
 
-# Регистрация глобальных компонентов (заглушки)
-app.component('RouterView', { template: '<div>Router View</div>' })
-app.component('RouterLink', { 
-  props: ['to'],
-  template: '<a :href="to"><slot></slot></a>'
-})
-
 # Монтирование приложения
 try
   app.mount('body')

+ 30 - 16
app/index.pug

@@ -5,21 +5,32 @@ div(class="app" :class="{'theme-dark': theme === 'dark'}")
       div(class="header-nav-block")
         div(class="header-nav--name") {{ currentDomainSettings?.companyName || companyName }}
         div(class="header-nav--menu")
-          //- TODO: Добавить компоненты
-          //- multilevelmenu(:domains="availableDomains" :current-domain="currentDomain")
-          //- themetoggle(:theme="theme" @theme-changed="toggleTheme")
-          //- languagetoggle(:languages="languages" :current-language="currentLanguage" @language-changed="setLanguage")
-          //- cartwidget(:items="cartItems" @update-cart="updateCart")
+          //- Мультиуровневое меню доменов
+          multilevel-menu(
+            :domains="availableDomains" 
+            :current-domain="currentDomain"
+            @domain-changed="changeDomain"
+          )
           
-          //- Временные элементы
-          button(@click="toggleTheme" class="btn btn-sm") 
-            span(v-if="theme === 'light'") 🌙
-            span(v-else) ☀️
+          //- Переключение темы
+          theme-toggle(
+            :theme="theme" 
+            @theme-changed="toggleTheme"
+          )
           
-          span(class="badge") {{ currentLanguage }}
+          //- Переключение языка
+          language-toggle(
+            :languages="languages" 
+            :current-language="currentLanguage" 
+            @language-changed="setLanguage"
+          )
           
-          button(@click="showNotification('Тестовое уведомление')" class="btn btn-sm") 
-            | 🔔
+          //- Виджет корзины
+          cart-widget(
+            :items="cartItems" 
+            @update-cart="updateCart"
+            @go-to-cart="goToCart"
+          )
   
   main(class="main-content")
     router-view(v-slot="{ Component, route }")
@@ -31,8 +42,11 @@ div(class="app" :class="{'theme-dark': theme === 'dark'}")
           :language="currentLanguage"
         )
   
-  //- TODO: Добавить компонент уведомлений
-  //- notification-container(:notifications="notifications")
+  //- Контейнер уведомлений
+  notification-container(
+    :notifications="notifications"
+    @close-notification="closeNotification"
+  )
   
-  div(v-if="loading" class="loading-overlay")
-    div(class="loading-spinner") Загрузка...
+  //- Индикатор загрузки
+  app-loader(:loading="loading")

+ 48 - 0
app/pages/Admin/Clients/index.coffee

@@ -0,0 +1,48 @@
+# app/pages/Admin/Clients/index.coffee
+document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Admin/Clients/index.styl']+'</style>')
+
+CouchDB = require 'app/utils/couch'
+
+module.exports =
+  name: 'AdminClients'
+  render: (new Function '_ctx', '_cache', renderFns['app/pages/Admin/Clients/index.pug'])()
+  
+  data: ->
+    users: []
+    clientSearch: ''
+  
+  computed:
+    filteredUsers: ->
+      if !@clientSearch
+        return @users
+      search = @clientSearch.toLowerCase()
+      return @users.filter (user) =>
+        name = "#{user.profile?.firstName || ''} #{user.profile?.lastName || ''}".toLowerCase()
+        email = user.email?.toLowerCase() || ''
+        phone = user.profile?.phone?.toLowerCase() || ''
+        name.includes(search) || email.includes(search) || phone.includes(search)
+  
+  mounted: ->
+    @loadUsers()
+  
+  methods:
+    # app/pages/Admin/Clients/index.coffee - обновите метод loadUsers
+    loadUsers: ->
+      # Используем view вместо find
+      @$root.couchRequest('queryView', 'admin', 'users_by_created', {
+        descending: true
+        limit: 100
+      })
+      .then (result) =>
+        @users = result.rows.map (row) -> row.value
+      .catch (error) =>
+        console.error 'Ошибка загрузки пользователей:', error
+        # Fallback к обычному find
+        @$root.couchRequest('find', {
+          selector: { type: 'user' }
+          sort: [{createdAt: 'desc'}]
+          limit: 100
+        })
+        .then (result) =>
+          @users = result.docs
+    

+ 39 - 0
app/pages/Admin/Clients/index.pug

@@ -0,0 +1,39 @@
+// app/pages/Admin/Clients/index.pug
+div
+  .flex.justify-between.items-center.mb-6
+    h1(class="text-2xl font-bold text-gray-900 dark:text-white") Управление клиентами
+    .flex.space-x-4
+      input(
+        v-model="clientSearch"
+        type="text"
+        placeholder="Поиск клиентов..."
+        class="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-700 dark:border-gray-600"
+      )
+  
+  .overflow-x-auto
+    table(class="min-w-full divide-y divide-gray-200 dark:divide-gray-700")
+      thead(class="bg-gray-50 dark:bg-gray-700")
+        tr
+          th(class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider") Имя
+          th(class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider") Email
+          th(class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider") Телефон
+          th(class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider") Заказов
+          th(class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider") Дата регистрации
+          th(class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider") Действия
+      tbody(class="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700")
+        tr(v-for="user in filteredUsers" :key="user._id")
+          td(class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white") 
+            | {{ user.profile?.firstName }} {{ user.profile?.lastName }}
+          td(class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300") {{ user.email }}
+          td(class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300") {{ user.profile?.phone || '-' }}
+          td(class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300") {{ user.orders?.length || 0 }}
+          td(class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300") {{ formatDate(user.createdAt) }}
+          td(class="px-6 py-4 whitespace-nowrap text-sm font-medium")
+            button(
+              @click="viewUser(user)"
+              class="text-blue-600 hover:text-blue-900 mr-3"
+            ) Просмотр
+            button(
+              @click="editUser(user)"
+              class="text-green-600 hover:text-green-900"
+            ) Редактировать

+ 6 - 0
app/pages/Admin/Clients/index.styl

@@ -0,0 +1,6 @@
+// app/pages/Admin/Clients/index.styl
+@css {
+  .client-table {
+    @apply min-w-full divide-y divide-gray-200 dark:divide-gray-700;
+  }
+}

+ 104 - 0
app/pages/Admin/Routes/index.coffee

@@ -0,0 +1,104 @@
+# app/pages/Admin/Routes/index.coffee
+document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Admin/Routes/index.styl']+'</style>')
+
+CouchDB = require 'app/utils/couch'
+
+module.exports =
+  name: 'AdminRoutes'
+  render: (new Function '_ctx', '_cache', renderFns['app/pages/Admin/Routes/index.pug'])()
+  
+  data: ->
+    routes: []
+    showRouteModal: false
+    currentRoute: {
+      path: ''
+      name: ''
+      component: ''
+      metaTitle: ''
+      metaDescription: ''
+      active: true
+    }
+  
+  mounted: ->
+    @loadRoutes()
+  
+  methods:
+    loadRoutes: ->
+      @$root.couchRequest('find', {
+        selector: { type: 'route' }
+        sort: [{path: 'asc'}]
+      })
+      .then (result) =>
+        @routes = result.docs
+      .catch (error) =>
+        console.error 'Ошибка загрузки маршрутов:', error
+        @$root.showNotification 'Ошибка загрузки маршрутов', 'error'
+    
+    editRoute: (route) ->
+      @currentRoute = { ...route }
+      @showRouteModal = true
+    
+    saveRoute: ->
+      routeData = {
+        type: 'route'
+        ...@currentRoute
+        updatedAt: new Date().toISOString()
+      }
+      
+      if !routeData._id
+        routeData.createdAt = new Date().toISOString()
+      
+      @$root.couchRequest('put', routeData)
+      .then (result) =>
+        if routeData._id
+          index = @routes.findIndex (r) -> r._id == routeData._id
+          if index != -1
+            @routes.splice(index, 1, { ...routeData, _id: routeData._id, _rev: result.rev })
+        else
+          @routes.push({ ...routeData, _id: result.id, _rev: result.rev })
+        
+        @showRouteModal = false
+        @resetCurrentRoute()
+        @$root.showNotification 'Маршрут успешно сохранен'
+        
+        # Обновляем роутер
+        @updateRouter()
+      .catch (error) =>
+        console.error 'Ошибка сохранения маршрута:', error
+        @$root.showNotification 'Ошибка сохранения маршрута', 'error'
+    
+    deleteRoute: (routeId) ->
+      if confirm('Вы уверены, что хотите удалить этот маршрут?')
+        route = @routes.find (r) -> r._id == routeId
+        @$root.couchRequest('put', {
+          _id: routeId
+          _rev: route._rev
+          _deleted: true
+        })
+        .then =>
+          @routes = @routes.filter (r) -> r._id != routeId
+          @$root.showNotification 'Маршрут успешно удален'
+          @updateRouter()
+        .catch (error) =>
+          console.error 'Ошибка удаления маршрута:', error
+          @$root.showNotification 'Ошибка удаления маршрута', 'error'
+    
+    resetCurrentRoute: ->
+      @currentRoute = {
+        path: ''
+        name: ''
+        component: ''
+        metaTitle: ''
+        metaDescription: ''
+        active: true
+      }
+    
+    getRouteStatusClass: (isActive) ->
+      if isActive
+        return 'px-2 py-1 text-xs rounded-full bg-green-100 text-green-800'
+      else
+        return 'px-2 py-1 text-xs rounded-full bg-red-100 text-red-800'
+    
+    updateRouter: ->
+      # Здесь можно добавить логику для динамического обновления роутера
+      console.log 'Роутер должен быть обновлен'

+ 104 - 0
app/pages/Admin/Routes/index.pug

@@ -0,0 +1,104 @@
+// app/pages/Admin/Routes/index.pug
+div
+  .flex.justify-between.items-center.mb-6
+    h1(class="text-2xl font-bold text-gray-900 dark:text-white") Управление маршрутами
+    button(
+      @click="showRouteModal = true"
+      class="bg-primary-500 text-white px-4 py-2 rounded-lg hover:bg-primary-600 transition-colors"
+    ) + Новый маршрут
+  
+  .bg-white.dark_bg-gray-800.rounded-lg.shadow.overflow-hidden
+    table(class="min-w-full divide-y divide-gray-200 dark:divide-gray-700")
+      thead(class="bg-gray-50 dark:bg-gray-700")
+        tr
+          th(class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider") Путь
+          th(class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider") Компонент
+          th(class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider") Название
+          th(class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider") Статус
+          th(class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider") Действия
+      tbody(class="divide-y divide-gray-200 dark:divide-gray-700")
+        tr(v-for="route in routes" :key="route._id")
+          td(class="px-6 py-4 whitespace-nowrap text-sm font-mono text-gray-900 dark:text-white") {{ route.path }}
+          td(class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300") {{ route.component }}
+          td(class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 dark:text-gray-300") {{ route.name }}
+          td(class="px-6 py-4 whitespace-nowrap")
+            span(
+              :class="getRouteStatusClass(route.active)"
+            ) {{ route.active ? 'Активен' : 'Неактивен' }}
+          td(class="px-6 py-4 whitespace-nowrap text-sm font-medium")
+            button(
+              @click="editRoute(route)"
+              class="text-blue-600 hover:text-blue-900 mr-3"
+            ) Редактировать
+            button(
+              @click="deleteRoute(route._id)"
+              class="text-red-600 hover:text-red-900"
+            ) Удалить
+
+  // Модальное окно маршрута
+  modalwindow(
+    v-if="showRouteModal"
+    @close="showRouteModal = false"
+    :title="currentRoute._id ? 'Редактирование маршрута' : 'Новый маршрут'"
+  )
+    .space-y-4
+      .grid.grid-cols-2.gap-4
+        div
+          label(class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1") Путь
+          input(
+            v-model="currentRoute.path"
+            type="text"
+            placeholder="/about"
+            class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-700 dark:border-gray-600"
+          )
+        div
+          label(class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1") Название
+          input(
+            v-model="currentRoute.name"
+            type="text"
+            placeholder="about"
+            class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-700 dark:border-gray-600"
+          )
+      .grid.grid-cols-1.gap-4
+        label(class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1") Компонент
+        select(
+          v-model="currentRoute.component"
+          class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-700 dark:border-gray-600"
+        )
+          option(value="") Выберите компонент
+          option(value="About") About
+          option(value="Contacts") Contacts
+          option(value="BlogArticle") BlogArticle
+          option(value="CustomPage") CustomPage
+      .grid.grid-cols-2.gap-4
+        div
+          label(class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1") Мета-заголовок
+          input(
+            v-model="currentRoute.metaTitle"
+            type="text"
+            class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-700 dark:border-gray-600"
+          )
+        div
+          label(class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1") Мета-описание
+          input(
+            v-model="currentRoute.metaDescription"
+            type="text"
+            class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500 dark:bg-gray-700 dark:border-gray-600"
+          )
+      .flex.items-center
+        input(
+          v-model="currentRoute.active"
+          type="checkbox"
+          id="routeActive"
+          class="w-4 h-4 text-primary-600 border-gray-300 rounded focus:ring-primary-500"
+        )
+        label(for="routeActive" class="ml-2 text-sm text-gray-700 dark:text-gray-300") Активный маршрут
+      .flex.justify-end.space-x-3
+        button(
+          @click="showRouteModal = false"
+          class="px-4 py-2 text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200"
+        ) Отмена
+        button(
+          @click="saveRoute"
+          class="bg-primary-500 text-white px-4 py-2 rounded hover:bg-primary-600 transition-colors"
+        ) {{ currentRoute._id ? 'Обновить' : 'Сохранить' }}

+ 14 - 0
app/pages/Admin/Routes/index.styl

@@ -0,0 +1,14 @@
+// app/pages/Admin/Routes/index.styl
+@css {
+  .routes-table {
+    @apply min-w-full divide-y divide-gray-200 dark:divide-gray-700;
+  }
+  
+  .route-status-active {
+    @apply px-2 py-1 text-xs rounded-full bg-green-100 text-green-800;
+  }
+  
+  .route-status-inactive {
+    @apply px-2 py-1 text-xs rounded-full bg-red-100 text-red-800;
+  }
+}

+ 14 - 227
app/pages/Admin/index.coffee

@@ -1,231 +1,18 @@
-document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Admin/index.styl']+'</style>')
+# app/pages/Admin/index.coffee
 
-PouchDB = require 'app/utils/pouch'
+module.exports = {
+  props:
+    domainSettings:
+      type: Object
+      default: -> {}
+    language:
+      type: String
+      default: 'ru'
 
-# Иконки для меню (упрощенные компоненты)
-MenuIcons =
-  SliderIcon:
-    template: """
-      <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
-        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8h16M4 16h16"></path>
-      </svg>
-    """
-  
-  ProductsIcon:
-    template: """
-      <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
-        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path>
-      </svg>
-    """
-  
-  ClientsIcon:
-    template: """
-      <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
-        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
-      </svg>
-    """
-  
-  BlogIcon:
-    template: """
-      <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
-        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
-      </svg>
-    """
-  
-  RoutesIcon:
-    template: """
-      <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
-        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
-      </svg>
-    """
-  
-  SettingsIcon:
-    template: """
-      <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
-        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
-        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
-      </svg>
-    """
-
-module.exports =
-  name: 'AdminPanel'
-  components: MenuIcons
-  
-  render: (new Function '_ctx', '_cache', renderFns['app/pages/Admin/index.pug'])()
-  
   data: ->
-    return {
-      currentDomain: window.location.hostname
-      theme: localStorage.getItem('theme') || 'light'
-      mobileMenuOpen: false
-      sidebarCollapsed: false
-      notificationsOpen: false
-      currentDomainSettings: null
-      user: null
-      mainMenuItems: [
-        {
-          id: 'slider'
-          name: 'Слайдер'
-          path: '/admin/slider'
-          icon: 'SliderIcon'
-        }
-        {
-          id: 'products'
-          name: 'Товары'
-          path: '/admin/products'
-          icon: 'ProductsIcon'
-        }
-        {
-          id: 'clients'
-          name: 'Клиенты'
-          path: '/admin/clients'
-          icon: 'ClientsIcon'
-        }
-      ]
-      contentMenuItems: [
-        {
-          id: 'blog'
-          name: 'Блог'
-          path: '/admin/blog'
-          icon: 'BlogIcon'
-        }
-        {
-          id: 'routes'
-          name: 'Маршруты'
-          path: '/admin/routes'
-          icon: 'RoutesIcon'
-        }
-      ]
-      systemMenuItems: [
-        {
-          id: 'settings'
-          name: 'Настройки'
-          path: '/admin/settings'
-          icon: 'SettingsIcon'
-        }
-      ]
-      breadcrumbs: []
-      notifications: []
-      unreadNotifications: 0
+    {
+      pageTitle: 'Админ-панель'
     }
-  
-  computed:
-    currentRoute: ->
-      @$route.path.split('/').pop() || 'settings'
-    
-    userInitials: ->
-      return 'АД' unless @user?.name
-      names = @user.name.split(' ')
-      if names.length >= 2
-        (names[0][0] + names[1][0]).toUpperCase()
-      else
-        @user.name.substring(0, 2).toUpperCase()
-  
-  methods:
-    navigateTo: (path) ->
-      @mobileMenuOpen = false
-      @$router.push(path)
-    
-    getMenuItemClass: (item) ->
-      baseClass = 'admin__nav-item'
-      isActive = @currentRoute == item.id
-      
-      if isActive
-        return "#{baseClass} admin__nav-item--active"
-      else
-        return "#{baseClass} admin__nav-item--inactive"
-    
-    toggleTheme: ->
-      @theme = if @theme == 'light' then 'dark' else 'light'
-      localStorage.setItem 'theme', @theme
-      document.documentElement.classList.toggle 'dark'
-      @$root.theme = @theme
-    
-    refreshData: ->
-      @showNotification 'Данные обновлены'
-    
-    markAsRead: (notificationId) ->
-      notification = @notifications.find (n) -> n.id == notificationId
-      if notification && !notification.read
-        notification.read = true
-        @unreadNotifications -= 1
-    
-    logout: ->
-      localStorage.removeItem 'user'
-      @user = null
-      @$router.push('/')
-      @showNotification 'Вы успешно вышли из системы'
-    
-    loadDomainSettings: ->
-      PouchDB.getDocument("domain_settings:#{@currentDomain}")
-        .then (settings) =>
-          @currentDomainSettings = settings
-        .catch (error) =>
-          debug.log 'Настройки домена не найдены, используются значения по умолчанию'
-          @currentDomainSettings = null
-    
-    loadUserData: ->
-      userData = localStorage.getItem 'user'
-      if userData
-        try
-          @user = JSON.parse userData
-        catch
-          @user = null
-      else
-        # Заглушка для демонстрации
-        @user = { name: 'Администратор Системы', role: 'admin' }
-    
-    updateBreadcrumbs: ->
-      routeName = @currentRoute
-      breadcrumbMap =
-        slider: { title: 'Слайдер' }
-        products: { title: 'Товары' }
-        clients: { title: 'Клиенты' }
-        blog: { title: 'Блог' }
-        routes: { title: 'Маршруты' }
-        settings: { title: 'Настройки' }
-      
-      current = breadcrumbMap[routeName]
-      if current
-        @breadcrumbs = [
-          { title: 'Главная', path: '/admin' }
-          { title: current.title }
-        ]
-      else
-        @breadcrumbs = [{ title: 'Главная' }]
-    
-    showNotification: (message, type = 'success') ->
-      @$root.showNotification?(message, type) || debug.log("#{type}: #{message}")
-  
-  watch:
-    currentRoute:
-      immediate: true
-      handler: ->
-        @updateBreadcrumbs()
-  
-  mounted: ->
-    @loadDomainSettings()
-    @loadUserData()
-    
-    # Инициализация темы
-    if @theme == 'dark'
-      document.documentElement.classList.add 'dark'
-    
-    # Заглушка для уведомлений
-    @notifications = [
-      {
-        id: 1
-        title: 'Новый заказ получен'
-        time: '5 мин назад'
-        read: false
-        icon: 'SettingsIcon'
-      }
-      {
-        id: 2
-        title: 'Обновление системы'
-        time: '1 час назад'
-        read: false
-        icon: 'SettingsIcon'
-      }
-    ]
-    @unreadNotifications = @notifications.filter((n) -> !n.read).length
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/pages/Admin/index.pug'])()
+}

+ 4 - 201
app/pages/Admin/index.pug

@@ -1,201 +1,4 @@
-div(class="admin")
-  // Мобильное меню - кнопка бургер
-  div(class="admin__mobile-toggle" @click="mobileMenuOpen = !mobileMenuOpen")
-    div(class="admin__burger" :class="{'admin__burger--active': mobileMenuOpen}")
-      span
-      span
-      span
-  
-  // Боковое меню для десктопа и мобильных устройств
-  div(
-    class="admin__sidebar" 
-    :class="{'admin__sidebar--mobile-open': mobileMenuOpen, 'admin__sidebar--collapsed': sidebarCollapsed}"
-  )
-    div(class="admin__sidebar-header")
-      div(class="admin__brand" v-if="!sidebarCollapsed")
-        img(
-          v-if="currentDomainSettings && currentDomainSettings.logo"
-          :src="currentDomainSettings.logo" 
-          :alt="currentDomainSettings.companyName || 'Браер-Колор'"
-          class="admin__logo"
-        )
-        span(class="admin__company-name") {{ currentDomainSettings?.companyName || 'Браер-Колор' }}
-      div(class="admin__sidebar-toggle" @click="sidebarCollapsed = !sidebarCollapsed")
-        div(class="admin__toggle-icon" :class="{'admin__toggle-icon--collapsed': sidebarCollapsed}")
-          svg(fill="none" stroke="currentColor" viewBox="0 0 24 24")
-            path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7")
-    
-    nav(class="admin__nav")
-      div(class="admin__nav-section")
-        h3(
-          v-if="!sidebarCollapsed" 
-          class="admin__nav-section-title"
-        ) Основные
-        ul(class="admin__nav-list")
-          li(
-            v-for="item in mainMenuItems"
-            :key="item.id"
-            class="admin__nav-item"
-          )
-            a(
-              :href="item.path"
-              @click.prevent="navigateTo(item.path)"
-              :class="getMenuItemClass(item)"
-              class="admin__nav-link"
-            )
-              component(:is="item.icon" class="admin__nav-icon")
-              span(
-                v-if="!sidebarCollapsed" 
-                class="admin__nav-text"
-              ) {{ item.name }}
-              span(
-                v-if="!sidebarCollapsed && item.badge"
-                class="admin__nav-badge"
-              ) {{ item.badge }}
-      
-      div(class="admin__nav-section")
-        h3(
-          v-if="!sidebarCollapsed" 
-          class="admin__nav-section-title"
-        ) Контент
-        ul(class="admin__nav-list")
-          li(
-            v-for="item in contentMenuItems"
-            :key="item.id"
-            class="admin__nav-item"
-          )
-            a(
-              :href="item.path"
-              @click.prevent="navigateTo(item.path)"
-              :class="getMenuItemClass(item)"
-              class="admin__nav-link"
-            )
-              component(:is="item.icon" class="admin__nav-icon")
-              span(
-                v-if="!sidebarCollapsed" 
-                class="admin__nav-text"
-              ) {{ item.name }}
-      
-      div(class="admin__nav-section")
-        h3(
-          v-if="!sidebarCollapsed" 
-          class="admin__nav-section-title"
-        ) Система
-        ul(class="admin__nav-list")
-          li(
-            v-for="item in systemMenuItems"
-            :key="item.id"
-            class="admin__nav-item"
-          )
-            a(
-              :href="item.path"
-              @click.prevent="navigateTo(item.path)"
-              :class="getMenuItemClass(item)"
-              class="admin__nav-link"
-            )
-              component(:is="item.icon" class="admin__nav-icon")
-              span(
-                v-if="!sidebarCollapsed" 
-                class="admin__nav-text"
-              ) {{ item.name }}
-    
-    div(class="admin__sidebar-footer" v-if="!sidebarCollapsed")
-      div(class="admin__user-info")
-        div(class="admin__user-avatar") {{ userInitials }}
-        div(class="admin__user-details")
-          span(class="admin__user-name") {{ user?.name || 'Администратор' }}
-          span(class="admin__user-role") {{ user?.role || 'Admin' }}
-      button(
-        @click="logout"
-        class="admin__logout-btn"
-      )
-        svg(fill="none" stroke="currentColor" viewBox="0 0 24 24")
-          path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1")
-        span Выйти
-  
-  // Основной контент
-  div(
-    class="admin__main" 
-    :class="{'admin__main--expanded': sidebarCollapsed, 'admin__main--mobile-open': mobileMenuOpen}"
-  )
-    div(class="admin__topbar")
-      div(class="admin__breadcrumbs")
-        span(
-          v-for="(breadcrumb, index) in breadcrumbs"
-          :key="index"
-          class="admin__breadcrumb"
-        )
-          a(
-            v-if="breadcrumb.path && index !== breadcrumbs.length - 1"
-            :href="breadcrumb.path"
-            @click.prevent="navigateTo(breadcrumb.path)"
-            class="admin__breadcrumb-link"
-          ) {{ breadcrumb.title }}
-          span(v-else class="admin__breadcrumb-current") {{ breadcrumb.title }}
-          span(
-            v-if="index !== breadcrumbs.length - 1"
-            class="admin__breadcrumb-separator"
-          ) /
-      
-      div(class="admin__actions")
-        button(
-          @click="toggleTheme"
-          class="admin__action-btn"
-          :title="theme === 'light' ? 'Темная тема' : 'Светлая тема'"
-        )
-          svg(v-if="theme === 'light'" fill="none" stroke="currentColor" viewBox="0 0 24 24")
-            path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z")
-          svg(v-else fill="none" stroke="currentColor" viewBox="0 0 24 24")
-            path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z")
-        
-        button(
-          @click="refreshData"
-          class="admin__action-btn"
-          title="Обновить данные"
-        )
-          svg(fill="none" stroke="currentColor" viewBox="0 0 24 24")
-            path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15")
-        
-        div(class="admin__notifications")
-          button(
-            @click="notificationsOpen = !notificationsOpen"
-            class="admin__action-btn admin__notifications-btn"
-            :class="{'admin__notifications-btn--active': notificationsOpen}"
-          )
-            svg(fill="none" stroke="currentColor" viewBox="0 0 24 24")
-              path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-5 5v-5zM4.5 6.5h15M4.5 12h15")
-            span(v-if="unreadNotifications > 0" class="admin__notification-badge") {{ unreadNotifications }}
-          
-          div(
-            v-if="notificationsOpen"
-            class="admin__notifications-dropdown"
-          )
-            div(class="admin__notifications-header")
-              h4 Уведомления
-              span(class="admin__notifications-count") {{ unreadNotifications }} новых
-            div(class="admin__notifications-list")
-              div(
-                v-for="notification in notifications"
-                :key="notification.id"
-                class="admin__notification-item"
-                :class="{'admin__notification-item--unread': !notification.read}"
-              )
-                div(class="admin__notification-icon")
-                  component(:is="notification.icon")
-                div(class="admin__notification-content")
-                  p(class="admin__notification-title") {{ notification.title }}
-                  span(class="admin__notification-time") {{ notification.time }}
-                button(
-                  @click="markAsRead(notification.id)"
-                  class="admin__notification-action"
-                ) ×
-    
-    div(class="admin__content")
-      router-view
-  
-  // Оверлей для мобильного меню
-  div(
-    v-if="mobileMenuOpen"
-    class="admin__overlay"
-    @click="mobileMenuOpen = false"
-  )
+div(class="admin-page")
+  h1 {{ pageTitle }}
+  p Админ-панель в разработке
+  ui-button(@click="$router.push('/')") На главную

+ 19 - 0
app/pages/Cart/index.coffee

@@ -0,0 +1,19 @@
+# app/pages/Cart/index.coffee
+
+
+module.exports = {
+  props:
+    domainSettings:
+      type: Object
+      default: -> {}
+    language:
+      type: String
+      default: 'ru'
+
+  data: ->
+    {
+      pageTitle: 'Корзина'
+    }
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/pages/Cart/index.pug'])()
+}

+ 4 - 0
app/pages/Cart/index.pug

@@ -0,0 +1,4 @@
+div(class="cart-page")
+  h1 {{ pageTitle }}
+  p Страница корзины в разработке
+  ui-button(@click="$router.push('/catalog')") Продолжить покупки

+ 18 - 0
app/pages/Catalog/index.coffee

@@ -0,0 +1,18 @@
+# app/pages/Catalog/index.coffee
+
+module.exports = {
+  props:
+    domainSettings:
+      type: Object
+      default: -> {}
+    language:
+      type: String
+      default: 'ru'
+
+  data: ->
+    {
+      pageTitle: 'Каталог товаров'
+    }
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/pages/Catalog/index.pug'])()
+}

+ 4 - 0
app/pages/Catalog/index.pug

@@ -0,0 +1,4 @@
+div(class="catalog-page")
+  h1 {{ pageTitle }}
+  p Страница каталога в разработке
+  ui-button(@click="$router.push('/')") На главную

+ 63 - 40
app/pages/Home/index.coffee

@@ -1,52 +1,75 @@
-document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Home/index.styl']+'</style>')
+# app/pages/Home/index.coffee
+
+# Добавление стилей страницы
+if globalThis.stylFns and globalThis.stylFns['app/pages/Home/index.styl']
+  styleElement = document.createElement('style')
+  styleElement.type = 'text/css'
+  styleElement.textContent = globalThis.stylFns['app/pages/Home/index.styl']
+  document.head.appendChild(styleElement)
+else
+  log '⚠️ Стили главной страницы не найдены'
+
+module.exports = {
+  # Импорт компонентов 
+  components: {
+    'product-grid': require 'app/components/Domain/ProductGrid/index.coffee'
+  }
+
+  props:
+    domainSettings:
+      type: Object
+      default: -> {}
+    language:
+      type: String
+      default: 'ru'
 
-module.exports =
-  name: 'HomePage'
-  
-  render: (new Function '_ctx', '_cache', renderFns['app/pages/Home/index.pug'])()
-  
   data: ->
-    return {
+    {
+      welcomeText: 'Добро пожаловать в Браер-Колор'
+      loading: false
+      productsLoading: false
+      featuredProducts: []
       features: [
         {
-          icon: '🎨'
-          title: 'Широкий ассортимент'
-          description: 'Более 1000 видов лакокрасочных материалов от проверенных производителей'
-        }
-        {
-          icon: '🚚'
-          title: 'Быстрая доставка'
-          description: 'Доставка по всему городу в течение 24 часов после заказа'
-        }
-        {
-          icon: '💯'
-          title: 'Гарантия качества'
-          description: 'Все товары сертифицированы и прошли контроль качества'
-        }
-      ]
-      categories: [
-        {
-          name: 'Краски для интерьера'
-          count: '245 товаров'
-          image: '/images/interior-paints.jpg'
-        }
-        {
-          name: 'Фасадные краски'
-          count: '189 товаров'
-          image: '/images/exterior-paints.jpg'
+          title: 'Качественные материалы'
+          description: 'Широкий ассортимент красок, грунтовок и лакокрасочных материалов от проверенных производителей'
         }
         {
-          name: 'Грунтовки'
-          count: '156 товаров'
-          image: '/images/primers.jpg'
+          title: 'Доставка по России'
+          description: 'Быстрая и надежная доставка в любой регион страны. Работаем с ведущими транспортными компаниями'
         }
         {
-          name: 'Лаки и пропитки'
-          count: '98 товаров'
-          image: '/images/varnishes.jpg'
+          title: 'Профессиональные консультации'
+          description: 'Наши специалисты помогут подобрать оптимальные материалы для ваших задач и бюджета'
         }
       ]
     }
-  
+
+  methods:
+    goToCatalog: ->
+      @$router.push '/catalog'
+
+    contactSupport: ->
+      @$emit 'show-notification', 'Форма обратной связи будет добавлена позже', 'info'
+
+    viewProduct: (productId) ->
+      @$router.push "/product/#{productId}"
+
+    loadFeaturedProducts: ->
+      @productsLoading = true
+      # Заглушка для загрузки товаров
+      setTimeout (=>
+        @featuredProducts = [
+          { id: 1, name: 'Грунтовка глубокого проникновения', price: 528, image: '' },
+          { id: 2, name: 'Краска акриловая белая', price: 890, image: '' },
+          { id: 3, name: 'Эмаль для металла', price: 670, image: '' }
+        ]
+        @productsLoading = false
+      ), 1000
+
   mounted: ->
-    debug.log 'Home page mounted'
+    log 'Главная страница загружена'
+    @loadFeaturedProducts()
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/pages/Home/index.pug'])()
+}

+ 48 - 51
app/pages/Home/index.pug

@@ -1,52 +1,49 @@
 div(class="home-page")
-  div(class="home-hero")
-    h1(class="home-hero__title") Добро пожаловать в Браер-Колор
-    p(class="home-hero__subtitle") Интернет-магазин лакокрасочной продукции
-    div(class="home-hero__actions")
-      router-link(to="/catalog" class="btn-primary") В каталог
-      router-link(to="/about" class="btn-secondary") О компании
-
-  div(class="home-features")
-    h2(class="home-features__title") Почему выбирают нас
-    div(class="home-features__grid")
-      div(class="home-feature")
-        div(class="home-feature__icon") 🎨
-        h3(class="home-feature__title") Широкий ассортимент
-        p(class="home-feature__description") Более 1000 видов лакокрасочных материалов от проверенных производителей
-      div(class="home-feature")
-        div(class="home-feature__icon") 🚚
-        h3(class="home-feature__title") Быстрая доставка
-        p(class="home-feature__description") Доставка по всему городу в течение 24 часов после заказа
-      div(class="home-feature")
-        div(class="home-feature__icon") 💯
-        h3(class="home-feature__title") Гарантия качества
-        p(class="home-feature__description") Все товары сертифицированы и прошли контроль качества
-
-  div(class="home-categories")
-    h2(class="home-categories__title") Популярные категории
-    div(class="home-categories__grid")
-      div(class="home-category")
-        img(src="/images/interior-paints.jpg" alt="Краски для интерьера" class="home-category__image")
-        div(class="home-category__content")
-          h3(class="home-category__name") Краски для интерьера
-          p(class="home-category__count") 245 товаров
-      div(class="home-category")
-        img(src="/images/exterior-paints.jpg" alt="Фасадные краски" class="home-category__image")
-        div(class="home-category__content")
-          h3(class="home-category__name") Фасадные краски
-          p(class="home-category__count") 189 товаров
-      div(class="home-category")
-        img(src="/images/primers.jpg" alt="Грунтовки" class="home-category__image")
-        div(class="home-category__content")
-          h3(class="home-category__name") Грунтовки
-          p(class="home-category__count") 156 товаров
-      div(class="home-category")
-        img(src="/images/varnishes.jpg" alt="Лаки и пропитки" class="home-category__image")
-        div(class="home-category__content")
-          h3(class="home-category__name") Лаки и пропитки
-          p(class="home-category__count") 98 товаров
-
-  div(class="home-cta")
-    h2(class="home-cta__title") Готовы начать проект?
-    p(class="home-cta__description") Получите бесплатную консультацию нашего специалиста по подбору материалов
-    router-link(to="/contacts" class="home-cta__button") Связаться с нами
+  div(class="hero-section")
+    h1(class="hero-title") {{ welcomeText }}
+    p(class="hero-subtitle") Интернет-магазин лакокрасочной продукции премиум-класса
+    div(class="hero-actions")
+      ui-button(
+        type="primary"
+        size="large"
+        @click="goToCatalog"
+      ) Перейти в каталог
+      ui-button(
+        type="outline"
+        size="large"
+        @click="contactSupport"
+      ) Связаться с нами
+  
+  div(class="features-section")
+    h2(class="section-title") Наши преимущества
+    div(class="features-grid")
+      div(
+        v-for="(feature, index) in features"
+        :key="index"
+        class="feature-card"
+      )
+        h3 {{ feature.title }}
+        p {{ feature.description }}
+  
+  div(class="products-preview")
+    h2(class="section-title") Популярные товары
+    product-grid(
+      :products="featuredProducts"
+      :loading="productsLoading"
+      @product-click="viewProduct"
+    )
+  
+  div(class="cta-section")
+    h2(class="cta-title") Готовы начать покупки?
+    p(class="cta-description") Ознакомьтесь с нашим каталогом товаров или свяжитесь с нашими специалистами для консультации
+    div(class="hero-actions")
+      ui-button(
+        type="primary"
+        size="large"
+        @click="goToCatalog"
+      ) Смотреть каталог
+      ui-button(
+        type="outline"
+        size="large"
+        @click="contactSupport"
+      ) Получить консультацию

+ 115 - 224
app/pages/Home/index.styl

@@ -1,244 +1,135 @@
+// Стили для главной страницы
 .home-page
-  padding: 2rem 1rem
   max-width: 1200px
   margin: 0 auto
+  padding: 0 var(--spacing-md)
 
-.home-hero
+.hero-section
   text-align: center
-  padding: 4rem 0
-  border-bottom: 1px solid $gray-200
-  margin-bottom: 4rem
-
-  .dark &
-    border-bottom-color: $gray-700
-
-.home-hero__title
-  font-size: 3rem
-  font-weight: bold
-  color: $gray-900
-  margin-bottom: 1rem
-  line-height: 1.2
-
-  .dark &
-    color: $white
-
-.home-hero__subtitle
-  font-size: 1.25rem
-  color: $gray-600
-  margin-bottom: 2rem
-  max-width: 600px
-  margin-left: auto
-  margin-right: auto
-
-  .dark &
-    color: $gray-400
-
-.home-hero__actions
+  padding: var(--spacing-2xl) 0
+  background: linear-gradient(135deg, var(--color-primary-10) 0%, transparent 50%)
+  border-radius: var(--border-radius-lg)
+  margin-bottom: var(--spacing-2xl)
+  
+  .theme-dark &
+    background: linear-gradient(135deg, var(--color-primary-20) 0%, transparent 50%)
+
+.hero-title
+  font-size: var(--font-size-3xl)
+  font-weight: var(--font-weight-bold)
+  margin-bottom: var(--spacing-md)
+  color: var(--color-primary)
+  line-height: var(--line-height-tight)
+  
+  @media (max-width: 768px)
+    font-size: var(--font-size-2xl)
+
+.hero-subtitle
+  font-size: var(--font-size-xl)
+  color: var(--color-secondary)
+  margin-bottom: var(--spacing-xl)
+  line-height: var(--line-height-relaxed)
+  
+  @media (max-width: 768px)
+    font-size: var(--font-size-lg)
+
+.hero-actions
   display: flex
-  gap: 1rem
+  gap: var(--spacing-md)
   justify-content: center
   flex-wrap: wrap
 
-.home-features
-  padding: 4rem 0
-  border-bottom: 1px solid $gray-200
-  margin-bottom: 4rem
-
-  .dark &
-    border-bottom-color: $gray-700
+.features-section
+  padding: var(--spacing-2xl) 0
 
-.home-features__title
-  font-size: 2.25rem
-  font-weight: bold
+.section-title
   text-align: center
-  color: $gray-900
-  margin-bottom: 3rem
-
-  .dark &
-    color: $white
-
-.home-features__grid
+  font-size: var(--font-size-2xl)
+  margin-bottom: var(--spacing-xl)
+  color: var(--color-dark)
+  font-weight: var(--font-weight-semibold)
+  
+  .theme-dark &
+    color: var(--color-light)
+
+.features-grid
   display: grid
-  grid-template-columns: 1fr
-  gap: 2rem
-
-  @media (min-width: 768px)
-    grid-template-columns: repeat(2, 1fr)
-
-  @media (min-width: 1024px)
-    grid-template-columns: repeat(3, 1fr)
-
-.home-feature
-  background-color: $white
-  padding: 2rem
-  border-radius: 0.5rem
-  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1)
-  text-align: center
-  transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out
-
-  &:hover
-    transform: translateY(-2px)
-    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1)
-
-  .dark &
-    background-color: $gray-800
-
-.home-feature__icon
-  width: 4rem
-  height: 4rem
-  margin: 0 auto 1rem
-  background-color: $primary-100
-  border-radius: 50%
-  display: flex
-  align-items: center
-  justify-content: center
-  font-size: 1.5rem
-
-  .dark &
-    background-color: $primary-900
-
-.home-feature__title
-  font-size: 1.25rem
-  font-weight: 600
-  color: $gray-900
-  margin-bottom: 1rem
-
-  .dark &
-    color: $white
-
-.home-feature__description
-  color: $gray-600
-  line-height: 1.6
-
-  .dark &
-    color: $gray-400
-
-.home-categories
-  padding: 4rem 0
-  border-bottom: 1px solid $gray-200
-  margin-bottom: 4rem
-
-  .dark &
-    border-bottom-color: $gray-700
-
-.home-categories__title
-  font-size: 2.25rem
-  font-weight: bold
+  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr))
+  gap: var(--spacing-lg)
+  
+  @media (max-width: 768px)
+    grid-template-columns: 1fr
+    gap: var(--spacing-md)
+
+.feature-card
+  padding: var(--spacing-xl)
+  border-radius: var(--border-radius)
+  background: var(--color-white)
+  box-shadow: var(--shadow-sm)
+  border: var(--border-width) solid var(--border-color)
+  transition: var(--transition-normal)
   text-align: center
-  color: $gray-900
-  margin-bottom: 3rem
-
-  .dark &
-    color: $white
-
-.home-categories__grid
-  display: grid
-  grid-template-columns: 1fr
-  gap: 1.5rem
-
-  @media (min-width: 640px)
-    grid-template-columns: repeat(2, 1fr)
-
-  @media (min-width: 1024px)
-    grid-template-columns: repeat(4, 1fr)
-
-.home-category
-  background-color: $white
-  border-radius: 0.5rem
-  overflow: hidden
-  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1)
-  transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out
-
+  
   &:hover
     transform: translateY(-2px)
-    box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1)
-
-  .dark &
-    background-color: $gray-800
-
-.home-category__image
-  width: 100%
-  height: 200px
-  object-fit: cover
-
-.home-category__content
-  padding: 1.5rem
-
-.home-category__name
-  font-size: 1.125rem
-  font-weight: 600
-  color: $gray-900
-  margin-bottom: 0.5rem
-
-  .dark &
-    color: $white
-
-.home-category__count
-  color: $gray-500
-  font-size: 0.875rem
-
-  .dark &
-    color: $gray-400
-
-.home-cta
-  background: linear-gradient(135deg, $primary-500, $accent-500)
-  color: $white
-  padding: 4rem 1rem
+    box-shadow: var(--shadow-md)
+  
+  .theme-dark &
+    background: var(--color-dark-50)
+    border-color: var(--color-light-10)
+  
+  h3
+    font-size: var(--font-size-lg)
+    font-weight: var(--font-weight-semibold)
+    margin-bottom: var(--spacing-sm)
+    color: var(--color-primary)
+  
+  p
+    color: var(--color-secondary)
+    line-height: var(--line-height-relaxed)
+    margin: 0
+
+.cta-section
   text-align: center
-  border-radius: 1rem
-  margin: 4rem 0
-
-.home-cta__title
-  font-size: 2.25rem
-  font-weight: bold
-  margin-bottom: 1rem
-
-.home-cta__description
-  font-size: 1.125rem
-  margin-bottom: 2rem
-  opacity: 0.9
+  padding: var(--spacing-2xl) 0
+  background: var(--color-light-10)
+  border-radius: var(--border-radius-lg)
+  margin-top: var(--spacing-2xl)
+  
+  .theme-dark &
+    background: var(--color-dark-50)
+
+.cta-title
+  font-size: var(--font-size-2xl)
+  margin-bottom: var(--spacing-md)
+  color: var(--color-dark)
+  
+  .theme-dark &
+    color: var(--color-light)
+
+.cta-description
+  font-size: var(--font-size-lg)
+  color: var(--color-secondary)
+  margin-bottom: var(--spacing-xl)
   max-width: 600px
   margin-left: auto
   margin-right: auto
 
-.home-cta__button
-  background-color: $white
-  color: $primary-600
-  padding: 0.75rem 2rem
-  border-radius: 0.5rem
-  font-weight: 600
-  text-decoration: none
-  display: inline-block
-  transition: all 0.2s ease-in-out
-
-  &:hover
-    background-color: $gray-100
-    transform: translateY(-1px)
-
-@media (max-width: 768px)
-  .home-hero
-    padding: 2rem 0
-
-  .home-hero__title
-    font-size: 2rem
-
-  .home-hero__subtitle
-    font-size: 1.125rem
-
-  .home-features,
-  .home-categories
-    padding: 2rem 0
-
-  .home-features__title,
-  .home-categories__title
-    font-size: 1.875rem
-
-  .home-cta
-    padding: 2rem 1rem
-    margin: 2rem 0
-
-  .home-cta__title
-    font-size: 1.875rem
-
-  .home-cta__description
-    font-size: 1rem
+// Адаптивность
+@media (max-width: 480px)
+  .home-page
+    padding: 0 var(--spacing-sm)
+  
+  .hero-section
+    padding: var(--spacing-xl) var(--spacing-md)
+  
+  .feature-card
+    padding: var(--spacing-lg)
+  
+  .hero-actions
+    flex-direction: column
+    align-items: center
+    
+    .btn
+      width: 100%
+      max-width: 280px

+ 29 - 0
app/pages/NotFound/index.coffee

@@ -0,0 +1,29 @@
+# app/pages/NotFound/index.coffee
+
+# Добавление стилей страницы
+if globalThis.stylFns and globalThis.stylFns['app/pages/NotFound/index.styl']
+  styleElement = document.createElement('style')
+  styleElement.type = 'text/css'
+  styleElement.textContent = globalThis.stylFns['app/pages/NotFound/index.styl']
+  document.head.appendChild(styleElement)
+else
+  log '⚠️ Стили страницы 404 не найдены'
+
+module.exports = {
+  props:
+    domainSettings:
+      type: Object
+      default: -> {}
+    language:
+      type: String
+      default: 'ru'
+
+  methods:
+    goHome: ->
+      @$router.push '/'
+
+    goBack: ->
+      @$router.back()
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/pages/NotFound/index.pug'])()
+}

+ 25 - 0
app/pages/NotFound/index.pug

@@ -0,0 +1,25 @@
+div(class="not-found-page")
+  div(class="error-container")
+    div(class="error-illustration")
+      | <!-- SVG иконка для 404 -->
+      svg(viewBox="0 0 200 150" xmlns="http://www.w3.org/2000/svg")
+        path(d="M80 30C80 35.52 75.52 40 70 40C64.48 40 60 35.52 60 30C60 24.48 64.48 20 70 20C75.52 20 80 24.48 80 30Z")
+        path(d="M140 30C140 35.52 135.52 40 130 40C124.48 40 120 35.52 120 30C120 24.48 124.48 20 130 20C135.52 20 140 24.48 140 30Z")
+        path(d="M50 70C50 75.52 45.52 80 40 80C34.48 80 30 75.52 30 70C30 64.48 34.48 60 40 60C45.52 60 50 64.48 50 70Z")
+        path(d="M170 70C170 75.52 165.52 80 160 80C154.48 80 150 75.52 150 70C150 64.48 154.48 60 160 60C165.52 60 170 64.48 170 70Z")
+    
+    h1(class="error-code") 404
+    h2(class="error-title") Страница не найдена
+    p(class="error-description") Извините, запрашиваемая страница не существует или была перемещена. Проверьте URL или вернитесь на предыдущую страницу.
+    
+    div(class="error-actions")
+      ui-button(
+        type="primary"
+        size="large"
+        @click="goHome"
+      ) Вернуться на главную
+      ui-button(
+        type="outline"
+        size="large"
+        @click="goBack"
+      ) Назад

+ 76 - 0
app/pages/NotFound/index.styl

@@ -0,0 +1,76 @@
+// Стили для страницы 404
+.not-found-page
+  display: flex
+  align-items: center
+  justify-content: center
+  min-height: 60vh
+  padding: var(--spacing-2xl) var(--spacing-md)
+
+.error-container
+  text-align: center
+  max-width: 500px
+  margin: 0 auto
+
+.error-code
+  font-size: 8rem
+  font-weight: var(--font-weight-bold)
+  color: var(--color-primary)
+  margin-bottom: var(--spacing-sm)
+  line-height: 1
+  opacity: 0.8
+  
+  @media (max-width: 768px)
+    font-size: 6rem
+
+.error-title
+  font-size: var(--font-size-2xl)
+  margin-bottom: var(--spacing-md)
+  color: var(--color-dark)
+  font-weight: var(--font-weight-semibold)
+  
+  .theme-dark &
+    color: var(--color-light)
+
+.error-description
+  color: var(--color-secondary)
+  margin-bottom: var(--spacing-xl)
+  font-size: var(--font-size-lg)
+  line-height: var(--line-height-relaxed)
+
+.error-actions
+  display: flex
+  gap: var(--spacing-md)
+  justify-content: center
+  flex-wrap: wrap
+
+.error-illustration
+  margin-bottom: var(--spacing-xl)
+  opacity: 0.7
+  
+  svg
+    width: 200px
+    height: 150px
+    fill: var(--color-primary-50)
+
+// Адаптивность
+@media (max-width: 480px)
+  .not-found-page
+    min-height: 50vh
+    padding: var(--spacing-xl) var(--spacing-sm)
+  
+  .error-code
+    font-size: 4rem
+  
+  .error-title
+    font-size: var(--font-size-xl)
+  
+  .error-description
+    font-size: var(--font-size-base)
+  
+  .error-actions
+    flex-direction: column
+    align-items: center
+    
+    .btn
+      width: 100%
+      max-width: 280px

+ 19 - 0
app/pages/Product/index.coffee

@@ -0,0 +1,19 @@
+# app/pages/Product/index.coffee
+
+
+module.exports = {
+  props:
+    domainSettings:
+      type: Object
+      default: -> {}
+    language:
+      type: String
+      default: 'ru'
+
+  data: ->
+    {
+      pageTitle: 'Страница товара'
+    }
+
+  render: (new Function '_ctx', '_cache', globalThis.renderFns['app/pages/Product/index.pug'])()
+}

+ 4 - 0
app/pages/Product/index.pug

@@ -0,0 +1,4 @@
+div(class="product-page")
+  h1 {{ pageTitle }}
+  p Страница товара в разработке
+  ui-button(@click="$router.push('/catalog')") В каталог

+ 77 - 0
app/router/index.coffee

@@ -0,0 +1,77 @@
+# app/router/index.coffee
+config = require 'app/config'
+
+
+# Middleware для проверки прав доступа
+authGuard = (to, from, next) ->
+  log 'Проверка прав доступа для route:', to.path
+  # Здесь будет логика проверки пользователя из глобального состояния
+  if to.matched.some (record) -> record.meta.requiresAuth
+    # Проверка авторизации
+    next() # Временная заглушка - всегда разрешаем доступ
+  else
+    next()
+
+domainMiddleware = (to, from, next) ->
+  log 'Обработка динамического домена для route'
+  # Логика обработки домена будет интегрирована позже
+  next()
+
+router = VueRouter.createRouter({
+  history: VueRouter.createWebHistory()
+  routes: [
+    {
+      path: '/'
+      name: 'Home'
+      component: require 'app/pages/Home/index.coffee'
+      beforeEnter: [domainMiddleware]
+    }
+    {
+      path: '/catalog'
+      name: 'Catalog'
+      component: require 'app/pages/Catalog/index.coffee'
+      beforeEnter: [domainMiddleware]
+    }
+    {
+      path: '/catalog/:category?'
+      name: 'CatalogCategory'
+      component: require 'app/pages/Catalog/index.coffee'
+      beforeEnter: [domainMiddleware]
+    }
+    {
+      path: '/product/:id'
+      name: 'Product'
+      component: require 'app/pages/Product/index.coffee'
+      beforeEnter: [domainMiddleware]
+    }
+    {
+      path: '/cart'
+      name: 'Cart'
+      component: require 'app/pages/Cart/index.coffee'
+      beforeEnter: [domainMiddleware]
+    }
+    {
+      path: '/admin'
+      name: 'Admin'
+      component: require 'app/pages/Admin/index.coffee'
+      meta: { requiresAuth: true }
+      beforeEnter: [domainMiddleware, authGuard]
+    }
+    {
+      path: '/:pathMatch(.*)*'
+      name: 'NotFound'
+      component: require 'app/pages/NotFound/index.coffee'
+      beforeEnter: [domainMiddleware]
+    }
+  ]
+})
+
+# Глобальные обработчики роутера
+router.beforeEach (to, from, next) ->
+  #log "Переход с "+ from.fullPath +" на "+to.fullPath+""
+  next()
+
+router.afterEach (to, from) ->
+  #log "Навигация завершена на "+to.fullPath+""
+
+module.exports = router

+ 0 - 0
app/shared/ImageSlider/index.coffee


+ 0 - 0
app/shared/ImageSlider/index.pug


+ 0 - 0
app/shared/ImageSlider/index.styl


+ 0 - 0
app/shared/ModalWindow/index.coffee


+ 0 - 0
app/shared/ModalWindow/index.pug


+ 0 - 0
app/shared/ModalWindow/index.styl


+ 0 - 0
app/shared/MultiLevelMenu/index.coffee


+ 41 - 0
app/shared/MultiLevelMenu/index.pug

@@ -0,0 +1,41 @@
+// app/shared/MultiLevelMenu/index.pug
+nav(class="relative")
+  button(
+    @click="toggleMenu"
+    class="flex items-center space-x-1 text-gray-700 dark:text-gray-200 hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
+  )
+    span Каталог
+    svg(:class="{'transform rotate-180': isOpen}" class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24")
+      path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7")
+  
+  transition(name="menu-slide")
+    div(
+      v-if="isOpen"
+      class="absolute top-full left-0 mt-2 w-64 bg-white dark:bg-gray-800 rounded-lg shadow-xl z-50 py-2"
+      v-click-outside="closeMenu"
+    )
+      .px-4.py-2.border-b.border-gray-200.dark_border-gray-700
+        h3(class="font-semibold text-gray-900 dark:text-white") Категории товаров
+      
+      ul(class="max-h-96 overflow-y-auto")
+        li(v-for="category in categories" :key="category._id")
+          .relative
+            button(
+              @click="toggleSubmenu(category._id)"
+              class="w-full flex justify-between items-center px-4 py-2 text-left hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
+            )
+              span {{ category.name }}
+              svg(v-if="category.children && category.children.length" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24")
+                path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7")
+            
+            transition(name="submenu-slide")
+              div(
+                v-if="openSubmenu === category._id"
+                class="absolute left-full top-0 ml-1 w-64 bg-white dark:bg-gray-800 rounded-lg shadow-xl z-50 py-2"
+              )
+                ul
+                  li(v-for="child in getChildCategories(category._id)" :key="child._id")
+                    a(
+                      :href="`/catalog?category=${child.slug}`"
+                      class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
+                    ) {{ child.name }}

+ 0 - 0
app/shared/MultiLevelMenu/index.styl


+ 0 - 0
app/shared/ThemeToggle/index.coffee


+ 22 - 0
app/shared/ThemeToggle/index.pug

@@ -0,0 +1,22 @@
+// app/shared/ThemeToggle/index.pug
+button(
+  @click="toggleTheme"
+  class="p-2 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-white hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-primary-500"
+  aria-label="Переключить тему"
+)
+  svg(
+    v-if="theme === 'light'"
+    class="w-5 h-5"
+    fill="none"
+    stroke="currentColor"
+    viewBox="0 0 24 24"
+  )
+    path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z")
+  svg(
+    v-else
+    class="w-5 h-5"
+    fill="none"
+    stroke="currentColor"
+    viewBox="0 0 24 24"
+  )
+    path(stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z")

+ 0 - 0
app/shared/ThemeToggle/index.styl


+ 124 - 0
app/types/data.coffee

@@ -0,0 +1,124 @@
+# Базовый класс для всех сущностей
+class DomainEntity
+  constructor: ->
+    @_id = ''
+    @_rev = ''
+    @type = ''
+    @domains = []
+    @createdAt = new Date().toISOString()
+    @updatedAt = new Date().toISOString()
+    @active = true
+    @createdBy = ''
+    @updatedBy = ''
+
+# Товар
+class Product extends DomainEntity
+  constructor: ->
+    super()
+    @type = 'product'
+    @name = ''
+    @sku = ''
+    @price = 0
+    @oldPrice = null
+    @category = ''
+    @brand = ''
+    @description = ''
+    @shortDescription = ''
+    @attributes = {}
+    @images = []
+    @richContent = null
+    @inStock = true
+    @stockQuantity = 0
+    @weight = 0
+    @volume = 0
+    @dimensions = {}
+    @seo = {}
+    @tags = []
+    @relatedProducts = []
+
+# Категория
+class Category extends DomainEntity
+  constructor: ->
+    super()
+    @type = 'category'
+    @name = ''
+    @slug = ''
+    @parent = null
+    @order = 0
+    @image = ''
+    @description = ''
+    @seo = {}
+    @attributesConfig = {} # Конфигурация атрибутов для категории
+
+# Слайд главной страницы
+class HeroSlide extends DomainEntity
+  constructor: ->
+    super()
+    @type = 'hero_slide'
+    @title = ''
+    @subtitle = ''
+    @image = ''
+    @buttonText = 'В каталог'
+    @buttonLink = '/catalog'
+    @order = 0
+    @active = true
+
+# Настройки домена
+class DomainSettings extends DomainEntity
+  constructor: ->
+    super()
+    @type = 'domain_settings'
+    @domain = ''
+    @companyName = ''
+    @companyLogo = ''
+    @languages = ['ru']
+    @defaultLanguage = 'ru'
+    @theme = 'light'
+    @contacts = {}
+    @seo = {}
+    @social = {}
+    @paymentMethods = []
+    @shippingMethods = []
+
+# Пользователь
+class User extends DomainEntity
+  constructor: ->
+    super()
+    @type = 'user'
+    @username = ''
+    @email = ''
+    @role = 'user' # user, admin, manager
+    @firstName = ''
+    @lastName = ''
+    @phone = ''
+    @address = {}
+    @orders = []
+    @favorites = []
+    @cart = []
+
+# Заказ
+class Order extends DomainEntity
+  constructor: ->
+    super()
+    @type = 'order'
+    @userId = ''
+    @items = []
+    @total = 0
+    @status = 'pending' # pending, confirmed, shipped, delivered, cancelled
+    @paymentStatus = 'pending'
+    @shippingAddress = {}
+    @billingAddress = {}
+    @paymentMethod = ''
+    @shippingMethod = ''
+    @trackingNumber = ''
+    @notes = ''
+
+module.exports = {
+  DomainEntity,
+  Product, 
+  Category,
+  HeroSlide,
+  DomainSettings,
+  User,
+  Order
+}

+ 46 - 0
app/types/events.coffee

@@ -0,0 +1,46 @@
+# Типы событий приложения
+class EventTypes
+  constructor: ->
+    # События темы
+    @THEME_CHANGED = 'theme-changed'
+    @THEME_LOADED = 'theme-loaded'
+    
+    # События языка
+    @LANGUAGE_CHANGED = 'language-changed'
+    @LANGUAGE_LOADED = 'language-loaded'
+    
+    # События пользователя
+    @USER_LOGIN = 'user-login'
+    @USER_LOGOUT = 'user-logout'
+    @USER_UPDATED = 'user-updated'
+    
+    # События корзины
+    @CART_ADD = 'cart-add'
+    @CART_REMOVE = 'cart-remove'
+    @CART_UPDATE = 'cart-update'
+    @CART_CLEAR = 'cart-clear'
+    
+    # События товаров
+    @PRODUCT_VIEW = 'product-view'
+    @PRODUCT_SEARCH = 'product-search'
+    @PRODUCT_FILTER = 'product-filter'
+    
+    # События заказов
+    @ORDER_CREATE = 'order-create'
+    @ORDER_UPDATE = 'order-update'
+    @ORDER_STATUS_CHANGE = 'order-status-change'
+    
+    # События UI
+    @MODAL_OPEN = 'modal-open'
+    @MODAL_CLOSE = 'modal-close'
+    @NOTIFICATION_SHOW = 'notification-show'
+    @NOTIFICATION_HIDE = 'notification-hide'
+    
+    # События данных
+    @DATA_LOADED = 'data-loaded'
+    @DATA_ERROR = 'data-error'
+    @SYNC_START = 'sync-start'
+    @SYNC_COMPLETE = 'sync-complete'
+    @SYNC_ERROR = 'sync-error'
+
+module.exports = new EventTypes()

+ 28 - 217
app/utils/pouch.coffee

@@ -1,233 +1,44 @@
+# app/utils/pouch.coffee
 class PouchDBService
   constructor: (options = {}) ->
-    {
-      @localDbName = 'braer_color_cache'
-      @remoteDbUrl = 'http://localhost:5984/braer_color_shop' 
-      @userFilter = { userId: null }
-      @appVersion = '1.0.0'
-    } = options
-    
+    {@localDbName, @remoteDbUrl, @appVersion} = options
     @localDb = null
     @remoteDb = null
-    @initialized = false
     @syncHandler = null
+    @initialized = false
 
-  # Основная инициализация
   init: ->
     return Promise.resolve() if @initialized
     
     try
-      debug.log '🚀 Инициализация PouchDB сервиса...'
-      
-      # Создание локальной базы
-      @localDb = new PouchDB(@localDbName)
-      debug.log '📁 Локальная база создана:', @localDbName
-      
-      # Создание/подключение удаленной базы
-      await @ensureRemoteDatabase()
-      
-      # Загрузка design документов
-      await @ensureDesignDocs()
-      
-      # Настройка синхронизации
-      await @setupSync()
+      # Инициализация локальной базы
+      @localDb = new PouchDB(@localDbName or 'braer_color_cache')
+      log 'Локальная PouchDB инициализирована'
+      
+      # Инициализация удаленной базы
+      @remoteDb = new PouchDB(@remoteDbUrl, {
+        skip_setup: false
+        fetch: (url, opts) ->
+          # Добавление обработки CORS и аутентификации
+          opts.credentials = 'include'
+          PouchDB.fetch(url, opts)
+      })
+      
+      # Настройка непрерывной синхронизации:cite[7]
+      @syncHandler = PouchDB.sync(@localDb, @remoteDb, {
+        live: true,
+        retry: true,
+        filter: (doc) => @shouldSyncDocument(doc)
+      })
+      .on 'change', (info) =>
+        log 'Синхронизация: данные изменены', info
+      .on 'error', (err) =>
+        log 'Ошибка синхронизации:', err
       
       @initialized = true
-      debug.log '✅ PouchDB сервис инициализирован'
+      log 'PouchDB сервис полностью инициализирован'
       return Promise.resolve()
       
     catch error
-      console.error '❌ Ошибка инициализации PouchDB:', error
+      log 'Критическая ошибка инициализации PouchDB:', error
       return Promise.reject(error)
-
-  # Создание удаленной базы если не существует
-  ensureRemoteDatabase: ->
-    try
-      @remoteDb = new PouchDB(@remoteDbUrl)
-      
-      # Проверка существования базы
-      info = await @remoteDb.info()
-      debug.log '🌐 Удаленная база подключена:', info.db_name
-      
-    catch error
-      if error.status == 404
-        debug.log '📦 Создание новой удаленной базы...'
-        # В браузере создание БД происходит автоматически при первом обращении
-        @remoteDb = new PouchDB(@remoteDbUrl)
-        info = await @remoteDb.info()
-        debug.log '✅ Удаленная база создана:', info.db_name
-      else
-        throw error
-
-  # Создание design документов
-  ensureDesignDocs: ->
-    try
-      # Загрузка design документов
-      adminDesign = require 'app/design/admin.coffee'
-      siteDesign = require 'app/design/site.coffee'
-      
-      # Сохранение design документов
-      await @saveDesignDoc(adminDesign)
-      await @saveDesignDoc(siteDesign)
-      
-      debug.log '📝 Design документы загружены'
-      
-    catch error
-      console.error 'Ошибка загрузки design документов:', error
-      throw error
-
-  # Сохранение design документа с проверкой версий
-  saveDesignDoc: (designDoc) ->
-    try
-      existingDoc = await @remoteDb.get(designDoc._id)
-      
-      # Проверка необходимости обновления
-      if existingDoc.hash != designDoc.hash || existingDoc.version != designDoc.version
-        designDoc._rev = existingDoc._rev
-        await @remoteDb.put(designDoc)
-        debug.log "🔄 Design документ обновлен: #{designDoc._id}"
-      else
-        debug.log "✅ Design документ актуален: #{designDoc._id}"
-        
-    catch error
-      if error.status == 404
-        # Документ не существует, создаем новый
-        await @remoteDb.put(designDoc)
-        debug.log "✅ Design документ создан: #{designDoc._id}"
-      else
-        throw error
-
-  # Настройка синхронизации
-  setupSync: ->
-    @syncHandler = PouchDB.sync(@remoteDb, @localDb, {
-      live: true,
-      retry: true,
-      filter: (doc) => @shouldSyncDocument(doc)
-    })
-    
-    @syncHandler
-      .on('change', (info) =>
-        debug.log '🔄 Синхронизация:', info)
-      .on('paused', (err) =>
-        debug.log '⏸️ Синхронизация приостановлена')
-      .on('active', =>
-        debug.log '🔄 Синхронизация активна')
-      .on('error', (err) =>
-        console.error '❌ Ошибка синхронизации:', err)
-      
-    debug.log '🔁 Синхронизация настроена'
-
-  # Проверка необходимости синхронизации документа
-  shouldSyncDocument: (doc) ->
-    return false if doc._id?.startsWith('_design/')
-    
-    # Всегда синхронизируем общие данные
-    if doc.type in ['product', 'category', 'settings', 'hero_slide', 'blog_article', 'route', 'domain_settings']
-      return true
-    
-    # Для пользовательских данных проверяем принадлежность
-    if doc.type in ['order', 'user_data', 'cart']
-      return doc.userId == @userFilter?.userId
-    
-    # Для мультидоменности проверяем принадлежность к домену
-    if doc.domains
-      return @userFilter?.currentDomain in doc.domains
-    
-    return false
-
-  # Умное получение документа
-  getDocument: (docId) ->
-    @ensureInit()
-    
-    try
-      # Сначала пробуем локально
-      doc = await @localDb.get(docId)
-      debug.log '📄 Документ получен из локального кэша:', docId
-      return doc
-      
-    catch localError
-      if localError.status == 404
-        try
-          # Затем пробуем удаленно
-          doc = await @remoteDb.get(docId)
-          # Сохраняем в кэш для будущих запросов
-          await @localDb.put(doc)
-          debug.log '📄 Документ получен из удаленной БД и закэширован:', docId
-          return doc
-        catch remoteError
-          console.error '❌ Документ не найден:', docId
-          throw remoteError
-      else
-        throw localError
-
-  # Сохранение документа (только в удаленную БД)
-  saveToRemote: (doc) ->
-    @ensureInit()
-    
-    try
-      # Проверяем существование документа
-      existingDoc = await @remoteDb.get(doc._id)
-      doc._rev = existingDoc._rev
-      result = await @remoteDb.put(doc)
-      debug.log '💾 Документ обновлен в удаленной БД:', doc._id
-      return result
-      
-    catch error
-      if error.status == 404
-        # Документ не существует, создаем новый
-        result = await @remoteDb.put(doc)
-        debug.log '💾 Документ создан в удаленной БД:', doc._id
-        return result
-      else
-        console.error '❌ Ошибка сохранения документа:', error
-        throw error
-
-  # Пакетное сохранение
-  bulkDocs: (docs) ->
-    @ensureInit()
-    await @remoteDb.bulkDocs(docs)
-
-  # Выполнение view запроса
-  queryView: (designDoc, viewName, options = {}) ->
-    @ensureInit()
-    await @remoteDb.query("#{designDoc}/#{viewName}", options)
-
-  # Получение вложений
-  getAttachment: (docId, attachmentName) ->
-    @ensureInit()
-    await @remoteDb.getAttachment(docId, attachmentName)
-
-  # Сохранение вложения
-  putAttachment: (docId, attachmentName, attachment, type) ->
-    @ensureInit()
-    
-    try
-      doc = await @remoteDb.get(docId)
-      await @remoteDb.putAttachment(docId, attachmentName, doc._rev, attachment, type)
-      debug.log '📎 Вложение сохранено:', "#{docId}/#{attachmentName}"
-    catch error
-      if error.status == 404
-        # Документ не существует, создаем сначала базовый документ
-        await @remoteDb.put({ _id: docId, type: 'with_attachments' })
-        await @remoteDb.putAttachment(docId, attachmentName, attachment, type)
-      else
-        throw error
-
-  # Вспомогательные методы
-  ensureInit: ->
-    if !@initialized
-      throw new Error 'PouchDBService не инициализирован. Вызовите init() сначала.'
-
-  destroy: ->
-    if @syncHandler
-      @syncHandler.cancel()
-    @initialized = false
-    debug.log '🧹 PouchDBService уничтожен'
-
-# Создание и экспорт синглтона
-module.exports = new PouchDBService({
-  localDbName: 'braer_color_cache'
-  remoteDbUrl: 'https://oleg:631074@couchdb.favt.ru.net/braer_color_shop'
-  userFilter: { userId: 'current_user_id' }
-  appVersion: '1.0.0'
-})