f52402fabe
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>
862 lines
41 KiB
Markdown
862 lines
41 KiB
Markdown
# Лидерра · 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 50–99, Safari 12–14 — добавить @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.*
|