Browse Source

add first file

Gogs 1 month ago
parent
commit
31faa0f796
10 changed files with 803 additions and 5 deletions
  1. 16 5
      README.md
  2. 157 0
      app/index.coffee
  3. 39 0
      app/index.pug
  4. 146 0
      app/index.styl
  5. 17 0
      app/pages/Home/index.coffee
  6. 19 0
      app/pages/Home/index.pug
  7. 61 0
      app/pages/Home/index.styl
  8. 94 0
      app/theme.config.coffee
  9. 233 0
      app/utils/pouch.coffee
  10. 21 0
      tailwind.config.js

+ 16 - 5
README.md

@@ -536,16 +536,27 @@ https://cdn1.ozone.ru/s3/multimedia-1-p/7663352533.jpg";;;ЭкоКрас;4673764
 ## 🚀 Состояние разработки
 
 ### ✅ Реализовано
-Анализировать реализованный код, по git репозитарию https://gogs.osvoj.ru/oleg/s5l.ru-crm.git
-Проверяй промт и изменения в нём по адресу https://gogs.osvoj.ru/oleg/s5l.ru-crm/raw/master/README.md
-
-### 🚧 В процессе
-  Начать разработку с полного листинга файлов
   app/index.coffee
   app/index.pug
   app/index.styl
+  app/theme.config.coffee
   app/utils/pouch.db
   app/desing/admin.coffee
   app/desing/site.coffee
+  app/pages/Home/index.coffee
+  app/pages/Home/index.pug
+  app/pages/Home/index.styl
+
 
 
+### 🚧 В процессе
+  Анализировать реализованный код, по git репозитарию https://gogs.osvoj.ru/oleg/s5l.ru-crm.git
+  Проверяй промт и изменения в нём по адресу https://gogs.osvoj.ru/oleg/s5l.ru-crm/raw/master/README.md
+  Начать разработку с полного листинга файлов
+  app/pages/Admin/index.coffee
+  app/pages/Admin/index.pug
+  app/pages/Admin/index.styl
+  app/pages/Admin/Settings/index.coffee
+  app/pages/Admin/Settings/index.pug
+  app/pages/Admin/Settings/index.styl
+

+ 157 - 0
app/index.coffee

@@ -0,0 +1,157 @@
+# Инициализация глобальных переменных
+globalThis.renderFns = require 'pug.json'
+globalThis.stylFns = require 'styl.json'
+globalThis.themeConfig = require 'app/theme.config.coffee'
+
+# Инициализация Tailwind с кастомной конфигурацией
+tailwind.config = {
+  theme: {
+    extend: {
+      colors: themeConfig.colors
+      fontFamily: themeConfig.typography.fonts
+      borderRadius: themeConfig.borderRadius
+    }
+  }
+  darkMode: 'class'
+}
+
+
+
+
+
+PouchDBService = require 'app/utils/pouch.coffee'
+
+# Инициализация сервиса данных
+pouchService = PouchDBService
+
+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>'+stylFns['main.css']+'</style>')
+document.head.insertAdjacentHTML('beforeend','<style  type="text/tailwindcss">'+stylFns['app/index.styl']+'</style>')
+
+document.head.insertAdjacentHTML('beforeend','<title> Кохи Борбад - Концертный зал Душанбе</title>')
+
+
+
+
+
+# Создание главного приложения Vue
+globalThis.app = Vue.createApp({
+  data: ->
+    return {
+      theme: localStorage.getItem('theme') || 'light'
+      companyName: 'Браер-Колор'
+      loading: false
+      cartItems: []
+      user: null
+      currentDomain: window.location.hostname
+      languages: ['ru', 'en']
+      currentLanguage: 'ru'
+    }
+  
+  render: (new Function '_ctx', '_cache', renderFns['app/index.pug'])()
+  
+  computed:
+    isAdmin: -> @user?.role == 'admin'
+  
+  methods:
+    toggleTheme: ->
+      @theme = if @theme == 'light' then 'dark' else 'light'
+      localStorage.setItem 'theme', @theme
+      document.documentElement.classList.toggle 'dark'
+    
+    setLanguage: (lang) ->
+      if @languages.includes lang
+        @currentLanguage = lang
+        localStorage.setItem 'language', lang
+    
+    showNotification: (message, type = 'success') ->
+      # Реализация системы уведомлений
+      console.dir "#{type.toUpperCase()}: #{message}"
+    loadUserData: ->
+        # Загрузка данных пользователя из localStorage или PouchDB
+        userData = localStorage.getItem 'user'
+        if userData
+          try
+            @user = JSON.parse userData
+          catch
+            @user = null
+  mounted: ->
+    # Инициализация темы
+    if @theme == 'dark'
+      document.documentElement.classList.add 'dark'
+    
+    # Инициализация PouchDB
+    try
+      await pouchService.init()
+      console.dir 'PouchDB инициализирован'
+    catch error
+      console.error 'Ошибка инициализации PouchDB:', error
+    
+    # Загрузка пользователя (если авторизован)
+    @loadUserData()
+  
+
+})
+
+# Настройка маршрутизатора
+router = VueRouter.createRouter({
+  history: VueRouter.createWebHistory()
+  routes: [
+    { path: '/', component: require 'app/pages/Home' },
+    { 
+      path: '/admin',
+      component: require 'app/pages/Admin'
+      meta: { requiresAdmin: true }
+      #redirect: '/admin/settings'
+      children: [
+        #{ path: 'slider', component: require 'app/pages/Admin/Slider' }
+        #{ path: 'products', component: require 'app/pages/Admin/Products' }
+        #{ path: 'clients', component: require 'app/pages/Admin/Clients' }
+        #{ path: 'blog', component: require 'app/pages/Admin/Blog' }
+        #{ path: 'routes', component: require 'app/pages/Admin/Routes' }
+        #{ path: 'settings', component: require 'app/pages/Admin/Settings' }
+      ]
+    },
+    #{ path: '/catalog', component: require 'app/pages/Catalog' },
+    #{ path: '/blog/:slug', component: require 'app/pages/BlogArticle' },
+    #{ path: '/contacts', component: require 'app/pages/Contacts' },
+    #{ path: '/about', component: require 'app/pages/About' }
+  ]
+})
+
+# Глобальный навигационный хук
+#router.beforeEach (to, from, next) ->
+#  if to.meta.requiresAdmin
+#    userData = localStorage.getItem 'user'
+#    if userData
+#      try
+#        user = JSON.parse userData
+#        if user.role == 'admin'
+#          next()
+#        else
+#          next('/')
+#      catch
+#        next('/')
+#    else
+#      next('/')
+#  else
+#    next()
+
+app.use(router)
+
+# Глобальная обработка ошибок
+app.config.errorHandler = (err, vm, info) ->
+  console.error 'Vue Error:', err
+  console.error 'Component:', vm
+  console.error 'Info:', info
+  
+  # В продакшне отправлять ошибки на сервер
+  if process.env.NODE_ENV != 'production'
+    vm?.$root?.showNotification?('Произошла ошибка приложения', 'error')
+
+# Монтирование приложения
+app.mount('body')
+
+console.log 'Приложение Браер-Колор инициализировано'

+ 39 - 0
app/index.pug

@@ -0,0 +1,39 @@
+div(id="app")
+  header(class="header")
+    nav(class="header__nav")
+      div(class="header__nav-block")
+        div(class="header__nav-name") {{ companyName }}
+        div(class="header__nav-menu")
+          div(class="header__menu-item")
+            router-link(to="/catalog" class="header__menu-link") Каталог
+          div(class="header__menu-item")
+            router-link(to="/blog" class="header__menu-link") Блог
+          div(class="header__menu-item")
+            router-link(to="/contacts" class="header__menu-link") Контакты
+          div(class="header__menu-item")
+            button(
+              @click="toggleTheme"
+              class="header__theme-toggle"
+            ) {{ theme === 'light' ? '🌙' : '☀️' }}
+  
+  main(class="main")
+    router-view(v-slot='{ Component }')
+      transition(name='page-slide' mode='out-in')
+        component(:is='Component')
+  
+  footer(class="footer")
+    div(class="footer__content")
+      div(class="footer__sections")
+        div(class="footer__section")
+          h3(class="footer__section-title") {{ companyName }}
+          p(class="footer__section-text") Интернет-магазин лакокрасочной продукции
+        div(class="footer__section")
+          h3(class="footer__section-title") Контакты
+          p(class="footer__section-text") Email: info@braer-color.ru
+          p(class="footer__section-text") Телефон: +7 (999) 999-99-99
+        div(class="footer__section")
+          h3(class="footer__section-title") Быстрые ссылки
+          div(class="footer__links")
+            router-link(to="/catalog" class="footer__link") Каталог
+            router-link(to="/blog" class="footer__link") Блог
+            router-link(to="/about" class="footer__link") О компании

+ 146 - 0
app/index.styl

@@ -0,0 +1,146 @@
+@css {
+  /* Определение кастомных цветов для Tailwind */
+  @layer base {
+    :root {
+      --color-primary-50: #fef2f2;
+      --color-primary-100: #fee2e2;
+      --color-primary-200: #fecaca;
+      --color-primary-300: #fca5a5;
+      --color-primary-400: #f87171;
+      --color-primary-500: #ef4444;
+      --color-primary-600: #dc2626;
+      --color-primary-700: #b91c1c;
+      --color-primary-800: #991b1b;
+      --color-primary-900: #7f1d1d;
+      
+      --color-accent-50: #f0f9ff;
+      --color-accent-100: #e0f2fe;
+      --color-accent-200: #bae6fd;
+      --color-accent-300: #7dd3fc;
+      --color-accent-400: #38bdf8;
+      --color-accent-500: #0ea5e9;
+      --color-accent-600: #0284c7;
+      --color-accent-700: #0369a1;
+      --color-accent-800: #075985;
+      --color-accent-900: #0c4a6e;
+    }
+  }
+
+  /* Основные стили приложения */
+  #app {
+    @apply min-h-screen bg-white dark:bg-gray-900 transition-colors duration-300;
+  }
+  
+  /* Header */
+  .header {
+    @apply bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50;
+  }
+  
+  .header__nav {
+    @apply container mx-auto px-4 py-3;
+  }
+  
+  .header__nav-block {
+    @apply flex items-center justify-between;
+  }
+  
+  .header__nav-name {
+    @apply text-2xl font-bold bg-gradient-to-r from-primary-500 to-accent-500 bg-clip-text text-transparent;
+  }
+  
+  .header__nav-menu {
+    @apply flex items-center space-x-4;
+  }
+  
+  .header__menu-item {
+    @apply flex items-center;
+  }
+  
+  .header__menu-link {
+    @apply text-gray-600 dark:text-gray-300 hover:text-primary-500 transition-colors duration-200;
+  }
+  
+  .header__theme-toggle {
+    @apply p-2 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-200;
+  }
+  
+  /* Main content */
+  .main {
+    @apply flex-1;
+  }
+  
+  /* Page transitions */
+  .page-slide-enter-active,
+  .page-slide-leave-active {
+    @apply transition-all duration-300 ease-in-out;
+  }
+  
+  .page-slide-enter-from {
+    @apply opacity-0 transform translate-x-4;
+  }
+  
+  .page-slide-leave-to {
+    @apply opacity-0 transform -translate-x-4;
+  }
+  
+  /* Footer */
+  .footer {
+    @apply bg-gray-800 dark:bg-gray-900 text-white py-8;
+  }
+  
+  .footer__content {
+    @apply container mx-auto px-4;
+  }
+  
+  .footer__sections {
+    @apply grid grid-cols-1 md:grid-cols-3 gap-8;
+  }
+  
+  .footer__section {
+    @apply flex flex-col;
+  }
+  
+  .footer__section-title {
+    @apply text-lg font-bold mb-4;
+  }
+  
+  .footer__section-text {
+    @apply text-gray-400 mb-2;
+  }
+  
+  .footer__links {
+    @apply flex flex-col space-y-2;
+  }
+  
+  .footer__link {
+    @apply text-gray-400 hover:text-white transition-colors duration-200;
+  }
+  
+  /* Утилитарные классы */
+  .btn-primary {
+    @apply bg-primary-500 hover:bg-primary-600 text-white px-4 py-2 rounded-lg transition-colors duration-200 font-medium;
+  }
+  
+  .btn-secondary {
+    @apply bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-white px-4 py-2 rounded-lg transition-colors duration-200 font-medium;
+  }
+  
+  .card {
+    @apply bg-white dark:bg-gray-800 rounded-lg shadow-md p-6;
+  }
+  
+  .form-input {
+    @apply w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent;
+  }
+  
+  .form-label {
+    @apply block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2;
+  }
+}
+
+/* Темная тема */
+@media (prefers-color-scheme: dark) {
+  :root {
+    color-scheme: dark;
+  }
+}

+ 17 - 0
app/pages/Home/index.coffee

@@ -0,0 +1,17 @@
+document.head.insertAdjacentHTML('beforeend','<style type="text/tailwindcss">'+stylFns['app/pages/Home/index.styl']+'</style>')
+
+module.exports =
+  name: 'HomePage'
+  render: (new Function '_ctx', '_cache', renderFns['app/pages/Home/index.pug'])()
+  
+  data: ->
+    return {
+      loading: false
+    }
+  
+  mounted: ->
+    console.log 'Главная страница загружена'
+  
+  methods:
+    navigateToCatalog: ->
+      @$router.push('/catalog')

+ 19 - 0
app/pages/Home/index.pug

@@ -0,0 +1,19 @@
+div(class="home-page")
+  div(class="home-page__hero")
+    h1(class="home-page__title") Добро пожаловать в Браер-Колор
+    p(class="home-page__subtitle") Интернет-магазин лакокрасочной продукции
+    div(class="home-page__actions")
+      router-link(to="/catalog" class="home-page__btn home-page__btn--primary") В каталог
+      router-link(to="/about" class="home-page__btn home-page__btn--secondary") О компании
+  
+  div(class="home-page__content")
+    div(class="home-page__section")
+      h2(class="home-page__section-title") Популярные категории
+      div(class="home-page__categories")
+        div(class="home-page__category") Краски
+        div(class="home-page__category") Грунтовки
+        div(class="home-page__category") Лаки
+        div(class="home-page__category") Эмали
+  
+  div(class="home-page__info")
+    p(class="home-page__info-text") Мы работаем над наполнением сайта. Скоро здесь появится больше информации!

+ 61 - 0
app/pages/Home/index.styl

@@ -0,0 +1,61 @@
+@css {
+  .home-page {
+    @apply min-h-screen bg-gray-50 dark:bg-gray-800 py-8;
+  }
+
+  .home-page__hero {
+    @apply text-center py-16 px-4 bg-white dark:bg-gray-900 shadow-sm mb-8;
+  }
+
+  .home-page__title {
+    @apply text-4xl md:text-5xl font-bold text-gray-800 dark:text-white mb-4;
+  }
+
+  .home-page__subtitle {
+    @apply text-xl text-gray-600 dark:text-gray-300 mb-8;
+  }
+
+  .home-page__actions {
+    @apply flex flex-col sm:flex-row gap-4 justify-center items-center;
+  }
+
+  .home-page__btn {
+    @apply px-6 py-3 rounded-lg font-medium transition-colors duration-200;
+  }
+
+  .home-page__btn--primary {
+    @apply bg-primary-500 hover:bg-primary-600 text-white;
+  }
+
+  .home-page__btn--secondary {
+    @apply bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-800 dark:text-white;
+  }
+
+  .home-page__content {
+    @apply container mx-auto px-4;
+  }
+
+  .home-page__section {
+    @apply mb-8;
+  }
+
+  .home-page__section-title {
+    @apply text-2xl font-bold text-gray-800 dark:text-white mb-6 text-center;
+  }
+
+  .home-page__categories {
+    @apply grid grid-cols-2 md:grid-cols-4 gap-4;
+  }
+
+  .home-page__category {
+    @apply bg-white dark:bg-gray-700 rounded-lg shadow-md p-6 text-center text-gray-700 dark:text-gray-300 font-medium hover:shadow-lg transition-shadow duration-200;
+  }
+
+  .home-page__info {
+    @apply text-center py-8 px-4 bg-primary-50 dark:bg-primary-900 mt-8;
+  }
+
+  .home-page__info-text {
+    @apply text-primary-700 dark:text-primary-300 text-lg;
+  }
+}

+ 94 - 0
app/theme.config.coffee

@@ -0,0 +1,94 @@
+module.exports = {
+  version: '1.0.0',
+  
+  colors: {
+    primary: {
+      50: '#fef2f2', 100: '#fee2e2', 200: '#fecaca', 300: '#fca5a5',
+      400: '#f87171', 500: '#ef4444', 600: '#dc2626', 700: '#b91c1c',
+      800: '#991b1b', 900: '#7f1d1d'
+    },
+    accent: {
+      50: '#f0f9ff', 100: '#e0f2fe', 200: '#bae6fd', 300: '#7dd3fc',
+      400: '#38bdf8', 500: '#0ea5e9', 600: '#0284c7', 700: '#0369a1',
+      800: '#075985', 900: '#0c4a6e'
+    },
+    gray: {
+      50: '#f9fafb', 100: '#f3f4f6', 200: '#e5e7eb', 300: '#d1d5db',
+      400: '#9ca3af', 500: '#6b7280', 600: '#4b5563', 700: '#374151',
+      800: '#1f2937', 900: '#111827'
+    }
+  },
+  
+  
+  
+  
+  typography: {
+    fonts: {
+      sans: ['Inter', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans', 'sans-serif'],
+      serif: ['ui-serif', 'Georgia', 'Cambria', 'Times New Roman', 'Times', 'serif'],
+      mono: ['ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', 'monospace']
+    },
+    sizes: {
+      xs: '0.75rem', 
+      sm: '0.875rem', 
+      base: '1rem', 
+      lg: '1.125rem',
+      xl: '1.25rem', 
+      '2xl': '1.5rem', 
+      '3xl': '1.875rem',
+      '4xl': '2.25rem',
+      '5xl': '3rem'
+    },
+    weights: {
+      light: 300,
+      normal: 400,
+      medium: 500,
+      semibold: 600,
+      bold: 700
+    }
+  },
+  
+  breakpoints: {
+    sm: '640px',
+    md: '768px', 
+    lg: '1024px',
+    xl: '1280px',
+    '2xl': '1536px'
+  },
+  
+  spacing: {
+    0: '0px',
+    1: '0.25rem',
+    2: '0.5rem',
+    3: '0.75rem', 
+    4: '1rem',
+    5: '1.25rem',
+    6: '1.5rem',
+    8: '2rem',
+    10: '2.5rem',
+    12: '3rem',
+    16: '4rem',
+    20: '5rem',
+    24: '6rem',
+    32: '8rem'
+  },
+  
+  borderRadius: {
+    none: '0px',
+    sm: '0.125rem',
+    default: '0.25rem',
+    md: '0.375rem',
+    lg: '0.5rem',
+    xl: '0.75rem',
+    '2xl': '1rem',
+    full: '9999px'
+  },
+  
+  shadows: {
+    sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
+    default: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
+    md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
+    lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
+    xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)'
+  }
+}

+ 233 - 0
app/utils/pouch.coffee

@@ -0,0 +1,233 @@
+class PouchDBService
+  constructor: (options = {}) ->
+    {
+      @localDbName = 'braer_color_cache'
+      @remoteDbUrl = 'http://localhost:5984/braer_color_shop' 
+      @userFilter = { userId: null }
+      @appVersion = '1.0.0'
+    } = options
+    
+    @localDb = null
+    @remoteDb = null
+    @initialized = false
+    @syncHandler = null
+
+  # Основная инициализация
+  init: ->
+    return Promise.resolve() if @initialized
+    
+    try
+      debug.log '🚀 Инициализация PouchDB сервиса...'
+      
+      # Создание локальной базы
+      @localDb = new PouchDB(@localDbName)
+      debug.log '📁 Локальная база создана:', @localDbName
+      
+      # Создание/подключение удаленной базы
+      await @ensureRemoteDatabase()
+      
+      # Загрузка design документов
+      await @ensureDesignDocs()
+      
+      # Настройка синхронизации
+      await @setupSync()
+      
+      @initialized = true
+      debug.log '✅ PouchDB сервис инициализирован'
+      return Promise.resolve()
+      
+    catch error
+      console.error '❌ Ошибка инициализации 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'
+})

+ 21 - 0
tailwind.config.js

@@ -0,0 +1,21 @@
+const themeConfig = require('./app/theme.config.coffee');
+
+module.exports = {
+  content: [
+    "./app/**/*.{pug,coffee,styl}",
+    "./src/**/*.{html,js,vue}"
+  ],
+  darkMode: 'class',
+  theme: {
+    extend: {
+      colors: themeConfig.colors,
+      fontFamily: themeConfig.typography.fonts,
+      fontSize: themeConfig.typography.sizes,
+      fontWeight: themeConfig.typography.weights,
+      spacing: themeConfig.spacing,
+      borderRadius: themeConfig.borderRadius,
+      boxShadow: themeConfig.shadows
+    },
+  },
+  plugins: [],
+}