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>
41 KiB
Лидерра · Developer Handoff
Версия: v8 Forest · 2026-05-07
Назначение: Всё необходимое разработчику для реализации портала Лидерра без участия дизайнера. Работает поверх HTML-прототипов в concepts/v8_*.html.
0. TL;DR — старт за 5 минут
- Стек, который нельзя оспаривать: Vue 3 + Vuetify 3, светлая тема (тёмная — v2), Yandex Cloud, Yandex 360 SSO для админки, ЮKassa primary platежи. CTO-11.
- Палитра v8 Forest: скопируй §3 «CSS-переменные» в
assets/tokens.cssи подключи глобально. - Шрифты: Inter (Google Fonts, axis
opsz14..32) + JetBrains Mono. Импорт в<head>или@import. См. §4. - Лого: SVG inline в
assets/logo-mark.svg(см. §2 «Бренд»). - Все 25 экранов: живые HTML в
concepts/— откройv8_dashboard.htmlи снимай вёрстку напрямую. WCAG 2.1 AA подтверждено axe-core 4.10.2 на 4 viewports. - Если что-то непонятно — смотри §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)
<!-- Версия для тёмного 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>
<!-- Версия для светлого фона (тёмный квадрат) -->
<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>
<!-- 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 макетах.
: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 для иерархии слоёв):
.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)
<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:
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 в таблицах ломается.
.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>:<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 (для расширенной совместимости)
@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
- Скачать
InterиJetBrains Monoс google-webfonts-helper.herokuapp.com или github.com/rsms/inter/releases и github.com/JetBrains/JetBrainsMono/releases. - Поместить в
public/fonts/(илиapp/public/fonts/для Laravel). - В
app/resources/css/app.css(или Vuetify global stylesheet) добавить @font-face блоки. - В 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, изучи .app, .side, .main, .topbar.
5.2. Sidebar nav-item
<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
<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-solidtext + hover#FFE7E2bg
5.4. Status Chip — 14 вариантов
Два режима:
1. Plain (dot + text, без фона) — для таблиц, плотных строк:
<span class="chip chip-paid">
<span class="dot" aria-hidden="true"></span>Оплачено
</span>
2. Tinted (с фоном) — для emphasis (drawer current status, dashboard summary):
<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 токены.
5.5. Avatar (concentric ring)
<span class="avatar" aria-hidden="true">
ИП <!-- 2 letter initials -->
</span>
.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 .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 .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 .kpi.
5.9. Balance widget с runway-scale
Наследник KPI. Те же hairlines + дополнительная Teal-рамка через ::before. Внутри — runway-bar (7 сегментов hairline-tick'ов, заполненных под уровень дней).
См. v8_dashboard.html .balance + .runway.
5.10. Timeline / Activity Log
Vertical 1px line + цветные точки по типу события:
create— Teal--accentstatus-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 .activity.
6. Список всех 25 экранов
Каждый — отдельный HTML в 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 | 5 состояний: login/register с zxcvbn-индикатором/2FA с 6-cell кодом/forgot/recovery с 8 одноразовыми кодами |
| 02 | /dashboard |
v8_dashboard.html | KPI strip + Hero balance + Activity chart + Funnel-bar + Recent deals + System health + Quick actions |
| 03 | /deals |
v8_deals.html | Таблица 14 строк × 14 status-chips, фильтры (search + selects + density toggle), drawer открыт статически |
| 04 | /deals/{id} |
v8_deal_card.html | Hero + параметры + комментарий с 8 шаблонами + напоминание + footer-actions; aside: менеджер + метрики + history |
| 05 | /deals/kanban |
v8_kanban.html | 14 колонок DnD (262px каждая), карточки с иконками 🔥/⏰/✓ |
| 06 | /billing |
v8_billing.html | Кошелёк ₽ + ГЦК + тариф; pending-banner; история транзакций; счета и УПД (1С 8.3 XML) |
| 07 | /settings |
v8_settings.html | 8 вкладок (Профиль/Безопасность/Проекты/Команда/API/Интеграции/Тихие часы/Уведомления); 3 раскрыты |
| 08 | /reports |
v8_reports.html | Форма запроса + список jobs (done/running с progressbar/queued/failed + retry); квота 2/3 |
6.2. Экраны ошибок (3)
Все в одном файле с review-tabs: 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. Отдельный домен 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
| 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 | 10 имён + 5 знаков + 3 favourite combo. Архив выбора. |
| BR-2 | 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: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, не через*:@media (prefers-reduced-motion: reduce) { .has-motion { animation: none !important; transition: none !important; } } -
Skip link на каждой странице для клавиатурной навигации:
<a href="#main" class="skip-link">К контенту</a>
7.2. Контраст — проверка перед мержем
Перед каждым PR прогнать через 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:
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 — Vuetify не покрывает status-палитру, sidebar tokens и др.
8.3. Шаблон страницы
<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 с полями:
{
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:
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 макетов
cd дизайн
python -m http.server 8765
# открыть http://localhost:8765/concepts/v8_dashboard.html
13.2. Прогон axe для проверки
// В 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 с другим start_hue и repulsion-итерацией. min ΔE2000 ≥ 10 обязательно.
cd дизайн
PYTHONIOENCODING=utf-8 python palette_14.py
14. FAQ
Q: Можно ли использовать Tailwind вместо чистого CSS?
A: Можно, но токены из §3 должны попасть в 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.