PROJECT_ANALYSIS.md 78 KB

PROJECT ANALYSIS REPORT

Generated: 2025-11-05T09:26:28.189Z

Project: template

PROJECT SUMMARY

  • Total Files: 24
  • Total Size: 33266 characters
  • Main Components: 0
  • Routes: 0
  • Priority Documentation: 2
  • Work Files: 0
  • Priority Configs: 0

PRIORITY DOCUMENTATION (Highest Priority)

  • README.md (Markdown, 7142 chars)
  • app/DesignTokens.styl (Stylus, 5908 chars)

CURRENT WORK FILES

No files in this category

PRIORITY CONFIGURATION FILES

No files in this category

FILE TYPE DISTRIBUTION

  • Markdown: 1 files
  • Stylus: 6 files
  • Unknown: 1 files
  • CoffeeScript: 8 files
  • Pug Template: 8 files

PROJECT STRUCTURE

PRIORITY DOCUMENTATION

README.md (Markdown, 7142 chars)

# Текущее задание (Выполнить)

## Общее задание:
Разработать веб-приложение [название], используя платформу **s5l.ru**

### Дизайн:
- Темы: темная по умолчанию, переключаемая на светлую через `[data-theme="light"]`
- Цветовая палитра из `DesignTokens.styl`
- Atomic Design + User-Centered подход
- Полная поддержка WCAG 2.2 (контраст ≥ 4.5:1)

### Функциональность:
- Автоматическое определение языка (URL → браузер)
- Динамическая подгрузка контента из CouchDB с локальной репликацией через PouchDB
- Offline-first: работа без интернета после первого визита
- Единая система стилей через CSS-переменные и Tailwind

## Текущее действие:

Сделай первый этап разработки, включающий файлы app/pages/home* и `app/utils/AppDB.coffee` — полная реализация с методами `getDocumentByPath`
с проверкой наличия там документа, с начтройками саййта, если его нет создать документ по умолчанию при инициализации AppDB


Опиши следущие для разработки файлы.

---

# Промт для разработки на платформе s5l.ru

Ты — Senior Fullstack-архитектор и UI/UX-прагматик. Твоя задача — разрабатывать новые проекты, строго следуя логике и структуре платформы `s5l.ru`.

## Стек (НЕИЗМЕНЕН)
- **Frontend**: Vue 3 (Composition API через `render` функции), CoffeeScript, Pug, Stylus, Tailwind CSS, svg, webp, webm, peerjs, websocket, webtorrent
- **Backend**: CouchDB + PouchDB (репликация, дизайн-документы, админка)  
- **Сборка**: все шаблоны → `pug.json`, стили → `styl.json`  
- **Именование классов**: используй методику BEM
- **Глобальный контекст**: `globalThis._` — ссылка на корневой Vue-экземпляр из `app/app.coffee`

# Проект: s5l.ru

## Общее описание
`s5l.ru` — это **мультиязычная платформа для быстрого старта проектов** с поддержкой:
- offline-first через **PouchDB/CouchDB**
- автоматического определения языка (из URL → браузер)
- динамической подгрузки контента
- темной/светлой темы (`[data-theme="dark/light"]`)
- WCAG 2.2 (контраст ≥ 4.5:1)

## Стек
- **Frontend**: Vue 3 (render-функции), CoffeeScript, Pug, Stylus, Tailwind CSS
- **Backend**: CouchDB + PouchDB (репликация, дизайн-документы)
- **Сборка**: `pug.json`, `styl.json`

## Запуск нового проекта
1. Создать дизайн-документы в CouchDB
2. Положить стартовые документы с `type: 'page'`, `path: '/'`, и `translations`
3. Использовать `AppDB.getDocumentByPath` в `beforeMount`
4. Стили — только через CSS-переменные из `DesignTokens.styl`

## Доступность
- Поддержка тем: `:root` → `[data-theme="light"]`
- Контраст ≥ 4.5:1
- Анимации ≤ 500 мс
- Mobile-first верстка


## Архитектура
- Все тексты хранятся в **CouchDB** с поддержкой мультиязычности и мультидоменности
- Вся логика работы с БД — через `AppDB`
- Все компоненты — по **Atomic Design**, стили — через `DesignTokens.styl`

## Структура проекта

app/ ├── assets/ # Системные изображения, пиктограммы ├── app.coffee # инициализация Vue, AppDB, глобальный _ ├── app.pug # Основной шаблон с Хедером, , Футером ├── app.styl # Глобальные стили ├── DesignTokens.styl # дизайн-система ├── utils/ | └── AppDB.coffee # доступ к данным ├── pages/ # Страницы проекта | ├── Home.coffee | ├── Home.pug | └── Home.styl └── shared/

 ├── AppLink.*     # компонент ссылок
 └── ...

## Правила

### 1. Vue-компоненты
- **Имя файла**: PascalCase (`NewsList.coffee`, `NewsList.pug`, `NewsList.styl`)  
- **Экспорт**: `module.exports = { name: '...', render: ..., data: -> {}, ... }`  
- **Стили**: подключать через  

coffee document.head.insertAdjacentHTML 'beforeend', ''+stylFns['app/shared/NewsList.styl']+'

'

- **Шаблон**: рендерить через  

coffee render: (new Function '_ctx', '_cache', renderFns['app/shared/NewsList.pug'])()

- **Жизненный цикл**:  
  ❌ НЕПРАВИЛЬНО:  

coffeescript async beforeMount: ->

  ✅ ПРАВИЛЬНО:  

coffeescript beforeMount: ->

  *(асинхронность обрабатывается внутри метода через `await`, но сигнатура — без `async`)*

### 2. Роутинг
- Все маршруты — в `temp.coffee` → `VueRouter.createRouter({ routes: [...] })`  
- Компонент страницы должен быть `require`'нут без `.default` только если не экспортирует как `default`

### 3. Ссылки
- **ЗАПРЕЩЕНО**: `a(href="...")`, `router-link(to="...")`  
- **ТОЛЬКО**: `app-link(to="...")` с подключением компонента `'app-link': require 'app/shared/AppLink'`

### 4. Стили
- **Цвета, отступы, шрифты** — ТОЛЬКО из `DesignTokens.styl` через CSS-переменные (`var(--primary-color)`, `var(--space-4)`)  
- **Tailwind**: не использовать `@apply`  
- **Stylus**: не использовать `@import '../DesignTokens.styl'` — он уже подключен глобально

### 5. Pug
- Атрибуты в одной строке, без многострочных выражений  
- Внешние данные — только через `data` или `computed`  
- **Tailwind-классы** — только внутри `class=""`, **не через точечную нотацию**  
  ❌ НЕПРАВИЛЬНО:  

pug .max-w-4xl.mx-auto.px-4

  ✅ ПРАВИЛЬНО:  

pug div(class="max-w-4xl mx-auto px-4")

- Пример правильно:

pug div(v-for="item in items" :key="item.id") div(:class="isActive ? 'active' : 'inactive'" class="w-full")


### 6. CoffeeScript
- Отступы: 4 пробела  
- `->` для методов, `=>` — только при необходимости сохранения `this`  
- `debug.log "сообщение"` вместо `console.log`  
- Строки: `"строка="+переменная`, без интерполяции

### 7. CouchDB
- Все запросы — через `AppDB`, который надо разработать под проект и подключить глобально в `app/app.coffee`
- Дизайн-документы: создавать функции `.toString()`

### 8. Доступность и дизайн
- WCAG 2.2 (контраст ≥ 4.5:1)  
- Mobile-first  
- Atomic Design + User-Centered подход  
- Анимации: 200–500 мс, плавные переходы

## Обязательное требование
> **ВСЕГДА прикладывай полные листинги всех файлов** — даже если изменения минимальны. Частичные или сокращённые фрагменты недопустимы. Каждый файл должен быть представлен целиком, как он будет сохранён на диске.

## Шаблон компонента (полный пример)
**Файл**: `app/shared/NewsList.coffee`

coffeescript document.head.insertAdjacentHTML 'beforeend',''+stylFns['app/shared/NewsList.styl']+'

' module.exports =

name: 'NewsList'
render: (new Function '_ctx', '_cache', renderFns['app/shared/NewsList.pug'])()
data: ->
    return {
        _: _
        posts: []
    }
beforeMount: ->
    @posts = await AppDB.getPublishedPosts(limit: 10)
components:
    'app-link': require 'app/shared/AppLink'

**Файл**: `app/shared/NewsList.pug`

pug div(class="space-y-4")

app-link(v-for="post in posts" :key="post.id" :to="'/pages/'+post.id")
    h3(class="text-xl") {{ post.doc.h }}

**Файл**: `app/shared/NewsList.styl`

stylus // Только стили компонента. DesignTokens — через var() .news-item

padding: var(--space-4)
border-bottom: var(--border-1) solid var(--neutral-300)

## Запрещено
- React, TypeScript, MongoDB, SASS, Webpack, Vite  
- Любые отклонения от стиля кода в `DEVELOPMENT.md`  
- Использование `Vue = require 'vue'` — всё глобально

Следуй этому промту для всех новых проектов.

---

app/DesignTokens.styl (Stylus, 5908 chars)

:root 
    // Цветовая система для темной темы по умолчанию
    --primary-color: #f87171
    --primary-dark: #ef4444
    --primary-light: #fca5a5

    --secondary-color: #1e293b
    --secondary-dark: #0f172a
    --secondary-light: #334155

    --accent-color: #22d3ee
    --accent-dark: #06b6d4
    --accent-light: #67e8f9

    // Нейтральные цвета для темной темы
    --neutral-50: #0f172a
    --neutral-100: #1e293b
    --neutral-200: #334155
    --neutral-300: #475569
    --neutral-400: #64748b
    --neutral-500: #94a3b8
    --neutral-600: #cbd5e1
    --neutral-700: #e2e8f0
    --neutral-800: #f1f5f9
    --neutral-900: #f8fafc

    // Цвета текста для темной темы
    --text-primary: #f8fafc
    --text-secondary: #e2e8f0
    --text-muted: #94a3b8

    // Цвета фона для темной темы
    --bg-primary: #0f172a
    --bg-secondary: #1e293b
    --bg-card: #1e293b
    --bg-overlay: rgba(15, 23, 42, 0.8)

    // Градиенты для темной темы
    --gradient-primary: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 50%, var(--accent-color) 100%)
    --gradient-dark: linear-gradient(135deg, var(--secondary-dark) 0%, var(--secondary-color) 100%)

    // Типографическая система
    --font-family-sans: 'Inter', 'Segoe UI', system-ui, sans-serif
    --font-family-serif: 'Georgia', 'Times New Roman', serif

    --text-xs: 0.75rem
    --text-sm: 0.875rem
    --text-base: 1rem
    --text-lg: 1.125rem
    --text-xl: 1.25rem
    --text-2xl: 1.5rem
    --text-3xl: 1.875rem
    --text-4xl: 2.25rem
    --text-5xl: 3rem

    --font-light: 300
    --font-normal: 400
    --font-medium: 500
    --font-semibold: 600
    --font-bold: 700

    // Spacing system
    --space-1: 0.25rem
    --space-2: 0.5rem
    --space-3: 0.75rem
    --space-4: 1rem
    --space-5: 1.25rem
    --space-6: 1.5rem
    --space-8: 2rem
    --space-10: 2.5rem
    --space-12: 3rem
    --space-16: 4rem
    --space-20: 5rem

    // Тени и эффекты для темной темы
    --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5)
    --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.6), 0 2px 4px -1px rgba(0, 0, 0, 0.4)
    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7), 0 4px 6px -2px rgba(0, 0, 0, 0.5)
    --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.8), 0 10px 10px -5px rgba(0, 0, 0, 0.6)

    // Анимации
    --transition-fast: 0.15s ease-in-out
    --transition-normal: 0.3s ease-in-out
    --transition-slow: 0.5s ease-in-out

    // Breakpoints
    --breakpoint-sm: 640px
    --breakpoint-md: 768px
    --breakpoint-lg: 1024px
    --breakpoint-xl: 1280px
    --breakpoint-2xl: 1536px

// Светлая тема
[data-theme="light"] 
    --primary-color: #e11d48
    --primary-dark: #be123c
    --primary-light: #fb7185

    --secondary-color: #f8fafc
    --secondary-dark: #f1f5f9
    --secondary-light: #ffffff

    --accent-color: #06b6d4
    --accent-dark: #0891b2
    --accent-light: #22d3ee

    --neutral-50: #f8fafc
    --neutral-100: #f1f5f9
    --neutral-200: #e2e8f0
    --neutral-300: #cbd5e1
    --neutral-400: #94a3b8
    --neutral-500: #64748b
    --neutral-600: #475569
    --neutral-700: #334155
    --neutral-800: #1e293b
    --neutral-900: #0f172a

    --text-primary: #0f172a
    --text-secondary: #334155
    --text-muted: #64748b

    --bg-primary: #ffffff
    --bg-secondary: #f8fafc
    --bg-card: #ffffff
    --bg-overlay: rgba(255, 255, 255, 0.8)

    --gradient-primary: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 50%, var(--accent-color) 100%)
    --gradient-dark: linear-gradient(135deg, var(--neutral-100) 0%, var(--neutral-50) 100%)

    --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05)
    --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)
    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)
    --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)

// Утилитарные классы для кастомных свойств
.bg-primary 
    background-color: var(--primary-color)

.text-primary 
    color: var(--primary-color)

.border-primary 
    border-color: var(--primary-color)

.bg-accent 
    background-color: var(--accent-color)

.text-accent 
    color: var(--accent-color)

.bg-neutral 
    background-color: var(--neutral-500)

.text-neutral 
    color: var(--neutral-500)

.bg-dark 
    background-color: var(--bg-primary)

.text-dark 
    color: var(--text-primary)

.bg-card 
    background-color: var(--bg-card)

.text-card 
    color: var(--text-primary)

// Анимации
.transition-fast 
    transition: var(--transition-fast)

.transition-normal 
    transition: var(--transition-normal)

.transition-slow 
    transition: var(--transition-slow)

// Тени
.shadow-custom-sm 
    box-shadow: var(--shadow-sm)

.shadow-custom-md 
    box-shadow: var(--shadow-md)

.shadow-custom-lg 
    box-shadow: var(--shadow-lg)

.shadow-custom-xl 
    box-shadow: var(--shadow-xl)
// Добавляем дополнительные утилитарные классы для лучшей поддержки компонентов
.card-primary 
    background-color: var(--bg-card)
    border: 1px solid var(--neutral-300)
    border-radius: 8px
    box-shadow: var(--shadow-sm)
    transition: all var(--transition-normal)

.card-primary:hover 
    border-color: var(--primary-color)
    box-shadow: var(--shadow-md)

.text-icon 
    filter: brightness(0) invert(0)
    transition: filter var(--transition-fast)

[data-theme="dark"] .text-icon 
    filter: brightness(0) invert(1)

.icon-primary 
    filter: brightness(0) invert(0)
    transition: filter var(--transition-fast)

[data-theme="dark"] .icon-primary 
    filter: brightness(0) invert(1)

// Утилиты для контрастного текста
.text-contrast-high 
    color: var(--text-primary)

.text-contrast-medium 
    color: var(--text-secondary)

.text-contrast-low 
    color: var(--text-muted)

// Утилиты для фонов
.bg-surface 
    background-color: var(--bg-card)

.bg-surface-alt 
    background-color: var(--bg-secondary)

OTHER FILES

.gitignore (Unknown, 29 chars)

html.json
pug.json
styl.json

app/app.coffee (CoffeeScript, 3584 chars)

# Подключение мета информации
document.head.insertAdjacentHTML 'beforeend', '<meta charset="UTF-8">'
document.head.insertAdjacentHTML 'beforeend', '<meta name="viewport" content="width=device-width, initial-scale=1.0">'

# Настройка tailwind
#tailwind.config = require 'tailwind.config.js'

# Подключение основных стилей
document.head.insertAdjacentHTML('beforeend', '<style type="text/tailwindcss" file="main.css">' + stylFns['main.css'] + '</style>')

document.head.insertAdjacentHTML('beforeend', '<style type="text/tailwindcss" file="app/DesignTokens.styl">' + stylFns['app/DesignTokens.styl'] + '</style>')
document.head.insertAdjacentHTML('beforeend', '<style type="text/tailwindcss" file="app/app.styl">' + stylFns['app/app.styl'] + '</style>')



# Маршруты
routes = [
    { path: '/', component: require 'app/pages/Home' }
    { path: '/:path*', component: require 'app/pages/DocumentPage' }
]
globalThis._ = {}
# Глобальное определение vuejs приложения
globalThis.app = Vue.createApp
    name: 'app'
    data: () ->
        return {
            appState:
                currentDocument: null
                currentLanguage: 'ru'
                availableLanguages: ['ru', 'en', 'tj']
                loading: true
                error: null
            dbService: new (require('app/core/DB'))()
        }
    
    beforeMount: ->
        globalThis.AppDB = new (require 'app/utils/AppDB')()
        await globalThis.AppDB.init()
        globalThis._ = @
    computed:
        currentLanguage: ->
            @appState.currentLanguage
    watch:
        currentLanguage:
            handler: (newDoc) ->
                debug.dir newDoc
                @loadDocumentForPath(window.location.pathname)
            immediate: true
    methods:
        initializeApp: ->
            # Определяем язык из URL или браузера
            @detectLanguage()
            # Загружаем документ для текущего пути
            @loadDocumentForPath(window.location.pathname)
        
        detectLanguage: ->
            # Простая логика определения языка
            pathLang = window.location.pathname.split('/')[1]
            if pathLang in @appState.availableLanguages
                @appState.currentLanguage = pathLang
            else
                browserLang = navigator.language.split('-')[0]
                if browserLang in @appState.availableLanguages
                    @appState.currentLanguage = browserLang
        
        loadDocumentForPath: (path) ->
            try
                @appState.loading = true
                doc = await AppDB.getDocumentByPath(path, AppDB.currentLanguage)
                @appState.currentDocument = doc
                @appState.loading = false
                
                # Устанавливаем заголовок страницы
                if doc?.title
                    document.head.insertAdjacentHTML('beforeend', '<title>' + doc.title + '</title>')
            catch error
                @appState.error = "Ошибка загрузки документа: " + error
                @appState.loading = false
        
    
    render: (new Function '_ctx', '_cache', renderFns['app/app.pug'])()

    
    components: {
        'hero-section': require('shared/HeroSection')
        'image-gallery': require('shared/ImageGallery')
    }
# Создаем и настраиваем роутер
router = VueRouter.createRouter({
    routes: routes
    history: VueRouter.createWebHistory()
    scrollBehavior: (to, from, savedPosition) ->
        if savedPosition
            return savedPosition
        else
            return { x: 0, y: 0 }
})

app.use(router)
app.mount('body')

app/app.pug (Pug Template, 699 chars)

include ../pug/base.pug
include ../pug/bem.pug

div(class="min-h-screen bg-white dark:bg-gray-900 transition-colors duration-300")
    div(v-if="appState.loading" class="flex items-center justify-center min-h-screen")
        div(class="text-center")
            div(class="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600")
            p(class="mt-4 text-gray-600 dark:text-gray-400") Загрузка...
    
    div(v-else-if="appState.error" class="flex items-center justify-center min-h-screen")
        div(class="text-center")
            div(class="text-red-600 text-xl") Ошибка
            p(class="text-gray-600 dark:text-gray-400") {{ appState.error }}
    
    router-view(v-else)

app/app.styl (Stylus, 1013 chars)

// Переменные темы
:root 
    --primary-color: #3b82f6
    --secondary-color: #1e40af
    --text-primary: #1f2937
    --text-secondary: #6b7280
    --bg-primary: #ffffff
    --bg-secondary: #f9fafb

[data-theme="dark"]
    --primary-color: #60a5fa
    --secondary-color: #3b82f6
    --text-primary: #f9fafb
    --text-secondary: #d1d5db
    --bg-primary: #111827
    --bg-secondary: #1f2937

// Базовые стили
body 
    font-family: 'Inter', system-ui, -apple-system, sans-serif
    color: var(--text-primary)
    background-color: var(--bg-primary)
    transition: all 0.3s ease

// Стили для Markdown контента
.prose 
    h1, h2, h3, h4, h5, h6 
        color: var(--text-primary)
        font-weight: 600
    
    p 
        color: var(--text-secondary)
        line-height: 1.7
    
    a 
        color: var(--primary-color)
        text-decoration: none
        &:hover 
            text-decoration: underline
    
    img 
        border-radius: 0.5rem
        box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1)

app/layout.pug (Pug Template, 324 chars)

include ../pug/base.pug
include ../pug/bem.pug

+mbh
    header(class="sticky top-0 z-50 glass border-b border-neutral-300/60 transition-all duration-300")
        div(class="navbar max-w-7xl mx-auto px-4 sm:px-6 lg:px-8")
    block top-content
    
+mbl(class="py-8 md:py-12 lg:py-16")
    .container
        block content

app/pages/Home.coffee (CoffeeScript, 697 chars)

document.head.insertAdjacentHTML 'beforeend', '<style type="text/css" file="app/pages/Home.styl">'+stylFns['app/pages/Home.styl']+'</style>'
module.exports =
    name: 'Home'
    render: (new Function '_ctx', '_cache', renderFns['app/pages/Home.pug'])()
    data: ->
        return {
            _: _
            document: null
        }
    beforeMount: ->
        try
            @document = await AppDB.getDocumentByPath('/', AppDB.currentLanguage)
        catch e
            debug.log "Document load error:", e
    components:
        'app-link': require 'app/shared/AppLink'
        'hero-section': require 'app/shared/HeroSection'
        'image-gallery': require 'app/shared/ImageGallery'

app/pages/Home.pug (Pug Template, 845 chars)

include ../../pug/base.pug
include ../../pug/bem.pug
extends ../layout.pug

block top-content
    hero-section(:document="document")

block content
    div(class="max-w-4xl mx-auto px-4" v-if="document")
        h1(class="text-3xl font-bold text-contrast-high animate-fade-in-up") {{ document.translations[_.currentLanguage]?.title || document.translations.en.title }}
        p(class="text-xl text-contrast-medium mb-8 animate-fade-in-up") {{ document.translations[_.currentLanguage]?.subtitle || document.translations.en.subtitle }}
        div(class="prose mt-6 animate-fade-in-up" v-html="marked.parse(document.translations[_.currentLanguage]?.content || document.translations.en.content)")
        div(class="mt-12")
            image-gallery(:images="document.translations[_.currentLanguage]?.gallery || document.translations.en.gallery")

app/pages/Home.styl (Stylus, 281 chars)

// Используем только утилиты из DesignTokens
.animate-fade-in-up
    animation: fadeInUp var(--transition-slow) ease-out

@keyframes fadeInUp
    from
        opacity: 0
        transform: translateY(var(--transition-px))
    to
        opacity: 1
        transform: translateY(0)

app/shared/AppLink.coffee (CoffeeScript, 633 chars)

document.head.insertAdjacentHTML('beforeend','<style type="text/css" file="app/shared/AppLink.styl">'+stylFns['app/shared/AppLink.styl']+'</style>')
module.exports =
    default:
        render: (new Function '_ctx', '_cache', renderFns['app/shared/AppLink.pug'])()
        name: 'AppLink'
        props:
            to:
                type: [String, Object]
                required: true
        computed:
            isExternal: ->
                if typeof @.to == 'string'
                    return @.to.startsWith('http')
                return false
        data: -> 
            return {
                _: _
            }

app/shared/AppLink.pug (Pug Template, 214 chars)

include ../../pug/base.pug
include ../../pug/bem.pug

a.app-link(v-if="isExternal" v-bind="$attrs" :href="to" target="_blank" rel="noopener")
    slot
router-link.app-link(v-else v-bind="$attrs" :to="to")
    slot

app/shared/AppLink.styl (Stylus, 1264 chars)

@import '../DesignTokens.styl'

// Базовые стили для AppLink компонента
.app-link 
    color: var(--primary-color)
    text-decoration: none
    transition: color var(--transition-fast)

.app-link:hover 
    color: var(--primary-dark)
    text-decoration: underline

.app-link.router-link-active 
    font-weight: var(--font-semibold)
    color: var(--primary-dark)

// Стили для внешних ссылок
.app-link-external::after 
    content: " ↗"
    font-size: 0.875em
    opacity: 0.7

// Стили для кнопко-подобных ссылок
.app-link-button 
    display: inline-flex
    align-items: center
    gap: var(--space-2)
    padding: var(--space-2) var(--space-4)
    background: var(--primary-color)
    color: white
    border-radius: 6px
    font-weight: var(--font-medium)
    transition: all var(--transition-fast)

.app-link-button:hover 
    background: var(--primary-dark)
    transform: translateY(-1px)
    text-decoration: none
    color: white
    box-shadow: var(--shadow-md)

// Стили для иконок в ссылках
.app-link-with-icon 
    display: inline-flex
    align-items: center
    gap: var(--space-2)

.app-link-icon 
    width: 16px
    height: 16px
    transition: transform var(--transition-fast)

.app-link:hover .app-link-icon 
    transform: translateX(2px)

app/shared/HeroSection.coffee (CoffeeScript, 311 chars)

document.head.insertAdjacentHTML 'beforeend', '<style type="text/css" file="app/shared/HeroSection.styl">'+stylFns['app/shared/HeroSection.styl']+'</style>'
module.exports =
    name: 'HeroSection'
    props: [ 'document' ]
    render: (new Function '_ctx', '_cache', renderFns['app/shared/HeroSection.pug'])()

app/shared/HeroSection.pug (Pug Template, 574 chars)

include ../../pug/base.pug
include ../../pug/bem.pug
section(v-if="document" class="hero-section bg-gradient")
    div(class="max-w-7xl mx-auto px-4 py-24 text-center text-white")
        h1(v-if="document.translations[_.appState.currentLanguage]" class="text-5xl font-bold mb-6") {{ document.translations[_.appState.currentLanguage].title || document.translations.en.title }}
        p(v-if="document.translations[_.appState.currentLanguage]" class="text-xl opacity-90") {{ document.translations[_.appState.currentLanguage].subtitle || document.translations.en.subtitle }}

app/shared/HeroSection.styl (Stylus, 125 chars)

.hero-section
    background: var(--gradient-primary)
    color: white
    padding: var(--space-16) 0
    text-align: center

app/shared/ImageGallery.coffee (CoffeeScript, 367 chars)

document.head.insertAdjacentHTML 'beforeend', '<style type="text/css" file="app/shared/ImageGallery.styl">'+stylFns['app/shared/ImageGallery.styl']+'</style>'
module.exports =
    name: 'ImageGallery'
    props:
        images:
            type: Array
            default: -> []
    render: (new Function '_ctx', '_cache', renderFns['app/shared/ImageGallery.pug'])()

app/shared/ImageGallery.pug (Pug Template, 414 chars)

include ../../pug/base.pug
include ../../pug/bem.pug
div(v-if="images && images.length > 0" class="image-gallery grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6")
    div(v-for="(img, idx) in images" :key="idx" class="overflow-hidden rounded-lg shadow-custom-md bg-surface")
        img(:src="img.src" :alt="img.alt || ''" class="w-full h-auto object-cover transition-transform duration-300 hover:scale-105")

app/shared/ImageGallery.styl (Stylus, 324 chars)

.image-gallery
    display: grid
    gap: var(--space-6)
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr))

.image-gallery img
    width: 100%
    height: auto
    object-fit: cover
    border-radius: 8px
    transition: transform var(--transition-normal)

.image-gallery img:hover
    transform: scale(1.05)

app/utils/AppDB.coffee (CoffeeScript, 4684 chars)

# FILE: app/utils/AppDB.coffee
class AppDB
    currentLanguage: 'ru'
    currentProject: 's5l.ru'
    designDocVersion: '1.1' # управление версией дизайна

    constructor: ->
        @localDB = new PouchDB('s5l_local')
        @remoteDB = new PouchDB('https://oleg:631074@couchdb.favt.ru.net/s5lru/')
        @syncHandler = null

    init: ->
        try
            await @remoteDB.info()
            debug.log "Remote DB connected"
            await @ensureDesignDocs()
            await @ensureDefaultContent()
            @startSync()
            AppDB.currentLanguage = globalThis._?.appState?.currentLanguage or 'ru'
        catch e
            debug.log "DB init failed:", e

    startSync: ->
        @syncHandler = @localDB.sync(@remoteDB, { live: true, retry: true })
            .on 'error', (err) -> debug.log "Sync error:", err

    ensureDesignDocs: ->
        adminDoc =
            _id: '_design/admin'
            version: @designDocVersion
            views:
                byPath:
                    map: (doc) ->
                        if doc.type == 'page'
                            emit [doc.domain, doc.path], doc
                    .toString()
                byType:
                    map: (doc) ->
                        emit doc.type, doc
                    .toString()

        try
            existing = await @remoteDB.get('_design/admin')
            if existing.version != @designDocVersion
                adminDoc._rev = existing._rev
                await @remoteDB.put(adminDoc)
                debug.log "Design doc updated to v#{@designDocVersion}"
        catch
            await @remoteDB.put(adminDoc)
            debug.log "Design doc created v#{@designDocVersion}"

    ensureDefaultContent: ->
        defaultHome =
            _id: 'page::s5l.ru::/'
            type: 'page'
            domain: 's5l.ru'
            path: '/'
            translations:
                ru:
                    title: "s5l.ru — мультиязычная offline-first платформа"
                    subtitle: "Разрабатывайте быстро, работайте везде"
                    content: '''
# Добро пожаловать на s5l.ru

**s5l.ru** — это платформа для быстрого запуска веб-проектов с поддержкой:
- offline-first через PouchDB/CouchDB
- автоматического переключения языка
- динамической подгрузки контента
- полной WCAG 2.2-совместимости

Все тексты хранятся в базе и легко редактируются через админку.
'''
                    gallery: [
                        { src: "/assets/hero-s5l.svg", alt: "Hero illustration" }
                    ]
                en:
                    title: "s5l.ru — multilingual offline-first platform"
                    subtitle: "Build fast, work anywhere"
                    content: '''
# Welcome to s5l.ru

**s5l.ru** is a platform for rapid web project launches with:
- offline-first via PouchDB/CouchDB
- automatic language switching
- dynamic content loading
- full WCAG 2.2 compliance

All text is stored in the database and editable via admin panel.
'''
                    gallery: [
                        { src: "/assets/hero-s5l.svg", alt: "Hero illustration" }
                    ]
                tj:
                    title: "s5l.ru — платформаи бисёрзабон ва аввал офлайн"
                    subtitle: "Бисёр тез бунёд кунед, дар ҳама ҷо кор кунед"
                    content: '''
# Ба s5l.ru хуш омадед

**s5l.ru** — ин платформа барои оғози тези лоиҳаҳои веб аст бо:
- офлайн-аввал аз рӯи PouchDB/CouchDB
- ивази худкори забон
- боркунии динамикӣ
- мутобиқати пурраи WCAG 2.2

Ҳамаи матнҳо дар база нигоҳ дошта мешаванд ва аз ҷониби панели маъмури озодона таҳрир карда мешаванд.
'''
                    gallery: [
                        { src: "/assets/hero-s5l.svg", alt: "Hero illustration" }
                    ]

        try
            await @remoteDB.get('page::s5l.ru::/')
        catch
            await @remoteDB.put(defaultHome)
            debug.log "Default home page created"

    getDocumentByPath: (path, lang = @currentLanguage) ->
        path = path or '/'
        try
            result = await @localDB.query('admin/byPath', { key: [@currentProject, path], include_docs: true })
            if result.rows.length > 0
                doc = result.rows[0].doc
                return doc
            else
                throw new Error "Document not found"
        catch e
            debug.log "Fallback for path:", path, "lang:", lang
            # fallback to English if not found
            if lang != 'en'
                return await @getDocumentByPath(path, 'en')
            else
                throw new Error "Document not available even in English"

module.exports = AppDB

doc.coffee (CoffeeScript, 1285 chars)

module.exports = 
     version: "0.0.1"
     adres: [
        {
          "adr": "https://cdn.tailwindcss.com/3.4.17"
          "eventName": "tailwindReady"
          "obj": "tailwind"
        }
        {
          "adr": "https://unpkg.com/pouchdb/dist/pouchdb.min.js"
          "eventName": "puochReady"
          "obj": "PouchDB"
        }
        {
          "adr": "https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"
          "eventName": "vueReady"
          "obj": "Vue"
        }
        {
          "adr": "https://unpkg.com/vue-router@4/dist/vue-router.global.js"
          "eventName": "VueRouterReady"
          "obj": "VueRouter"
        }
        {
          "adr": "https://cdn.jsdelivr.net/npm/@vue/compiler-sfc@3.5.22/dist/compiler-sfc.esm-browser.min.js"
          "eventName": "compileTemplateReady"
          "obj": "compileTemplate"
        }
        {
          "adr": "https://cdn.jsdelivr.net/npm/marked@16.4.1/lib/marked.umd.min.js"
          "eventName": "markedReady"
          "obj": "marked"
        }
        {
          "adr": "https://cdn.jsdelivr.net/npm/openlayers@4.6.5/dist/ol.min.js"
          "css": "https://cdn.jsdelivr.net/npm/openlayers@4.6.5/dist/ol.min.css"
          "eventName": "leafletReady"
          "obj": "ol"
        }
    ]

lzma.coffee (CoffeeScript, 1010 chars)

globalThis.debug = require('debug.coffee').default
require('headVue.coffee')


document.documentElement.classList.add('dark')

globalThis.initCount = 0
ic = ()->
    if initCount > 5
       window.location.reload()
    else if  not globalThis['appReady']
       initCount++
       #setTimeout ic, 200
ic()
# обязательно подключение глобальных массивов
globalThis.renderFns = require 'pug.json'
globalThis.stylFns   = require 'styl.json'

try
    init =  (event={})->
        debug.dir   globalThis['vueReady']
        debug.log "Init Start"
        if  not globalThis['appReady'] and globalThis['vueReady'] and globalThis['puochReady']
            debug.log 'init start ok'
            try
                require('app/app.coffee')
                globalThis['appReady'] = true
                debug.log "init is ok"
            catch err
                debug.dir err
        else if  not globalThis['appReady']
                debug.log 'pausedEvent appReady'
                setTimeout init, 200
    init()

pug/base.pug (Pug Template, 504 chars)

include ./bem.pug


mixin mbh
    - var otherClasses = attributes.class || ''
    +b('hfn')(class="h-max overflow-hidden relative")
        +e('hfn','fon')

mixin mbl
    - var otherClasses = attributes.class || ''
    +b('hfn')(class="h-max overflow-hidden relative")
        +e('hfn','fon')(class="[background:url('https://jahonnamo.s5l.ru/assets/jahonnamo.s5l.ru/bkg00.webp')_0_0_/_cover_no-repeat] bg-[#ffffff] absolute h-full w-full z-[-1]")
                  if block
                        block

pug/bem.pug (Pug Template, 1035 chars)

//- bem.pug

//- Базовый миксин для блоков и элементов
mixin b(blockName, elemName)
  - var className = blockName
  - var twClasses = attributes.tw || ''
  - var otherClasses = attributes.class || ''

  if elemName
    - className += '__' + elemName

  //- Обработка модификаторов
  if attributes.mod
    - className += ' ' + blockName + '--' + attributes.mod
  if attributes.mods
    each mod in attributes.mods.split(',')
      - className += ' ' + blockName + '--' + mod.trim()

  //- Собираем финальный класс
  - var finalClass = [className, twClasses, otherClasses].filter(Boolean).join(' ')

  //- Удаляем обработанные атрибуты
  - attributes.tw = null
  - attributes.mod = null
  - attributes.mods = null
  - attributes.class = null

  //- Генерация элемента
  if block
    div(class=finalClass)&attributes(attributes)
      block
  else
    div(class=finalClass)&attributes(attributes)

//- Миксин для элементов (альтернативный синтаксис)
mixin e(blockName, elemName)
  +b(blockName, elemName)&attributes(attributes)
    block

MAIN COMPONENTS

ROUTES

DEPENDENCIES

FULL SOURCE CODE

PRIORITY DOCUMENTATION SOURCE CODE

// FILE: README.md // TYPE: Markdown // SIZE: 7142 characters // CATEGORY: PRIORITY DOCUMENTATION

# Текущее задание (Выполнить)

## Общее задание:
Разработать веб-приложение [название], используя платформу **s5l.ru**

### Дизайн:
- Темы: темная по умолчанию, переключаемая на светлую через `[data-theme="light"]`
- Цветовая палитра из `DesignTokens.styl`
- Atomic Design + User-Centered подход
- Полная поддержка WCAG 2.2 (контраст ≥ 4.5:1)

### Функциональность:
- Автоматическое определение языка (URL → браузер)
- Динамическая подгрузка контента из CouchDB с локальной репликацией через PouchDB
- Offline-first: работа без интернета после первого визита
- Единая система стилей через CSS-переменные и Tailwind

## Текущее действие:

Сделай первый этап разработки, включающий файлы app/pages/home* и `app/utils/AppDB.coffee` — полная реализация с методами `getDocumentByPath`
с проверкой наличия там документа, с начтройками саййта, если его нет создать документ по умолчанию при инициализации AppDB


Опиши следущие для разработки файлы.

---

# Промт для разработки на платформе s5l.ru

Ты — Senior Fullstack-архитектор и UI/UX-прагматик. Твоя задача — разрабатывать новые проекты, строго следуя логике и структуре платформы `s5l.ru`.

## Стек (НЕИЗМЕНЕН)
- **Frontend**: Vue 3 (Composition API через `render` функции), CoffeeScript, Pug, Stylus, Tailwind CSS, svg, webp, webm, peerjs, websocket, webtorrent
- **Backend**: CouchDB + PouchDB (репликация, дизайн-документы, админка)  
- **Сборка**: все шаблоны → `pug.json`, стили → `styl.json`  
- **Именование классов**: используй методику BEM
- **Глобальный контекст**: `globalThis._` — ссылка на корневой Vue-экземпляр из `app/app.coffee`

# Проект: s5l.ru

## Общее описание
`s5l.ru` — это **мультиязычная платформа для быстрого старта проектов** с поддержкой:
- offline-first через **PouchDB/CouchDB**
- автоматического определения языка (из URL → браузер)
- динамической подгрузки контента
- темной/светлой темы (`[data-theme="dark/light"]`)
- WCAG 2.2 (контраст ≥ 4.5:1)

## Стек
- **Frontend**: Vue 3 (render-функции), CoffeeScript, Pug, Stylus, Tailwind CSS
- **Backend**: CouchDB + PouchDB (репликация, дизайн-документы)
- **Сборка**: `pug.json`, `styl.json`

## Запуск нового проекта
1. Создать дизайн-документы в CouchDB
2. Положить стартовые документы с `type: 'page'`, `path: '/'`, и `translations`
3. Использовать `AppDB.getDocumentByPath` в `beforeMount`
4. Стили — только через CSS-переменные из `DesignTokens.styl`

## Доступность
- Поддержка тем: `:root` → `[data-theme="light"]`
- Контраст ≥ 4.5:1
- Анимации ≤ 500 мс
- Mobile-first верстка


## Архитектура
- Все тексты хранятся в **CouchDB** с поддержкой мультиязычности и мультидоменности
- Вся логика работы с БД — через `AppDB`
- Все компоненты — по **Atomic Design**, стили — через `DesignTokens.styl`

## Структура проекта

app/ ├── assets/ # Системные изображения, пиктограммы ├── app.coffee # инициализация Vue, AppDB, глобальный _ ├── app.pug # Основной шаблон с Хедером, , Футером ├── app.styl # Глобальные стили ├── DesignTokens.styl # дизайн-система ├── utils/ | └── AppDB.coffee # доступ к данным ├── pages/ # Страницы проекта | ├── Home.coffee | ├── Home.pug | └── Home.styl └── shared/

 ├── AppLink.*     # компонент ссылок
 └── ...

## Правила

### 1. Vue-компоненты
- **Имя файла**: PascalCase (`NewsList.coffee`, `NewsList.pug`, `NewsList.styl`)  
- **Экспорт**: `module.exports = { name: '...', render: ..., data: -> {}, ... }`  
- **Стили**: подключать через  

coffee document.head.insertAdjacentHTML 'beforeend', ''+stylFns['app/shared/NewsList.styl']+'

'

- **Шаблон**: рендерить через  

coffee render: (new Function '_ctx', '_cache', renderFns['app/shared/NewsList.pug'])()

- **Жизненный цикл**:  
  ❌ НЕПРАВИЛЬНО:  

coffeescript async beforeMount: ->

  ✅ ПРАВИЛЬНО:  

coffeescript beforeMount: ->

  *(асинхронность обрабатывается внутри метода через `await`, но сигнатура — без `async`)*

### 2. Роутинг
- Все маршруты — в `temp.coffee` → `VueRouter.createRouter({ routes: [...] })`  
- Компонент страницы должен быть `require`'нут без `.default` только если не экспортирует как `default`

### 3. Ссылки
- **ЗАПРЕЩЕНО**: `a(href="...")`, `router-link(to="...")`  
- **ТОЛЬКО**: `app-link(to="...")` с подключением компонента `'app-link': require 'app/shared/AppLink'`

### 4. Стили
- **Цвета, отступы, шрифты** — ТОЛЬКО из `DesignTokens.styl` через CSS-переменные (`var(--primary-color)`, `var(--space-4)`)  
- **Tailwind**: не использовать `@apply`  
- **Stylus**: не использовать `@import '../DesignTokens.styl'` — он уже подключен глобально

### 5. Pug
- Атрибуты в одной строке, без многострочных выражений  
- Внешние данные — только через `data` или `computed`  
- **Tailwind-классы** — только внутри `class=""`, **не через точечную нотацию**  
  ❌ НЕПРАВИЛЬНО:  

pug .max-w-4xl.mx-auto.px-4

  ✅ ПРАВИЛЬНО:  

pug div(class="max-w-4xl mx-auto px-4")

- Пример правильно:

pug div(v-for="item in items" :key="item.id") div(:class="isActive ? 'active' : 'inactive'" class="w-full")


### 6. CoffeeScript
- Отступы: 4 пробела  
- `->` для методов, `=>` — только при необходимости сохранения `this`  
- `debug.log "сообщение"` вместо `console.log`  
- Строки: `"строка="+переменная`, без интерполяции

### 7. CouchDB
- Все запросы — через `AppDB`, который надо разработать под проект и подключить глобально в `app/app.coffee`
- Дизайн-документы: создавать функции `.toString()`

### 8. Доступность и дизайн
- WCAG 2.2 (контраст ≥ 4.5:1)  
- Mobile-first  
- Atomic Design + User-Centered подход  
- Анимации: 200–500 мс, плавные переходы

## Обязательное требование
> **ВСЕГДА прикладывай полные листинги всех файлов** — даже если изменения минимальны. Частичные или сокращённые фрагменты недопустимы. Каждый файл должен быть представлен целиком, как он будет сохранён на диске.

## Шаблон компонента (полный пример)
**Файл**: `app/shared/NewsList.coffee`

coffeescript document.head.insertAdjacentHTML 'beforeend',''+stylFns['app/shared/NewsList.styl']+'

' module.exports =

name: 'NewsList'
render: (new Function '_ctx', '_cache', renderFns['app/shared/NewsList.pug'])()
data: ->
    return {
        _: _
        posts: []
    }
beforeMount: ->
    @posts = await AppDB.getPublishedPosts(limit: 10)
components:
    'app-link': require 'app/shared/AppLink'

**Файл**: `app/shared/NewsList.pug`

pug div(class="space-y-4")

app-link(v-for="post in posts" :key="post.id" :to="'/pages/'+post.id")
    h3(class="text-xl") {{ post.doc.h }}

**Файл**: `app/shared/NewsList.styl`

stylus // Только стили компонента. DesignTokens — через var() .news-item

padding: var(--space-4)
border-bottom: var(--border-1) solid var(--neutral-300)

## Запрещено
- React, TypeScript, MongoDB, SASS, Webpack, Vite  
- Любые отклонения от стиля кода в `DEVELOPMENT.md`  
- Использование `Vue = require 'vue'` — всё глобально

Следуй этому промту для всех новых проектов.

---

// FILE: app/DesignTokens.styl // TYPE: Stylus // SIZE: 5908 characters // CATEGORY: PRIORITY DOCUMENTATION

:root 
    // Цветовая система для темной темы по умолчанию
    --primary-color: #f87171
    --primary-dark: #ef4444
    --primary-light: #fca5a5

    --secondary-color: #1e293b
    --secondary-dark: #0f172a
    --secondary-light: #334155

    --accent-color: #22d3ee
    --accent-dark: #06b6d4
    --accent-light: #67e8f9

    // Нейтральные цвета для темной темы
    --neutral-50: #0f172a
    --neutral-100: #1e293b
    --neutral-200: #334155
    --neutral-300: #475569
    --neutral-400: #64748b
    --neutral-500: #94a3b8
    --neutral-600: #cbd5e1
    --neutral-700: #e2e8f0
    --neutral-800: #f1f5f9
    --neutral-900: #f8fafc

    // Цвета текста для темной темы
    --text-primary: #f8fafc
    --text-secondary: #e2e8f0
    --text-muted: #94a3b8

    // Цвета фона для темной темы
    --bg-primary: #0f172a
    --bg-secondary: #1e293b
    --bg-card: #1e293b
    --bg-overlay: rgba(15, 23, 42, 0.8)

    // Градиенты для темной темы
    --gradient-primary: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 50%, var(--accent-color) 100%)
    --gradient-dark: linear-gradient(135deg, var(--secondary-dark) 0%, var(--secondary-color) 100%)

    // Типографическая система
    --font-family-sans: 'Inter', 'Segoe UI', system-ui, sans-serif
    --font-family-serif: 'Georgia', 'Times New Roman', serif

    --text-xs: 0.75rem
    --text-sm: 0.875rem
    --text-base: 1rem
    --text-lg: 1.125rem
    --text-xl: 1.25rem
    --text-2xl: 1.5rem
    --text-3xl: 1.875rem
    --text-4xl: 2.25rem
    --text-5xl: 3rem

    --font-light: 300
    --font-normal: 400
    --font-medium: 500
    --font-semibold: 600
    --font-bold: 700

    // Spacing system
    --space-1: 0.25rem
    --space-2: 0.5rem
    --space-3: 0.75rem
    --space-4: 1rem
    --space-5: 1.25rem
    --space-6: 1.5rem
    --space-8: 2rem
    --space-10: 2.5rem
    --space-12: 3rem
    --space-16: 4rem
    --space-20: 5rem

    // Тени и эффекты для темной темы
    --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.5)
    --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.6), 0 2px 4px -1px rgba(0, 0, 0, 0.4)
    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.7), 0 4px 6px -2px rgba(0, 0, 0, 0.5)
    --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.8), 0 10px 10px -5px rgba(0, 0, 0, 0.6)

    // Анимации
    --transition-fast: 0.15s ease-in-out
    --transition-normal: 0.3s ease-in-out
    --transition-slow: 0.5s ease-in-out

    // Breakpoints
    --breakpoint-sm: 640px
    --breakpoint-md: 768px
    --breakpoint-lg: 1024px
    --breakpoint-xl: 1280px
    --breakpoint-2xl: 1536px

// Светлая тема
[data-theme="light"] 
    --primary-color: #e11d48
    --primary-dark: #be123c
    --primary-light: #fb7185

    --secondary-color: #f8fafc
    --secondary-dark: #f1f5f9
    --secondary-light: #ffffff

    --accent-color: #06b6d4
    --accent-dark: #0891b2
    --accent-light: #22d3ee

    --neutral-50: #f8fafc
    --neutral-100: #f1f5f9
    --neutral-200: #e2e8f0
    --neutral-300: #cbd5e1
    --neutral-400: #94a3b8
    --neutral-500: #64748b
    --neutral-600: #475569
    --neutral-700: #334155
    --neutral-800: #1e293b
    --neutral-900: #0f172a

    --text-primary: #0f172a
    --text-secondary: #334155
    --text-muted: #64748b

    --bg-primary: #ffffff
    --bg-secondary: #f8fafc
    --bg-card: #ffffff
    --bg-overlay: rgba(255, 255, 255, 0.8)

    --gradient-primary: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 50%, var(--accent-color) 100%)
    --gradient-dark: linear-gradient(135deg, var(--neutral-100) 0%, var(--neutral-50) 100%)

    --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05)
    --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)
    --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)
    --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)

// Утилитарные классы для кастомных свойств
.bg-primary 
    background-color: var(--primary-color)

.text-primary 
    color: var(--primary-color)

.border-primary 
    border-color: var(--primary-color)

.bg-accent 
    background-color: var(--accent-color)

.text-accent 
    color: var(--accent-color)

.bg-neutral 
    background-color: var(--neutral-500)

.text-neutral 
    color: var(--neutral-500)

.bg-dark 
    background-color: var(--bg-primary)

.text-dark 
    color: var(--text-primary)

.bg-card 
    background-color: var(--bg-card)

.text-card 
    color: var(--text-primary)

// Анимации
.transition-fast 
    transition: var(--transition-fast)

.transition-normal 
    transition: var(--transition-normal)

.transition-slow 
    transition: var(--transition-slow)

// Тени
.shadow-custom-sm 
    box-shadow: var(--shadow-sm)

.shadow-custom-md 
    box-shadow: var(--shadow-md)

.shadow-custom-lg 
    box-shadow: var(--shadow-lg)

.shadow-custom-xl 
    box-shadow: var(--shadow-xl)
// Добавляем дополнительные утилитарные классы для лучшей поддержки компонентов
.card-primary 
    background-color: var(--bg-card)
    border: 1px solid var(--neutral-300)
    border-radius: 8px
    box-shadow: var(--shadow-sm)
    transition: all var(--transition-normal)

.card-primary:hover 
    border-color: var(--primary-color)
    box-shadow: var(--shadow-md)

.text-icon 
    filter: brightness(0) invert(0)
    transition: filter var(--transition-fast)

[data-theme="dark"] .text-icon 
    filter: brightness(0) invert(1)

.icon-primary 
    filter: brightness(0) invert(0)
    transition: filter var(--transition-fast)

[data-theme="dark"] .icon-primary 
    filter: brightness(0) invert(1)

// Утилиты для контрастного текста
.text-contrast-high 
    color: var(--text-primary)

.text-contrast-medium 
    color: var(--text-secondary)

.text-contrast-low 
    color: var(--text-muted)

// Утилиты для фонов
.bg-surface 
    background-color: var(--bg-card)

.bg-surface-alt 
    background-color: var(--bg-secondary)

OTHER FILES SOURCE CODE

// FILE: .gitignore // TYPE: Unknown // SIZE: 29 characters

html.json
pug.json
styl.json

// FILE: app/app.coffee // TYPE: CoffeeScript // SIZE: 3584 characters

# Подключение мета информации
document.head.insertAdjacentHTML 'beforeend', '<meta charset="UTF-8">'
document.head.insertAdjacentHTML 'beforeend', '<meta name="viewport" content="width=device-width, initial-scale=1.0">'

# Настройка tailwind
#tailwind.config = require 'tailwind.config.js'

# Подключение основных стилей
document.head.insertAdjacentHTML('beforeend', '<style type="text/tailwindcss" file="main.css">' + stylFns['main.css'] + '</style>')

document.head.insertAdjacentHTML('beforeend', '<style type="text/tailwindcss" file="app/DesignTokens.styl">' + stylFns['app/DesignTokens.styl'] + '</style>')
document.head.insertAdjacentHTML('beforeend', '<style type="text/tailwindcss" file="app/app.styl">' + stylFns['app/app.styl'] + '</style>')



# Маршруты
routes = [
    { path: '/', component: require 'app/pages/Home' }
    { path: '/:path*', component: require 'app/pages/DocumentPage' }
]
globalThis._ = {}
# Глобальное определение vuejs приложения
globalThis.app = Vue.createApp
    name: 'app'
    data: () ->
        return {
            appState:
                currentDocument: null
                currentLanguage: 'ru'
                availableLanguages: ['ru', 'en', 'tj']
                loading: true
                error: null
            dbService: new (require('app/core/DB'))()
        }
    
    beforeMount: ->
        globalThis.AppDB = new (require 'app/utils/AppDB')()
        await globalThis.AppDB.init()
        globalThis._ = @
    computed:
        currentLanguage: ->
            @appState.currentLanguage
    watch:
        currentLanguage:
            handler: (newDoc) ->
                debug.dir newDoc
                @loadDocumentForPath(window.location.pathname)
            immediate: true
    methods:
        initializeApp: ->
            # Определяем язык из URL или браузера
            @detectLanguage()
            # Загружаем документ для текущего пути
            @loadDocumentForPath(window.location.pathname)
        
        detectLanguage: ->
            # Простая логика определения языка
            pathLang = window.location.pathname.split('/')[1]
            if pathLang in @appState.availableLanguages
                @appState.currentLanguage = pathLang
            else
                browserLang = navigator.language.split('-')[0]
                if browserLang in @appState.availableLanguages
                    @appState.currentLanguage = browserLang
        
        loadDocumentForPath: (path) ->
            try
                @appState.loading = true
                doc = await AppDB.getDocumentByPath(path, AppDB.currentLanguage)
                @appState.currentDocument = doc
                @appState.loading = false
                
                # Устанавливаем заголовок страницы
                if doc?.title
                    document.head.insertAdjacentHTML('beforeend', '<title>' + doc.title + '</title>')
            catch error
                @appState.error = "Ошибка загрузки документа: " + error
                @appState.loading = false
        
    
    render: (new Function '_ctx', '_cache', renderFns['app/app.pug'])()

    
    components: {
        'hero-section': require('shared/HeroSection')
        'image-gallery': require('shared/ImageGallery')
    }
# Создаем и настраиваем роутер
router = VueRouter.createRouter({
    routes: routes
    history: VueRouter.createWebHistory()
    scrollBehavior: (to, from, savedPosition) ->
        if savedPosition
            return savedPosition
        else
            return { x: 0, y: 0 }
})

app.use(router)
app.mount('body')

// FILE: app/app.pug // TYPE: Pug Template // SIZE: 699 characters

include ../pug/base.pug
include ../pug/bem.pug

div(class="min-h-screen bg-white dark:bg-gray-900 transition-colors duration-300")
    div(v-if="appState.loading" class="flex items-center justify-center min-h-screen")
        div(class="text-center")
            div(class="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600")
            p(class="mt-4 text-gray-600 dark:text-gray-400") Загрузка...
    
    div(v-else-if="appState.error" class="flex items-center justify-center min-h-screen")
        div(class="text-center")
            div(class="text-red-600 text-xl") Ошибка
            p(class="text-gray-600 dark:text-gray-400") {{ appState.error }}
    
    router-view(v-else)

// FILE: app/app.styl // TYPE: Stylus // SIZE: 1013 characters

// Переменные темы
:root 
    --primary-color: #3b82f6
    --secondary-color: #1e40af
    --text-primary: #1f2937
    --text-secondary: #6b7280
    --bg-primary: #ffffff
    --bg-secondary: #f9fafb

[data-theme="dark"]
    --primary-color: #60a5fa
    --secondary-color: #3b82f6
    --text-primary: #f9fafb
    --text-secondary: #d1d5db
    --bg-primary: #111827
    --bg-secondary: #1f2937

// Базовые стили
body 
    font-family: 'Inter', system-ui, -apple-system, sans-serif
    color: var(--text-primary)
    background-color: var(--bg-primary)
    transition: all 0.3s ease

// Стили для Markdown контента
.prose 
    h1, h2, h3, h4, h5, h6 
        color: var(--text-primary)
        font-weight: 600
    
    p 
        color: var(--text-secondary)
        line-height: 1.7
    
    a 
        color: var(--primary-color)
        text-decoration: none
        &:hover 
            text-decoration: underline
    
    img 
        border-radius: 0.5rem
        box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1)

// FILE: app/layout.pug // TYPE: Pug Template // SIZE: 324 characters

include ../pug/base.pug
include ../pug/bem.pug

+mbh
    header(class="sticky top-0 z-50 glass border-b border-neutral-300/60 transition-all duration-300")
        div(class="navbar max-w-7xl mx-auto px-4 sm:px-6 lg:px-8")
    block top-content
    
+mbl(class="py-8 md:py-12 lg:py-16")
    .container
        block content

// FILE: app/pages/Home.coffee // TYPE: CoffeeScript // SIZE: 697 characters

document.head.insertAdjacentHTML 'beforeend', '<style type="text/css" file="app/pages/Home.styl">'+stylFns['app/pages/Home.styl']+'</style>'
module.exports =
    name: 'Home'
    render: (new Function '_ctx', '_cache', renderFns['app/pages/Home.pug'])()
    data: ->
        return {
            _: _
            document: null
        }
    beforeMount: ->
        try
            @document = await AppDB.getDocumentByPath('/', AppDB.currentLanguage)
        catch e
            debug.log "Document load error:", e
    components:
        'app-link': require 'app/shared/AppLink'
        'hero-section': require 'app/shared/HeroSection'
        'image-gallery': require 'app/shared/ImageGallery'

// FILE: app/pages/Home.pug // TYPE: Pug Template // SIZE: 845 characters

include ../../pug/base.pug
include ../../pug/bem.pug
extends ../layout.pug

block top-content
    hero-section(:document="document")

block content
    div(class="max-w-4xl mx-auto px-4" v-if="document")
        h1(class="text-3xl font-bold text-contrast-high animate-fade-in-up") {{ document.translations[_.currentLanguage]?.title || document.translations.en.title }}
        p(class="text-xl text-contrast-medium mb-8 animate-fade-in-up") {{ document.translations[_.currentLanguage]?.subtitle || document.translations.en.subtitle }}
        div(class="prose mt-6 animate-fade-in-up" v-html="marked.parse(document.translations[_.currentLanguage]?.content || document.translations.en.content)")
        div(class="mt-12")
            image-gallery(:images="document.translations[_.currentLanguage]?.gallery || document.translations.en.gallery")

// FILE: app/pages/Home.styl // TYPE: Stylus // SIZE: 281 characters

// Используем только утилиты из DesignTokens
.animate-fade-in-up
    animation: fadeInUp var(--transition-slow) ease-out

@keyframes fadeInUp
    from
        opacity: 0
        transform: translateY(var(--transition-px))
    to
        opacity: 1
        transform: translateY(0)

// FILE: app/shared/AppLink.coffee // TYPE: CoffeeScript // SIZE: 633 characters

document.head.insertAdjacentHTML('beforeend','<style type="text/css" file="app/shared/AppLink.styl">'+stylFns['app/shared/AppLink.styl']+'</style>')
module.exports =
    default:
        render: (new Function '_ctx', '_cache', renderFns['app/shared/AppLink.pug'])()
        name: 'AppLink'
        props:
            to:
                type: [String, Object]
                required: true
        computed:
            isExternal: ->
                if typeof @.to == 'string'
                    return @.to.startsWith('http')
                return false
        data: -> 
            return {
                _: _
            }

// FILE: app/shared/AppLink.pug // TYPE: Pug Template // SIZE: 214 characters

include ../../pug/base.pug
include ../../pug/bem.pug

a.app-link(v-if="isExternal" v-bind="$attrs" :href="to" target="_blank" rel="noopener")
    slot
router-link.app-link(v-else v-bind="$attrs" :to="to")
    slot

// FILE: app/shared/AppLink.styl // TYPE: Stylus // SIZE: 1264 characters

@import '../DesignTokens.styl'

// Базовые стили для AppLink компонента
.app-link 
    color: var(--primary-color)
    text-decoration: none
    transition: color var(--transition-fast)

.app-link:hover 
    color: var(--primary-dark)
    text-decoration: underline

.app-link.router-link-active 
    font-weight: var(--font-semibold)
    color: var(--primary-dark)

// Стили для внешних ссылок
.app-link-external::after 
    content: " ↗"
    font-size: 0.875em
    opacity: 0.7

// Стили для кнопко-подобных ссылок
.app-link-button 
    display: inline-flex
    align-items: center
    gap: var(--space-2)
    padding: var(--space-2) var(--space-4)
    background: var(--primary-color)
    color: white
    border-radius: 6px
    font-weight: var(--font-medium)
    transition: all var(--transition-fast)

.app-link-button:hover 
    background: var(--primary-dark)
    transform: translateY(-1px)
    text-decoration: none
    color: white
    box-shadow: var(--shadow-md)

// Стили для иконок в ссылках
.app-link-with-icon 
    display: inline-flex
    align-items: center
    gap: var(--space-2)

.app-link-icon 
    width: 16px
    height: 16px
    transition: transform var(--transition-fast)

.app-link:hover .app-link-icon 
    transform: translateX(2px)

// FILE: app/shared/HeroSection.coffee // TYPE: CoffeeScript // SIZE: 311 characters

document.head.insertAdjacentHTML 'beforeend', '<style type="text/css" file="app/shared/HeroSection.styl">'+stylFns['app/shared/HeroSection.styl']+'</style>'
module.exports =
    name: 'HeroSection'
    props: [ 'document' ]
    render: (new Function '_ctx', '_cache', renderFns['app/shared/HeroSection.pug'])()

// FILE: app/shared/HeroSection.pug // TYPE: Pug Template // SIZE: 574 characters

include ../../pug/base.pug
include ../../pug/bem.pug
section(v-if="document" class="hero-section bg-gradient")
    div(class="max-w-7xl mx-auto px-4 py-24 text-center text-white")
        h1(v-if="document.translations[_.appState.currentLanguage]" class="text-5xl font-bold mb-6") {{ document.translations[_.appState.currentLanguage].title || document.translations.en.title }}
        p(v-if="document.translations[_.appState.currentLanguage]" class="text-xl opacity-90") {{ document.translations[_.appState.currentLanguage].subtitle || document.translations.en.subtitle }}

// FILE: app/shared/HeroSection.styl // TYPE: Stylus // SIZE: 125 characters

.hero-section
    background: var(--gradient-primary)
    color: white
    padding: var(--space-16) 0
    text-align: center

// FILE: app/shared/ImageGallery.coffee // TYPE: CoffeeScript // SIZE: 367 characters

document.head.insertAdjacentHTML 'beforeend', '<style type="text/css" file="app/shared/ImageGallery.styl">'+stylFns['app/shared/ImageGallery.styl']+'</style>'
module.exports =
    name: 'ImageGallery'
    props:
        images:
            type: Array
            default: -> []
    render: (new Function '_ctx', '_cache', renderFns['app/shared/ImageGallery.pug'])()

// FILE: app/shared/ImageGallery.pug // TYPE: Pug Template // SIZE: 414 characters

include ../../pug/base.pug
include ../../pug/bem.pug
div(v-if="images && images.length > 0" class="image-gallery grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6")
    div(v-for="(img, idx) in images" :key="idx" class="overflow-hidden rounded-lg shadow-custom-md bg-surface")
        img(:src="img.src" :alt="img.alt || ''" class="w-full h-auto object-cover transition-transform duration-300 hover:scale-105")

// FILE: app/shared/ImageGallery.styl // TYPE: Stylus // SIZE: 324 characters

.image-gallery
    display: grid
    gap: var(--space-6)
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr))

.image-gallery img
    width: 100%
    height: auto
    object-fit: cover
    border-radius: 8px
    transition: transform var(--transition-normal)

.image-gallery img:hover
    transform: scale(1.05)

// FILE: app/utils/AppDB.coffee // TYPE: CoffeeScript // SIZE: 4684 characters

# FILE: app/utils/AppDB.coffee
class AppDB
    currentLanguage: 'ru'
    currentProject: 's5l.ru'
    designDocVersion: '1.1' # управление версией дизайна

    constructor: ->
        @localDB = new PouchDB('s5l_local')
        @remoteDB = new PouchDB('https://oleg:631074@couchdb.favt.ru.net/s5lru/')
        @syncHandler = null

    init: ->
        try
            await @remoteDB.info()
            debug.log "Remote DB connected"
            await @ensureDesignDocs()
            await @ensureDefaultContent()
            @startSync()
            AppDB.currentLanguage = globalThis._?.appState?.currentLanguage or 'ru'
        catch e
            debug.log "DB init failed:", e

    startSync: ->
        @syncHandler = @localDB.sync(@remoteDB, { live: true, retry: true })
            .on 'error', (err) -> debug.log "Sync error:", err

    ensureDesignDocs: ->
        adminDoc =
            _id: '_design/admin'
            version: @designDocVersion
            views:
                byPath:
                    map: (doc) ->
                        if doc.type == 'page'
                            emit [doc.domain, doc.path], doc
                    .toString()
                byType:
                    map: (doc) ->
                        emit doc.type, doc
                    .toString()

        try
            existing = await @remoteDB.get('_design/admin')
            if existing.version != @designDocVersion
                adminDoc._rev = existing._rev
                await @remoteDB.put(adminDoc)
                debug.log "Design doc updated to v#{@designDocVersion}"
        catch
            await @remoteDB.put(adminDoc)
            debug.log "Design doc created v#{@designDocVersion}"

    ensureDefaultContent: ->
        defaultHome =
            _id: 'page::s5l.ru::/'
            type: 'page'
            domain: 's5l.ru'
            path: '/'
            translations:
                ru:
                    title: "s5l.ru — мультиязычная offline-first платформа"
                    subtitle: "Разрабатывайте быстро, работайте везде"
                    content: '''
# Добро пожаловать на s5l.ru

**s5l.ru** — это платформа для быстрого запуска веб-проектов с поддержкой:
- offline-first через PouchDB/CouchDB
- автоматического переключения языка
- динамической подгрузки контента
- полной WCAG 2.2-совместимости

Все тексты хранятся в базе и легко редактируются через админку.
'''
                    gallery: [
                        { src: "/assets/hero-s5l.svg", alt: "Hero illustration" }
                    ]
                en:
                    title: "s5l.ru — multilingual offline-first platform"
                    subtitle: "Build fast, work anywhere"
                    content: '''
# Welcome to s5l.ru

**s5l.ru** is a platform for rapid web project launches with:
- offline-first via PouchDB/CouchDB
- automatic language switching
- dynamic content loading
- full WCAG 2.2 compliance

All text is stored in the database and editable via admin panel.
'''
                    gallery: [
                        { src: "/assets/hero-s5l.svg", alt: "Hero illustration" }
                    ]
                tj:
                    title: "s5l.ru — платформаи бисёрзабон ва аввал офлайн"
                    subtitle: "Бисёр тез бунёд кунед, дар ҳама ҷо кор кунед"
                    content: '''
# Ба s5l.ru хуш омадед

**s5l.ru** — ин платформа барои оғози тези лоиҳаҳои веб аст бо:
- офлайн-аввал аз рӯи PouchDB/CouchDB
- ивази худкори забон
- боркунии динамикӣ
- мутобиқати пурраи WCAG 2.2

Ҳамаи матнҳо дар база нигоҳ дошта мешаванд ва аз ҷониби панели маъмури озодона таҳрир карда мешаванд.
'''
                    gallery: [
                        { src: "/assets/hero-s5l.svg", alt: "Hero illustration" }
                    ]

        try
            await @remoteDB.get('page::s5l.ru::/')
        catch
            await @remoteDB.put(defaultHome)
            debug.log "Default home page created"

    getDocumentByPath: (path, lang = @currentLanguage) ->
        path = path or '/'
        try
            result = await @localDB.query('admin/byPath', { key: [@currentProject, path], include_docs: true })
            if result.rows.length > 0
                doc = result.rows[0].doc
                return doc
            else
                throw new Error "Document not found"
        catch e
            debug.log "Fallback for path:", path, "lang:", lang
            # fallback to English if not found
            if lang != 'en'
                return await @getDocumentByPath(path, 'en')
            else
                throw new Error "Document not available even in English"

module.exports = AppDB

// FILE: doc.coffee // TYPE: CoffeeScript // SIZE: 1285 characters

module.exports = 
     version: "0.0.1"
     adres: [
        {
          "adr": "https://cdn.tailwindcss.com/3.4.17"
          "eventName": "tailwindReady"
          "obj": "tailwind"
        }
        {
          "adr": "https://unpkg.com/pouchdb/dist/pouchdb.min.js"
          "eventName": "puochReady"
          "obj": "PouchDB"
        }
        {
          "adr": "https://unpkg.com/vue@3/dist/vue.runtime.global.prod.js"
          "eventName": "vueReady"
          "obj": "Vue"
        }
        {
          "adr": "https://unpkg.com/vue-router@4/dist/vue-router.global.js"
          "eventName": "VueRouterReady"
          "obj": "VueRouter"
        }
        {
          "adr": "https://cdn.jsdelivr.net/npm/@vue/compiler-sfc@3.5.22/dist/compiler-sfc.esm-browser.min.js"
          "eventName": "compileTemplateReady"
          "obj": "compileTemplate"
        }
        {
          "adr": "https://cdn.jsdelivr.net/npm/marked@16.4.1/lib/marked.umd.min.js"
          "eventName": "markedReady"
          "obj": "marked"
        }
        {
          "adr": "https://cdn.jsdelivr.net/npm/openlayers@4.6.5/dist/ol.min.js"
          "css": "https://cdn.jsdelivr.net/npm/openlayers@4.6.5/dist/ol.min.css"
          "eventName": "leafletReady"
          "obj": "ol"
        }
    ]

// FILE: lzma.coffee // TYPE: CoffeeScript // SIZE: 1010 characters

globalThis.debug = require('debug.coffee').default
require('headVue.coffee')


document.documentElement.classList.add('dark')

globalThis.initCount = 0
ic = ()->
    if initCount > 5
       window.location.reload()
    else if  not globalThis['appReady']
       initCount++
       #setTimeout ic, 200
ic()
# обязательно подключение глобальных массивов
globalThis.renderFns = require 'pug.json'
globalThis.stylFns   = require 'styl.json'

try
    init =  (event={})->
        debug.dir   globalThis['vueReady']
        debug.log "Init Start"
        if  not globalThis['appReady'] and globalThis['vueReady'] and globalThis['puochReady']
            debug.log 'init start ok'
            try
                require('app/app.coffee')
                globalThis['appReady'] = true
                debug.log "init is ok"
            catch err
                debug.dir err
        else if  not globalThis['appReady']
                debug.log 'pausedEvent appReady'
                setTimeout init, 200
    init()

// FILE: pug/base.pug // TYPE: Pug Template // SIZE: 504 characters

include ./bem.pug


mixin mbh
    - var otherClasses = attributes.class || ''
    +b('hfn')(class="h-max overflow-hidden relative")
        +e('hfn','fon')

mixin mbl
    - var otherClasses = attributes.class || ''
    +b('hfn')(class="h-max overflow-hidden relative")
        +e('hfn','fon')(class="[background:url('https://jahonnamo.s5l.ru/assets/jahonnamo.s5l.ru/bkg00.webp')_0_0_/_cover_no-repeat] bg-[#ffffff] absolute h-full w-full z-[-1]")
                  if block
                        block

// FILE: pug/bem.pug // TYPE: Pug Template // SIZE: 1035 characters

//- bem.pug

//- Базовый миксин для блоков и элементов
mixin b(blockName, elemName)
  - var className = blockName
  - var twClasses = attributes.tw || ''
  - var otherClasses = attributes.class || ''

  if elemName
    - className += '__' + elemName

  //- Обработка модификаторов
  if attributes.mod
    - className += ' ' + blockName + '--' + attributes.mod
  if attributes.mods
    each mod in attributes.mods.split(',')
      - className += ' ' + blockName + '--' + mod.trim()

  //- Собираем финальный класс
  - var finalClass = [className, twClasses, otherClasses].filter(Boolean).join(' ')

  //- Удаляем обработанные атрибуты
  - attributes.tw = null
  - attributes.mod = null
  - attributes.mods = null
  - attributes.class = null

  //- Генерация элемента
  if block
    div(class=finalClass)&attributes(attributes)
      block
  else
    div(class=finalClass)&attributes(attributes)

//- Миксин для элементов (альтернативный синтаксис)
mixin e(blockName, elemName)
  +b(blockName, elemName)&attributes(attributes)
    block