Files
portal/liderra_v8_handoff/docs/DEVELOPER_HANDOFF.md
T
Дмитрий f52402fabe docs(handoff): Sprint 2 Phase C — Google Fonts API v2 + @font-face fallback strategy (audit O-stack-10)
Sprint 2 Phase C (modernization). Закрытие audit O-stack-10:
- DEVELOPER_HANDOFF §4.5 — Google Fonts API v2 + @font-face fallback strategy
  (расширение раздела о Sprint 1 Phase E font-display стратегия §4.4).
- Документировано: когда нужен fallback (расширенная аудитория Chrome 50-99 / Safari 12-14),
  шаблон @font-face блоков, migration path (скачивание + размещение), trade-offs.
- Текущее решение Лидерры: только API v1 + display=swap (целевая аудитория Chrome 100+).
- Решение пересмотреть при GDPR/audit/статистике старых браузеров.

O-stack-06 (FD plugin в ~/.claude/settings.json) — manual user step, выводится в
финальном отчёте Sprint 2 (файл вне git).

O-refactor-07 (CLAUDE.md §0 reorg) — уже фактически реализовано в §9 (полная
история перенесена в docs/CHANGELOG_claude_md.md ранее, оставлены 2 последние
версии в шапке).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 19:39:26 +03:00

862 lines
41 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Лидерра · Developer Handoff
**Версия:** v8 Forest · 2026-05-07
**Назначение:** Всё необходимое разработчику для реализации портала Лидерра без участия дизайнера. Работает поверх HTML-прототипов в `concepts/v8_*.html`.
---
## 0. TL;DR — старт за 5 минут
1. **Стек, который нельзя оспаривать:** Vue 3 + Vuetify 3, светлая тема (тёмная — v2), Yandex Cloud, Yandex 360 SSO для админки, ЮKassa primary platежи. CTO-11.
2. **Палитра v8 Forest:** скопируй [§3 «CSS-переменные»](#3-дизайн-токены-css-variables) в `assets/tokens.css` и подключи глобально.
3. **Шрифты:** Inter (Google Fonts, axis `opsz` 14..32) + JetBrains Mono. Импорт в `<head>` или `@import`. См. [§4](#4-типографика).
4. **Лого:** SVG inline в `assets/logo-mark.svg` (см. [§2 «Бренд»](#2-бренд)).
5. **Все 25 экранов:** живые HTML в [`concepts/`](../concepts/) — открой `v8_dashboard.html` и снимай вёрстку напрямую. WCAG 2.1 AA подтверждено axe-core 4.10.2 на 4 viewports.
6. **Если что-то непонятно — смотри [§14 «FAQ»](#14-faq)** или пиши автору.
---
## 1. Архитектура и стек
### 1.1. Технологии (зафиксировано)
| Слой | Технология | Источник |
|---|---|---|
| Frontend | **Vue 3 + Vuetify 3** | CTO-11, не оспаривается |
| Стейт | Pinia | стандарт Vue 3 |
| Роутер | Vue Router 4 | стандарт |
| Сборка | Vite | стандарт |
| HTTP | Axios или native `fetch` | на выбор разработчика |
| Charts | ApexCharts | бриф §12 — указан напрямую |
| Виртуализация | `vue-virtual-scroller` | бриф §11 для Канбана |
| Drag-and-drop | `@vueuse/integrations` или `vuedraggable` | для Канбана |
| Backend | (out-of-scope) | Yandex Cloud, FastAPI/Node — на выбор |
### 1.2. Структура проекта (рекомендация)
```
src/
├── assets/
│ ├── tokens.css ← из §3 этого документа
│ ├── fonts.css ← @import Inter + JBM
│ └── logo-mark.svg ← из §2.3
├── components/
│ ├── shell/
│ │ ├── AppSidebar.vue
│ │ ├── AppTopbar.vue
│ │ └── BrandMark.vue ← inline SVG
│ ├── ui/
│ │ ├── BaseButton.vue
│ │ ├── BaseChip.vue ← с 14 status variants
│ │ ├── BaseInput.vue
│ │ ├── DataTable.vue
│ │ ├── KpiCard.vue
│ │ ├── SlideOverDrawer.vue
│ │ └── BalanceWidget.vue ← с runway-scale
│ └── domain/
│ ├── DealCard.vue
│ ├── KanbanBoard.vue
│ ├── ActivityLog.vue
│ └── BillingTransactionRow.vue
├── pages/
│ ├── Dashboard.vue
│ ├── DealsList.vue
│ ├── DealCard.vue
│ ├── Kanban.vue
│ ├── Billing.vue
│ ├── Settings.vue
│ ├── Reports.vue
│ ├── Login.vue
│ ├── errors/
│ │ ├── NotFound.vue
│ │ ├── Forbidden.vue
│ │ └── ServerError.vue
│ ├── admin/
│ │ ├── AdminLogin.vue
│ │ ├── TenantsList.vue
│ │ ├── TenantCard.vue
│ │ ├── AdminBilling.vue
│ │ ├── Incidents.vue
│ │ └── SystemSettings.vue
│ └── public/
│ ├── Home.vue
│ ├── Pricing.vue
│ ├── Offer.vue
│ └── Privacy.vue
└── App.vue
```
### 1.3. Окружения
| Окружение | Frontend | API | Что особенного |
|---|---|---|---|
| local | localhost:5173 | localhost:8000 | hot reload, mock data |
| stage | stage.liderra.app | api.stage.liderra.app | seed-данные, без оплат |
| prod | liderra.app + admin.liderra.app | api.liderra.app | полный 2FA, RLS, audit log |
---
## 2. Бренд
### 2.1. Имя
**«Лидерра»** — кириллица. Wordmark с **точкой** в конце: `Лидерра.` Точка покрашена в **bright Teal `#32C8A9`** (`--side-icon-act`) на тёмном sidebar и в **базовый Teal `#0F6E56`** на светлом фоне. Латинская транслитерация: `Liderra` (для домена `liderra.app`, email `support@liderra.app`).
**Tone of voice:** уверенный без помпезности, технический без сухости, премиальный без снобизма. Прямые формулировки про деньги. Никакого «эстетичного решения», «бесшовного опыта», «инновационного подхода».
### 2.2. Лого — `L-Square с Teal-точкой`
```
[ белый rounded-square 22×22 ]
└─ внутри: L-stroke 4.5px (тёмный) + Teal-точка r=3.5 в конце
```
### 2.3. SVG (inline, копировать в `BrandMark.vue`)
```html
<!-- Версия для тёмного sidebar (на белом квадрате) -->
<svg viewBox="0 0 48 48" width="22" height="22">
<rect x="2" y="2" width="44" height="44" rx="10" fill="#FFFFFF"/>
<path d="M16 14 L16 34 L32 34"
stroke="#012019" stroke-width="4.5"
stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<circle cx="32" cy="34" r="3.5" fill="#0F6E56"/>
</svg>
```
```html
<!-- Версия для светлого фона (тёмный квадрат) -->
<svg viewBox="0 0 48 48" width="22" height="22">
<rect x="2" y="2" width="44" height="44" rx="10" fill="#0A1319"/>
<path d="M16 14 L16 34 L32 34"
stroke="#FFFFFF" stroke-width="4.5"
stroke-linecap="round" stroke-linejoin="round" fill="none"/>
<circle cx="32" cy="34" r="3.5" fill="#32C8A9"/>
</svg>
```
```html
<!-- Favicon — без рамки и точки (упрощённый) -->
<svg viewBox="0 0 48 48" width="32" height="32">
<rect width="48" height="48" rx="8" fill="#0F6E56"/>
<path d="M16 14 L16 34 L32 34"
stroke="#FFFFFF" stroke-width="5.5"
stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>
```
### 2.4. Что нельзя
- Менять пропорции, цвета, обводку лого, добавлять тени/градиенты
- Использовать «Лидпоток» (старое имя — deprecated)
- Использовать lowercase «лидерра.» — wordmark капитализирован
- Использовать слово «премиум» в маркетинге без обоснования (мы — про прозрачность и контроль, не про роскошь)
---
## 3. Дизайн-токены (CSS variables)
Все токены — реальные значения из v8 Forest. Скопировать в `assets/tokens.css`, подключить в `<head>` глобально. Все WCAG-контрасты подтверждены через axe-core 4.10.2 на 25 макетах.
```css
:root {
/* === ПОВЕРХНОСТИ (Forest light, hue ≈ 80, warm ivory) === */
--bg: #F6F3EC; /* Page background */
--surface: #FFFDFA; /* Карточки, поднятые поверхности */
--sunken: #F0EDE4; /* Чуть глубже bg — для table thead, sunken-areas */
--hairline: #D9D5CD; /* 1px UI-border (формальный) */
--hairline-soft: #E8E3D6; /* Внутренние дивайдеры строк */
/* === ТЕКСТ (на ivory bg, проверено WCAG AA) === */
--ink: #081319; /* 16.96:1 — primary text */
--ink-2: #343C41; /* 10.34:1 — secondary */
--ink-3: #66635C; /* 5.42:1 — muted (≥4.5:1) */
--ink-disabled: #92907B; /* 3.48:1 — для UI-компонентов / disabled fields */
/* === БРЕНД === */
--accent: #0F6E56; /* Teal — primary brand · 5.94:1 на bg */
--accent-tint: #E1EEEA; /* Tint для selection / chip-tinted */
--accent-deep: #084635; /* Hover/active state */
/* === SIDEBAR (тёмный teal-noir) === */
--side-bg: #012019;
--side-text: #B1C2BD; /* 9.23:1 на side-bg */
--side-text-2: #7A8C87; /* nav-eyebrow muted */
--side-active: #13382F; /* Selected item fill */
--side-icon: #5C7A72; /* Idle icon */
--side-icon-act: #32C8A9; /* Bright Teal — active icon · 8.15:1 */
--side-hover: #0A2A22;
--side-border: #1A3A30;
/* === 14 СТАТУСОВ ВОРОНКИ (OKLCH, ΔE2000 ≥ 10.57) === */
--st-new-tint: #FFD8CF; --st-new-solid: #B94837; /* Новая */
--st-work-tint: #FFDBC4; --st-work-solid: #B35100; /* В работе */
--st-call-tint: #F6E2BC; --st-call-solid: #9A6700; /* Дозвон */
--st-nocall-tint: #E9E8BD; --st-nocall-solid: #7E7500; /* Не дозвон. */
--st-neg-tint: #D9EDC6; --st-neg-solid: #538200; /* Переговоры */
--st-quote-tint: #C7F0D7; --st-quote-solid: #008A4D; /* КП отправлено */
--st-think-tint: #BCF1E9; --st-think-solid: #008C7E; /* Думает */
--st-wait-tint: #BAF0F6; --st-wait-solid: #00889B; /* Ждёт оплату */
--st-paid-tint: #C0ECFF; --st-paid-solid: #007EB8; /* Оплачено */
--st-refund-tint: #D1E5FF; --st-refund-solid: #406DC8; /* Возврат */
--st-fail-tint: #E1E0FF; --st-fail-solid: #6C60C4; /* Отказ */
--st-dup-tint: #F2DAFF; --st-dup-solid: #9052AE; /* Дубликат */
--st-spam-tint: #FFD7EE; --st-spam-solid: #AA4788; /* Спам */
--st-arch-tint: #FFD6DD; --st-arch-solid: #B7445F; /* Архив */
/* === РАДИУСЫ === */
--r-xs: 4px; /* мелкие пилы (chip count, kbd, mini-badges) */
--r-sm: 6px; /* кнопки, инпуты, нав-айтемы */
--r-md: 10px; /* карточки, секции, табли */
--r-lg: 14px; /* hero-карточки, лендинг-блоки */
/* radius для аватара/чипа — 50% или 100px */
/* === ШРИФТЫ === */
--font-ui: 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Consolas, monospace;
}
```
### 3.1. Тени — НЕ используем
**v8 Forest стилистически отказывается от теней.** Глубина обеспечивается **только 1px hairline borders**. Исключение — slide-over drawer (нужен лёгкий left-edge shadow для иерархии слоёв):
```css
.drawer { box-shadow: -16px 0 40px rgba(10,19,25,0.10); }
```
Если разработчик сомневается — **не добавлять `box-shadow`**, использовать `border: 1px solid var(--hairline)`.
### 3.2. Spacing scale (4px-base)
| Token | Value | Применение |
|---|---|---|
| 4px | xs | плотные внутренние gap |
| 8px | sm | gap между кнопками в группе |
| 12px | md | padding внутри карточек |
| 16px | lg | gap между элементами секции |
| 20-24px | xl | padding section, gap между секциями |
| 32px | 2xl | page padding desktop |
| 48-64px | 3xl | hero padding |
---
## 4. Типографика
### 4.1. Шрифты (Google Fonts)
```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400;14..32,500;14..32,600;14..32,700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
```
**Inter с осью `opsz`** — это Inter 4.0+, один файл с двумя «режимами»:
- `font-variation-settings: 'opsz' 14` — UI-режим (мелкий текст)
- `font-variation-settings: 'opsz' 28` — display-режим (заголовки)
Подключается через CSS variable:
```css
body {
font-family: var(--font-ui);
font-feature-settings: 'cv11', 'ss01', 'ss03'; /* slashed-zero, single-storey a */
font-variation-settings: 'opsz' 14;
-webkit-font-smoothing: antialiased;
}
.page-title {
font-size: 28px;
font-weight: 600;
font-variation-settings: 'opsz' 28; /* visual switch to display */
letter-spacing: -0.02em;
}
```
### 4.2. Шкала размеров
| Применение | font-size | weight | opsz | letter-spacing | line-height |
|---|---|---|---|---|---|
| Hero h1 (лендинг) | 56px | 600 | 32 | -0.03em | 1.05 |
| Page title | 28px | 600 | 28 | -0.02em | 1.1 |
| Section h2 | 22px | 600 | 24 | -0.018em | 1.15 |
| Card title | 16px | 600 | 18 | -0.012em | 1.3 |
| Body | 13-14px | 400 | 14 | -0.005em | 1.5 |
| Eyebrow / label | 11.5px | 500 | 14 | letter-spacing 0.005em | 1.4 |
| Mono numeric | 12.5px | 500 | — | -0.005em | 1.4 |
| Caption | 11px | 500 | 14 | 0.005em | 1.4 |
### 4.3. Numeric (числа = деньги)
**Везде где число — `var(--font-mono)` + `font-feature-settings: 'tnum'`.** Это даёт tabular figures (одинаковая ширина цифр), без чего ровный right-align в таблицах ломается.
```css
.price, .deal-price, .balance-amount, .kpi-value, .tx-amount, .timer {
font-family: var(--font-mono);
font-feature-settings: 'tnum';
}
```
### 4.4. Стратегия загрузки шрифтов (audit O-stack-09)
Используется `<link rel="stylesheet">` с Google Fonts API v1 + параметр `&display=swap` (см. §4.1).
- **`&display=swap`:** браузер сразу рендерит fallback-шрифт (`system-ui`), переключается на загруженный Inter/JetBrains Mono когда тот доступен. Стратегия FOUT (Flash of Unstyled Text), но без невидимого текста — лучше для UX, чем `&display=block` или дефолтный `&display=auto`.
- **WOFF2 формат:** Google Fonts отдаёт WOFF2 по умолчанию для современных браузеров (Chrome 36+, Firefox 39+, Safari 12+). Сжатие лучше WOFF на 20–30%.
- **Preconnect:** для ускорения первого byte рекомендуется добавить в `<head>`:
```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
```
Это сокращает TTFB на медленных сетях (~50–200 мс). В §4.1 этот блок уже включён в snippet подключения.
- **Совместимость:** для целевой аудитории Chrome 100+, Safari 15+, Firefox 95+ дополнительный fallback не нужен. Для расширенной аудитории — добавить `@font-face` с WOFF (legacy).
### 4.5. Google Fonts API v2 + @font-face fallback (audit O-stack-10)
Текущий `<link rel="stylesheet">` использует Google Fonts API v1 (`https://fonts.googleapis.com/css2?family=...`). Для расширенной совместимости с устаревшими браузерами (поддержка ниже Chrome 100 / Safari 15 / Firefox 95) можно добавить @font-face fallback:
#### Когда использовать API v2
- **Целевая аудитория** Chrome 100+, Safari 15+, Firefox 95+ (default для Лидерры) — оставить только API v1, fallback не нужен.
- **Расширенная аудитория** включая Chrome 5099, Safari 1214 — добавить @font-face WOFF fallback.
#### Шаблон @font-face fallback (для расширенной совместимости)
```css
@font-face {
font-family: 'Inter Fallback';
src: url('/fonts/inter-variable.woff2') format('woff2-variations'),
url('/fonts/inter-variable.woff') format('woff');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'JetBrains Mono Fallback';
src: url('/fonts/jetbrains-mono.woff2') format('woff2'),
url('/fonts/jetbrains-mono.woff') format('woff');
font-weight: 400 700;
font-style: normal;
font-display: swap;
}
```
#### Migration path
1. Скачать `Inter` и `JetBrains Mono` с [google-webfonts-helper.herokuapp.com](https://google-webfonts-helper.herokuapp.com) или [github.com/rsms/inter/releases](https://github.com/rsms/inter/releases) и [github.com/JetBrains/JetBrainsMono/releases](https://github.com/JetBrains/JetBrainsMono/releases).
2. Поместить в `public/fonts/` (или `app/public/fonts/` для Laravel).
3. В `app/resources/css/app.css` (или Vuetify global stylesheet) добавить @font-face блоки.
4. В Vuetify config `defaults` — добавить fallback chain: `font-family: 'Inter', 'Inter Fallback', system-ui, sans-serif;`.
#### Trade-offs
- **+ Self-hosted шрифты** — нет зависимости от Google CDN, GDPR-friendly (нет cookies на fonts.googleapis.com).
- **+ Гарантия совместимости** на 10+ лет в браузерах.
- **− Размер репозитория** — +500 KB на файлы шрифтов (Inter Variable ~250 KB + JetBrains Mono ~250 KB в WOFF2).
- ** Ручной upgrade** — при выходе новых версий Inter/JetBrains Mono нужно скачивать вручную (vs автообновление через Google Fonts).
#### Решение для Лидерры (текущее, 09.05.2026)
Используется только Google Fonts API v1 + `&display=swap` (см. §4.4). @font-face fallback **не применяется** — целевая аудитория покрывает 99%+ современных браузеров. Решение пересмотреть, если: появится требование GDPR-encryption, аудит безопасности обнаружит зависимость от внешнего CDN, или статистика браузеров покажет долю старых ≥5%.
---
## 5. Компонентная библиотека
### 5.1. AppShell — общий каркас (sidebar + topbar)
Sidebar 232px sticky слева на тёмном `--side-bg`, topbar 48px sticky сверху, контент в центре. На viewport ≤ 1100px sidebar сжимается до 56px (icon-only). На ≤ 768px sidebar скрывается.
Живой пример: открой [`v8_dashboard.html`](../concepts/v8_dashboard.html), изучи `.app`, `.side`, `.main`, `.topbar`.
### 5.2. Sidebar nav-item
```html
<a class="nav-item" :class="{ active: isActive }" :aria-current="isActive ? 'page' : null" :href="route">
<svg class="nav-icon" ... aria-hidden="true"><!-- 15px stroke 1.6 --></svg>
<span class="nav-text">{{ label }}</span>
<span v-if="count" class="nav-count">{{ count }}</span>
</a>
```
CSS правила см. в `v8_dashboard.html` `.nav-item` block. Selected state = solid `--side-active` fill, no border, no glow. Active icon — `--side-icon-act` (bright Teal).
### 5.3. Button
```html
<button type="button" class="btn">Базовая</button>
<button type="button" class="btn btn-primary">Primary (Teal)</button>
<button type="button" class="btn btn-danger">Danger</button>
```
Высоты:
- 30px — icon-only
- 32px — компактные / topbar / в фильтрах
- 34-36px — inline в формах
- 38-42px — primary CTA
Variants:
- `.btn` — surface + hairline border
- `.btn-primary` — accent fill, white text
- `.btn-danger` — `--st-new-solid` text + hover `#FFE7E2` bg
### 5.4. Status Chip — 14 вариантов
Два режима:
**1. Plain (dot + text, без фона)** — для таблиц, плотных строк:
```html
<span class="chip chip-paid">
<span class="dot" aria-hidden="true"></span>Оплачено
</span>
```
**2. Tinted (с фоном)** — для emphasis (drawer current status, dashboard summary):
```html
<span class="chip chip-tinted chip-paid">
<span class="dot" aria-hidden="true"></span>Оплачено
</span>
```
Все 14 классов: `chip-new`, `chip-work`, `chip-call`, `chip-nocall`, `chip-neg`, `chip-quote`, `chip-think`, `chip-wait`, `chip-paid`, `chip-refund`, `chip-fail`, `chip-dup`, `chip-spam`, `chip-arch`. Цвета — в [§3 токены](#3-дизайн-токены-css-variables).
### 5.5. Avatar (concentric ring)
```html
<span class="avatar" aria-hidden="true">
ИП <!-- 2 letter initials -->
</span>
```
```css
.avatar {
width: 26px; height: 26px;
border-radius: 50%;
background: var(--ink); color: #fff; /* dark variant; см. v8_* для tinted */
display: inline-flex; align-items: center; justify-content: center;
font-size: 9.5px; font-weight: 600;
position: relative;
}
.avatar::after {
content: ''; position: absolute; inset: 0;
border-radius: 50%;
border: 1px solid rgba(10,19,25,0.10); /* concentric ring trick */
pointer-events: none;
}
```
### 5.6. Table
Hairline-only, no shadows. Sticky `thead`. Hover row → `--sunken`. Selected row → solid `--ink` fill, white text.
См. [`v8_deals.html`](../concepts/v8_deals.html) `.tbl`. На mobile — таблица превращается в карточки.
### 5.7. Drawer (slide-over)
460px справа, `--surface` bg, hairline left border + lazy left-edge shadow. Scrim — `rgba(10,19,25,0.18)` + `backdrop-filter: blur(2px)`.
См. [`v8_deals.html`](../concepts/v8_deals.html) `.drawer`.
### 5.8. KPI card
Hairline border, no shadow. Number в JBM mono с tnum. Trend = single Teal up-arrow на положительной дельте, **`--st-new-solid` red** на отрицательной. Никаких icon-чипов.
См. [`v8_dashboard.html`](../concepts/v8_dashboard.html) `.kpi`.
### 5.9. Balance widget с runway-scale
Наследник KPI. Те же hairlines + дополнительная Teal-рамка через `::before`. Внутри — `runway-bar` (7 сегментов hairline-tick'ов, заполненных под уровень дней).
См. [`v8_dashboard.html`](../concepts/v8_dashboard.html) `.balance` + `.runway`.
### 5.10. Timeline / Activity Log
Vertical 1px line + цветные точки по типу события:
- `create` — Teal `--accent`
- `status-change` — `--st-call-solid` (оранжевый)
- `comment` — `--ink` (чёрный)
- `call` — `--st-quote-solid` (зелёный)
- `tag` — `--st-think-solid` (бирюзовый)
- `assign` — `--st-paid-solid` (синий)
- `next/dashed` — `--ink-disabled` с `border-style: dashed`
Описание + старое→новое значение через `<span class="from">old</span> → <span class="to">new</span>` с line-through на `from` и mono font на обоих.
См. [`v8_deal_card.html`](../concepts/v8_deal_card.html) `.activity`.
---
## 6. Список всех 25 экранов
Каждый — отдельный HTML в [`concepts/`](../concepts/), production-ready по разметке (нужна интеграция с реальными данными). Все прошли axe-core 4.10.2 = **0 violations** на 1680/1440/768/375 (где применимо). Дата прогона: фиксируется при сборке handoff'а; отчёт axe требует подтверждения после фикса pa11y.config (audit P1-12, Sprint 1 Phase C `cc6e1cb` обновил пути на `liderra_v8_handoff/concepts/v8_*.html`). Перепрогон: `npm run a11y` из корня репозитория.
### 6.1. Кабинет тенанта (8 экранов)
| ID | URL | HTML | Что внутри |
|---|---|---|---|
| 01 | `/login`, `/register`, `/2fa`, `/forgot-password` | [v8_login.html](../concepts/v8_login.html) | 5 состояний: login/register с zxcvbn-индикатором/2FA с 6-cell кодом/forgot/recovery с 8 одноразовыми кодами |
| 02 | `/dashboard` | [v8_dashboard.html](../concepts/v8_dashboard.html) | KPI strip + Hero balance + Activity chart + Funnel-bar + Recent deals + System health + Quick actions |
| 03 | `/deals` | [v8_deals.html](../concepts/v8_deals.html) | Таблица 14 строк × 14 status-chips, фильтры (search + selects + density toggle), drawer открыт статически |
| 04 | `/deals/{id}` | [v8_deal_card.html](../concepts/v8_deal_card.html) | Hero + параметры + комментарий с 8 шаблонами + напоминание + footer-actions; aside: менеджер + метрики + history |
| 05 | `/deals/kanban` | [v8_kanban.html](../concepts/v8_kanban.html) | 14 колонок DnD (262px каждая), карточки с иконками 🔥/⏰/✓ |
| 06 | `/billing` | [v8_billing.html](../concepts/v8_billing.html) | Кошелёк ₽ + ГЦК + тариф; pending-banner; история транзакций; счета и УПД (1С 8.3 XML) |
| 07 | `/settings` | [v8_settings.html](../concepts/v8_settings.html) | 8 вкладок (Профиль/Безопасность/Проекты/Команда/API/Интеграции/Тихие часы/Уведомления); 3 раскрыты |
| 08 | `/reports` | [v8_reports.html](../concepts/v8_reports.html) | Форма запроса + список jobs (done/running с progressbar/queued/failed + retry); квота 2/3 |
### 6.2. Экраны ошибок (3)
Все в одном файле с review-tabs: [v8_errors.html](../concepts/v8_errors.html)
| ID | URL | Особенности |
|---|---|---|
| 09 | 404 | Декоративный SVG с маркером, кнопки «На дашборд» / «Назад» |
| 10 | 403 | Replace-key SVG, `REQ-id` для копирования, mailto support |
| 11 | 500/503 | Status-list (API/Telegram/YooKassa OK/деградация), `INC-id`, ссылка на status page |
### 6.3. Админка SaaS (6)
Все в одном файле: [v8_admin.html](../concepts/v8_admin.html). Отдельный домен `admin.liderra.app`. Тёмная плашка `ADMIN` рядом с лого.
| ID | URL | Что показано |
|---|---|---|
| 12 | `/admin/login` | SSO Yandex 360 primary + локальный 2FA fallback |
| 13 | `/admin/tenants` | Список 142 тенанта, фильтры, статус-чипы (active/trial/overdue/suspended) |
| 14 | `/admin/tenants/{id}` | Карточка тенанта: профиль, баланс, действия (impersonation, корректировка, удаление 152-ФЗ), журнал |
| 15 | Impersonation-режим | Красный sticky banner + специальная аватарка `Админ → ИП` + крест выхода |
| 16 | `/admin/incidents` | Журнал HIGH/MED/LOW, read-only для compliance |
| 17 | `/admin/system` | 6 sys-cards: system_settings / tariff_plans / legal_entities / payment_gateways / webhook_log / auth_log |
### 6.4. Лендинг и юр. (4)
Все в одном файле: [v8_landing.html](../concepts/v8_landing.html)
| ID | URL | Особенности |
|---|---|---|
| 18 | `/` | Hero с 3 hi-cards справа + 6 features + 4 tariffs + FAQ + footer (содержимое — Lorem ipsum) |
| 19 | `/pricing` | Только tariffs (4 карточки: Start/Basic/Команда POPULAR/Enterprise) |
| 20 | `/legal/offer` | TOC + 8 разделов оферты (шаблон Прил. Ж) |
| 21 | `/legal/privacy` | TOC + 10 разделов 152-ФЗ Politики |
### 6.5. Бренд-документация
| ID | Файл | Что |
|---|---|---|
| BR-1 | [v8_brand.html](../concepts/v8_brand.html) | 10 имён + 5 знаков + 3 favourite combo. Архив выбора. |
| BR-2 | [v8_palette_options.html](../concepts/v8_palette_options.html) | 5 палитр на выбор (А Graphite/Б Forest/В Slate-Blue/Г Champagne/Д Inverted Hero). Архив. |
---
## 7. Соглашения по доступности (WCAG 2.1 AA)
### 7.1. Что обязательно
- **Все интерактивные** (`<a>`, `<button>`, `<input>`, `<select>`, `<textarea>`, `[tabindex]`) имеют `:focus-visible` с outline:
```css
a:focus-visible, button:focus-visible, input:focus-visible, [tabindex]:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--r-sm);
}
```
- **Все SVG-иконки** — `aria-hidden="true"` (декоративные)
- **Все icon-only кнопки** — `aria-label="..."`
- **Все form-inputs** — `<label for="...">` или `aria-label`
- **Selection state** — solid fill (см. row.selected на dark `--ink`), не tint
- **`role="tablist"`** требует все children = `<button role="tab">` (не просто `<button>`)
- **`prefers-reduced-motion`** — выключает анимации scoped через class `.has-motion`, не через `*`:
```css
@media (prefers-reduced-motion: reduce) {
.has-motion { animation: none !important; transition: none !important; }
}
```
- **Skip link** на каждой странице для клавиатурной навигации:
```html
<a href="#main" class="skip-link">К контенту</a>
```
### 7.2. Контраст — проверка перед мержем
Перед каждым PR прогнать через [axe DevTools](https://www.deque.com/axe/devtools/) или Playwright + axe-core 4.10.2. **0 violations — обязательное условие** для merge. Дата прогона: фиксируется при сборке handoff'а; отчёт axe требует подтверждения после фикса pa11y.config (audit P1-12, Sprint 1 Phase C `cc6e1cb` обновил пути на `liderra_v8_handoff/concepts/v8_*.html`). Перепрогон: `npm run a11y` из корня репозитория.
### 7.3. Известные «проблемные» комбинации
Эти пары давали contrast violations в процессе и были исправлены — повторять НЕ нужно:
| Не делать | Контраст | Решение |
|---|---|---|
| `--ink-disabled` на `--surface` для текста ≥9px | 3.18:1 | `--ink-3` (5.42:1) |
| `--st-quote-solid` (#008A4D) на белом для 11.5px text | 4.36:1 | #006A3B + bold (4.5:1+) |
| Текст 10px на solid `--st-neg-solid` / `--st-quote-solid` / `--st-paid-solid` | 4.14-4.33:1 | Убрать текст из solid-сегмента, перенести в подпись |
| `aria-hidden="true"` на `<aside>` с фокусируемыми ссылками | aria-hidden-focus | Заменить на `aria-label="..."` |
| `<div>` с `aria-label` без role | aria-prohibited-attr | Добавить `role="progressbar"` / `role="img"` |
| 5 tabs в `display:flex` без `flex-wrap` на mobile | Overflow → axe не видит true bg | Добавить `flex-wrap: wrap` |
---
## 8. Vue 3 + Vuetify 3 — практические подсказки
### 8.1. Где брать Vuetify-компонент vs кастомный
| Vuetify-готовый | Полу-кастомный | Полностью кастомный |
|---|---|---|
| `v-text-field`, `v-select`, `v-checkbox`, `v-snackbar`, `v-dialog`, `v-menu` | `v-data-table-server` (с правкой стилей под наши токены) | StatusChip с 14 вариантами, BalanceWidget с runway, Timeline, Sidebar nav |
### 8.2. Vuetify тема через токены
В `vuetify.options.ts`:
```ts
import 'vuetify/styles';
export default {
theme: {
defaultTheme: 'liderra',
themes: {
liderra: {
dark: false,
colors: {
primary: '#0F6E56',
'on-primary': '#FFFDFA',
surface: '#FFFDFA',
background: '#F6F3EC',
error: '#B94837',
warning: '#9A6700',
success: '#008A4D',
info: '#007EB8',
},
},
},
},
};
```
Дополнительно в `assets/tokens.css` подключить ВСЕ переменные из [§3](#3-дизайн-токены-css-variables) — Vuetify не покрывает status-палитру, sidebar tokens и др.
### 8.3. Шаблон страницы
```vue
<template>
<AppShell>
<template #topbar-crumb>
<span>Рабочая область</span>
<ChevronIcon />
<strong>{{ pageTitle }}</strong>
</template>
<main id="main" class="content">
<PageHeader :title="pageTitle" :stats="pageStats" />
<!-- секции -->
</main>
</AppShell>
</template>
```
`AppShell` инкапсулирует `.app + .side + .topbar + .main`. Детальная реализация — отдельный issue.
---
## 9. Известные правила проекта (нельзя оспаривать)
Из брифа `01_Scope_i_ekrany.md` §6:
- **Тёмная тема** — отложена на v2 (CSS-vars для будущей dark theme заложены, но макетов нет)
- **2FA** — на всех тарифах, включая бесплатный
- **Минимум 100 ₽** пополнения, округление **вниз** при ₽→лиды
- **Pending-платеж** — нет кнопки «Отменить» (CTO-11), самовосстановление 30 минут
- **Click-wrap при регистрации** — 3 чекбокса (оферта / ПДн / маркетинг-опционально)
- **Yandex Cloud + Yandex 360 SSO** для админки
- **JivoSite** для helpdesk-чата (Биз-5)
- **Webhook outbound** — primary integration option, REST API на втором месте
- **amoCRM** — спринт 14-15, Bitrix24/RetailCRM — Post-MVP
---
## 10. Что точно НЕ делать на MVP
- Темная тема UI
- Wizard «OSINT-рекомендации источников» (Биз-15)
- Телефонная интеграция (UI VoIP) — Биз-12
- Промокоды и реферальная программа — Б-6
- Tab «Удалённые проекты» с покупкой обратно — Биз-14
- Bitrix24 / RetailCRM коннекторы — только amoCRM
- Кнопка «Отменить» pending-платежа — CTO-11
- Кнопка «Отменить подписку» — уход через смену тарифа на Start (Биз-2)
---
## 11. API-контракт (намёки для backend)
Это не полный контракт (он out-of-scope этого документа), но есть конкретные требования из дизайна:
### 11.1. WebSocket / SSE для real-time
Дашборд показывает «LIVE» индикатор на balance widget — это требует push-канала. Минимум: `lead.created`, `payment.completed`, `balance.updated`.
### 11.2. Activity Log
Каждое изменение поля карточки сделки — запись в `deal_activity_log` с полями:
```typescript
{
id: string,
deal_id: string,
actor_id: string | null, // менеджер или null = system
type: 'create'|'status-change'|'comment'|'call'|'tag'|'assign'|'reminder',
timestamp: ISO8601,
payload: {
field?: string, // 'status' | 'manager_id' | ...
from?: any, // old value
to?: any, // new value
text?: string, // for comments
duration_sec?: number, // for calls
}
}
```
### 11.3. Saas Admin Audit Log
Согласно бриф §23.10 + дизайн админки — каждое действие админа пишется в `saas_admin_audit_log`. Impersonation-режим имеет **обязательное поле `reason`** при входе.
### 11.4. RLS для multi-tenant
Каждый запрос API проверяет `tenant_id` через RLS на стороне БД. Bypass attempt = инцидент HIGH severity (см. админка/Incidents).
### 11.5. Webhook signatures
Outbound webhook подписывается **HMAC SHA-256** в заголовке `X-Liderra-Sign`. Документация для клиентского интегратора — отдельный документ.
### 11.6. Status проекта
```
GET /status → { api: 'OK' | 'degraded' | 'down', integrations: [...] }
```
Используется на 500-странице.
---
## 12. Тестирование
### 12.1. axe-core 4.10.2 — обязательно
Прогон через CI на каждом PR. Pseudo-pipeline:
```bash
playwright test --headed
# в playwright тесте:
await page.evaluate(() => axe.run(document, { runOnly: ['wcag2a','wcag2aa','wcag21aa'] }))
# violations.length должен быть 0
```
### 12.2. Viewport coverage
Все экраны проверять как минимум на 4 viewports:
- **1680×1050** — desktop large
- **1440×900** — desktop стандарт
- **768×1024** — tablet
- **375×812** — mobile (iPhone X reference)
### 12.3. Reduced motion
Включить `prefers-reduced-motion: reduce` в DevTools → проверить, что декоративные анимации (pulse, slide) выключены, но focus rings и hover transitions работают.
### 12.4. Keyboard-only навигация
Tab через всю страницу — все интерактивные элементы должны иметь видимый focus ring.
---
## 13. Полезные команды для разработчика
### 13.1. Локальный preview макетов
```bash
cd дизайн
python -m http.server 8765
# открыть http://localhost:8765/concepts/v8_dashboard.html
```
### 13.2. Прогон axe для проверки
```javascript
// В DevTools console на любой странице:
const s = document.createElement('script');
s.src = 'https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.10.2/axe.min.js';
document.head.appendChild(s);
s.onload = async () => {
const r = await axe.run(document, { runOnly: ['wcag2a','wcag2aa','wcag21aa'] });
console.log('Violations:', r.violations.length, r.violations);
console.log('Passes:', r.passes.length);
};
```
### 13.3. OKLCH палитра — ещё пары
Если нужно добавить новый статус — использовать [`palette_14.py`](../palette_14.py) с другим `start_hue` и repulsion-итерацией. **min ΔE2000 ≥ 10** обязательно.
```bash
cd дизайн
PYTHONIOENCODING=utf-8 python palette_14.py
```
---
## 14. FAQ
**Q: Можно ли использовать Tailwind вместо чистого CSS?**
A: Можно, но токены из [§3](#3-дизайн-токены-css-variables) должны попасть в `tailwind.config.ts`. Проще импортировать tokens.css глобально и использовать классы из v8_*.html напрямую (они стабильные).
**Q: Что делать, если нужен новый статус (15-й)?**
A: Перезапустить `palette_14.py` с N=15, ΔE2000 пересчитается. Все 14 существующих останутся, новый встанет в свободное место hue-кругa. Дизайнер должен утвердить (новый статус = новый workflow в БД).
**Q: Можно ли поменять Teal `#0F6E56` на синий?**
A: НЕТ без явного OK заказчика — это брендовый цвет (зафиксирован Диз-2). Если нужно — сначала переоткрыть бренд-решение.
**Q: ApexCharts не дружит с opsz axis Inter?**
A: Использовать font-family fallback: ApexCharts читает `font-family: 'Inter'` без opsz, выглядит чуть-чуть толще, но не критично.
**Q: Как повторить OKLCH-расчёт?**
A: Python 3.12+ с `pip install colour-science numpy`. Скрипты в `palette_*.py` в корне `дизайн/`.
**Q: Где живёт реальный source of truth для брендбука?**
A: Этот файл (`docs/DEVELOPER_HANDOFF.md`). Файл `дизайн/02_Brand_i_A11y.md` устарел (там про v1.1 «Лидпоток»).
---
## 15. Контакты
| Роль | Контакт |
|---|---|
| Дизайнер | `kpd9363@gmail.com` (Платон) |
| Поддержка дизайна | через данный handoff + `concepts/v8_*.html` |
| Заказчик | Дмитрий |
---
## 16. Изменения
| Версия | Дата | Что изменилось |
|---|---|---|
| v8 Forest | 2026-05-07 | Полная переработка: Лидерра, Forest палитра, L-Square лого, 25 макетов |
| v7 cool-tech | 2026-05-07 | Деприкейтнут (cool-grey слишком белый, sidebar сливался) |
| v4-v6 | до 2026-05-07 | Архивы итераций — не использовать |
---
*Конец `DEVELOPER_HANDOFF.md` v8 Forest.*