Files
vidconf/design/DESIGN_SYSTEM.md

792 lines
73 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.
# VidConf — Дизайн-система
Дизайн зафиксирован как обязательный референс для реализации; изменения —
только через обновление этого документа и макетов `design/mockups/`. Оболочка
поддерживает светлую и тёмную тему с переключателем и автодетектом системной
темы (см. §0.1 и §1.2b ниже).
Референсы: `referens.png` (стиль/layout светлой оболочки), `collor_ico.png` (цвета/иконография тёмной комнаты конференции).
Все hex-значения взяты пипеткой непосредственно из референсов (см. таблицы ниже)
и проверены на контраст по формуле WCAG 2.1 (относительная светимость sRGB);
тёмная оболочка (§1.2b) — производная от уже утверждённой палитры комнаты
(hex переиспользованы 1:1, без повторной пипетки, см. таблицу происхождения).
## 0. Принцип тем
Оболочка (auth, лобби, календарь, «Мои конференции», join, админка, профиль,
email) имеет **две** темы — светлую (дефолт) и тёмную, с ручным переключателем
и автодетектом системной темы (см. §0.1). Комната конференции — **всегда**
тёмная, отдельная тема без переключателя.
| | Экраны | Референс | Характер |
|---|---|---|---|
| **Оболочка, светлая** | auth, лобби (хаб действий), календарь (менеджер конференций), «Мои конференции», подключение по ссылке/номеру, админка, профиль, email | `referens.png` | светлый фон, глубокий индиго в типографике, крупные скругления (2032px), pill-кнопки с обводкой, пастельно-мятный CTA, glass-панели |
| **Оболочка, тёмная** | те же экраны оболочки | `design/mockups/dark/` (утв. 2026-07-18), палитра — производная от `collor_ico.png` (см. §1.2b) | графитовый фон, пастельная мята вместо индиго (бренд/заголовки/ссылки/CTA), розовый danger, янтарный warning/focus, те же формы/скругления/типографика, что у светлой оболочки — меняется только цвет |
| **Комната (всегда тёмная)** | room (конференция) | `collor_ico.png` | графитовый фон, тёмно-серые плитки участников, зелёный/красный микрофон, жёлто-оранжевая рамка активного спикера, контурные иконки тулбара |
Темы не смешиваются на одном экране: комната конференции — единственный экран
с собственной несменяемой тёмной темой; всё остальное — оболочка, которая
целиком переключается между светлым и тёмным вариантом (пользователь никогда
не видит светлую панель внутри тёмной оболочки или наоборот).
### 0.1 Механизм переключения (оболочка)
Контракт для frontend-dev (реализовано в `design/tokens.css`):
- **Атрибут на корне** — `data-theme="dark"` / `data-theme="light"` на
`<html>` (или другом предке `:root`-скоупа приложения) — явный выбор
пользователя, сохраняется (localStorage/настройка профиля), имеет приоритет
над системной темой.
- **Дефолт по ОС** — если `data-theme` не проставлен вообще, действует
`@media (prefers-color-scheme: dark)`: тёмная палитра активируется
автоматически при тёмной системной теме, светлая — при светлой/отсутствии
поддержки. Как только пользователь явно переключил тему, автодетект
отключается для этого выбора (селектор `:root:not([data-theme])` в
`tokens.css` перестаёт совпадать).
- Оба триггера переопределяют **один и тот же набор** переменных, что и
светлый `:root` (`--color-bg`, `--color-ink-900`, `--color-accent` и т.д.) —
весь остальной CSS оболочки (компоненты, `frontend/`) ссылается на них через
`var()` и не нуждается в собственной `dark:`-логике.
- `[data-theme="room"]` (комната) — независимый третий скоуп с отдельным
неймспейсом `--color-room-*`; не активируется ни атрибутом `dark`, ни
`prefers-color-scheme`, и не участвует в переключателе оболочки.
Предустановленных переговорных комнат нет — конференции создаются динамически
(мгновенно из лобби или планово из календаря), закрепляются с повторением и
видны в «Мои конференции»; вход — по ссылке или номеру, гости входят через
«представиться». Макеты:
`design/mockups/lobby.html`, `calendar.html`, `my-conferences.html`, `join.html`.
---
## 1. Цветовые токены
Все значения продублированы 1:1 в `tokens.css`. Контраст текста проверен и
везде ≥ 4.5:1 (см. колонку «контраст»), кроме специально помеченных
декоративных/крупных элементов (правило WCAG для large text ≥ 3:1, отмечено ⚠︎).
### 1.1 Светлая тема (оболочка)
| Токен | Hex | Назначение | Контраст |
|---|---|---|---|
| `--color-bg` | `#F1F1F1` | фон страницы (пипетка referens.png, фон вокруг карточек) | — |
| `--color-bg-alt` | `#F7F7F8` | альтернативный фон секций / подложка полей | — |
| `--color-surface` | `#FFFFFF` | карточки, модалки, поля ввода | — |
| `--color-surface-glass` | `rgba(255,255,255,.55)` | glass-панели (стат-карточки в стиле referens.png), с `backdrop-filter: blur(16px)` | — |
| `--color-border` | `#E1E3E9` | базовая обводка карточек/инпутов | — |
| `--color-border-strong` | `#C7CCDC` | обводка при hover | — |
| `--color-border-pill` | `#4A5A9E` | обводка pill-кнопок (пипетка контура «НАПРАВЛЕНИЕ») | — |
| `--color-ink-900` | `#2E3454` | основной текст (параграфы), **текст на CTA** (`--color-accent-text`, см. ниже) | 11.3:1 на `--color-bg-alt` |
| `--color-ink-700` | `#3B4D95` | **брендовый индиго** — дисплейные заголовки, ссылки (пипетка «X'FLOW GYM») | 6.9:1 на `--color-bg` |
| `--color-ink-500` | `#535F94` | вторичный текст (подписи, мета) | 5.7:1 на `--color-bg-alt` |
| `--color-ink-400` | `#6976AC` | third-tier — только крупный/жирный текст (эйбрау-подписи в [скобках], ≥ 20px) | 4.1:1 ⚠︎ large-text-only |
| `--color-ink-300` | `#A6AECB` | плейсхолдеры, disabled-текст | — |
| `--color-accent` | `#D4F2E3` | **пастельная мята CTA** (заливка primary-кнопки; токен переименован из `--color-accent-green`, т.к. акцент больше не обязан быть зелёным) | — |
| `--color-accent-hover` | `#C7EBD9` | hover CTA (переименовано из `--color-accent-green-hover`; осветлена на 50%, было `#8FD6B2`) | — |
| `--color-accent-active` | `#BAE4CE` | active/pressed CTA (переименовано из `--color-accent-green-active`; осветлена на 50%, было `#74C89D`) | — |
| `--color-accent-text` | `#2E3454` | текст на мятной заливке — тёмный индиго, равен `--color-ink-900` (переименовано из `--color-accent-green-text`) | 10.15:1 (default) / 9.40:1 (hover) / 8.69:1 (active) |
| `--color-success` | `#0A7A4C` | текст статуса «свободна» | 5.38:1 на белом |
| `--color-success-bg` | `#E4F7ED` | подложка бейджа «свободна» | — |
| `--color-success-dot` | `#46916B` | индикатор-точка (пастелизирована, была неоновой `#1BC46B`; не единственный носитель статуса) | 3.80:1 на белом (нетекст) |
| `--color-danger` | `#C7364F` | текст статуса «отменена», кнопки удаления | 5.16:1 на белом |
| `--color-danger-bg` | `#FCE7EB` | подложка бейджа «отменена» | — |
| `--color-danger-dot` | `#C65366` | индикатор-точка (пастелизирована, была `#E0526B`) | 4.35:1 на белом (нетекст) |
| `--color-warning` | `#7F560D` | текст статуса «запланирована» | 6.48:1 на белом |
| `--color-warning-bg` | `#FBF0DC` | подложка бейджа «запланирована» | — |
| `--color-warning-dot` | `#B58A26` | индикатор-точка (углублена с `#D6A83D` ради контраста 3:1 на белом; рамка спикера в тёмной теме — отдельный токен, тот же исходный тон, оставлена без изменений) | 3.17:1 на белом (нетекст) |
| `--color-focus-ring` | `#3B4D95` | focus-visible обводка | — |
### 1.2a Тёмная тема — комната конференции
| Токен | Hex | Назначение | Контраст |
|---|---|---|---|
| `--color-room-bg` | `#1E1E1E` | фон канвы конференции (пипетка collor_ico.png) | — |
| `--color-room-surface` | `#2A2A2A` | топбар, нижний тулбар, всплывающие панели | — |
| `--color-room-surface-raised` | `#333333` | дропдауны, поповеры, панель чата | — |
| `--color-room-tile` | `#363636` | плитка участника (пипетка тёмно-серых плиток) | — |
| `--color-room-tile-border` | `#3F3F3F` | обводка плитки в покое | — |
| `--color-room-tile-hover` | `#3D3D3D` | плитка при hover/фокусе | — |
| `--color-room-text-primary` | `#E8E8E8` | имя участника, основной текст | 9.9:1 на tile |
| `--color-room-text-secondary` | `#B0B0B0` | вторичный текст (время, статус) | 5.6:1 на tile |
| `--color-room-text-tertiary` | `#7A7A7A` | таймстемпы чата (декоративно, не единственный носитель смысла) | — |
| `--color-room-mic-on` | `#7FDDA8` | **микрофон активен**, пастельная мята (была неоновая `#46D97A`, тот же зелёный вектор, что и `--color-accent` в оболочке) | 8.76:1 на surface / 7.37:1 на tile (нетекст) |
| `--color-room-mic-off` | `#EB93A1` | **микрофон выключен / mute**, пастельный розово-красный (была `#F2778C`) | 6.30:1 на surface / 5.31:1 на tile (нетекст) |
| `--color-room-speaker-ring` | `#D6A83D` | **рамка активного спикера**, жёлто-оранжевая (пипетка collor_ico.png) | 6.51:1 ⚠︎ декоративная рамка |
| `--color-room-danger` | `#EB93A1` | иконка «выйти», деструктивные контуры (пастелизирована вместе с mic-off) | 6.30:1 (нетекст) |
| `--color-room-danger-bg` | `#B85468` | заливка кнопки «Выйти» (hover/подтверждение), пастельная роза (была `#B3384E`) | 4.66:1 (белый текст) |
| `--color-room-danger-bg-hover` | `#B9495F` | hover заливки danger (была `#C24363`) | 5.02:1 (белый текст) |
| `--color-room-focus-ring` | `#D6A83D` | focus-visible обводка в тёмной теме | — |
Статусы никогда не передаются только цветом: у микрофона есть форма иконки
(перечёркнутый микрофон = mute), у бейджей лобби — иконка + текст, у активного
спикера — рамка + подпись, у конфликта брони — иконка `AlertTriangle` + текст.
### 1.2b Тёмная тема — оболочка
Макеты: `design/mockups/dark/*.html` (6
экранов, 1:1 разметка со светлыми прообразами `design/mockups/*.html`,
меняется только цвет — см. `design/mockups/dark/README.md`, полное
обоснование подбора палитры). Механизм переключения — §0.1.
**Принцип.** Тёмная оболочка не изобретает новых цветов, а переиспользует уже
утверждённые токены комнаты (§1.2a) в новых ролях — так тёмное лобби и тёмная
комната конференции читаются как одна система при переходе пользователя между
ними. Там, где у комнаты нет готового аналога (бренд-панель, формы, glass-
карточки — компоненты, которых в комнате не существует), значения получены
подмешиванием уже утверждённых цветов комнаты к графитовому фону/друг к другу,
а не придуманы независимо.
**Токены, переиспользованные из комнаты 1:1** (тот же hex, что и `--color-room-*`, §1.2a):
| Токен оболочки (`[data-theme='dark']`) | = токен комнаты | Hex | Новая роль в оболочке |
|---|---|---|---|
| `--color-bg` | `--color-room-bg` | `#1E1E1E` | фон страницы |
| `--color-surface` | `--color-room-surface` | `#2A2A2A` | карточки, топбар, поля |
| `--color-border` | `--color-room-tile-border` | `#3F3F3F` | базовая обводка |
| `--color-ink-900` | `--color-room-text-primary` | `#E8E8E8` | основной текст |
| `--color-ink-500` | `--color-room-text-secondary` | `#B0B0B0` | вторичный текст |
| `--color-ink-300` | `--color-room-text-tertiary` | `#7A7A7A` | плейсхолдеры/disabled |
| `--color-ink-700`, `--color-accent`, `--color-border-pill`, `--color-success`, `--color-success-dot` | `--color-room-mic-on` | `#7FDDA8` | мята: бренд/заголовки/ссылки/CTA/«успех» — единая пастельно-зелёная роль (см. ниже) |
| `--color-danger`, `--color-danger-dot` | `--color-room-mic-off` | `#EB93A1` | розово-красный: текст/иконки/обводка статуса «отменена»/ошибки |
| `--color-warning`, `--color-warning-dot` | `--color-room-speaker-ring` | `#D6A83D` | янтарь: статус «запланирована» |
| `--color-focus-ring` | `--color-room-focus-ring` | `#D6A83D` | кольцо `:focus-visible` |
| `--color-danger-solid`, `--color-danger-solid-hover` | `--color-room-danger-bg(-hover)` | `#B85468` / `#B9495F` | заливка **сплошных** danger-кнопок (`.btn-danger`) — отдельная пара, см. ниже почему |
**Одна мята — три роли.** В светлой оболочке бренд-индиго (`--color-ink-700`)
и мятный CTA (`--color-accent`) — два разных токена/hex. В тёмной оболочке они
**сведены к одному и тому же** `#7FDDA8` (= `--color-room-mic-on`), как и в
самой комнате, где одна пастельная мята уже покрывает всё «позитивное»
(микрофон вкл, демонстрация экрана, счётчик участников, отправка в чате).
Различие ролей (бренд-текст vs интерактивная заливка) остаётся читаемым за
счёт контекста, а не второго оттенка.
**Почему заливка danger-кнопок — отдельный токен.** В светлой теме один hex
(`--color-danger`, `#C7364F`) одинаково хорошо работает и как текст на белом,
и как заливка кнопки с белым текстом. В тёмной теме роли расходятся:
`--color-danger` (`#EB93A1`, светлый пастельный тон) отлично читается как
**текст на тёмном фоне** (6.30:1 на `#2A2A2A`), но слишком светлый для белого
текста кнопки поверх него (≈1.9:1, провал). Поэтому у **тёмной** оболочки
заведена отдельная пара `--color-danger-solid` / `--color-danger-solid-hover`
(`#B85468` / `#B9495F`, контраст с белым текстом 4.66:1 / 5.02:1) — только для
сплошных danger-кнопок (`.btn-danger`: «Отменить все повторения» в календаре,
«Удалить» в админке). В светлой теме этот токен не заведён — компонент
`.btn-danger` там по-прежнему ссылается на `--color-danger` напрямую (§4.3),
т.к. проблема специфична для тёмной палитры.
**Новые токены** (введены там, где у комнаты нет готового компонента-аналога —
формы, бренд-панель, glass-карточки; получены смешиванием уже утверждённых
цветов комнаты, не придуманы независимо):
| Токен | Hex | Как получен | Контраст | Роль |
|---|---|---|---|---|
| `--color-bg-alt` | `#242424` | между `--color-room-bg` и `--color-room-surface` | — | альт-фон секций, инпуты, disabled-поля |
| `--color-surface-glass` | `rgba(51,51,51,.55)` | `--color-room-surface-raised` (`#333333`) с прозрачностью, по образцу светлой `--color-surface-glass` | — | glass-панели (стат-виджеты) |
| `--color-border-strong` | `#585858` | осветление `--color-border` | 2.02:1 на surface (нетекст, декоративный hover-акцент, задублирован тенью — не единственный носитель состояния) | обводка при hover |
| `--color-ink-400` | `#5D9A78` | мята `#7FDDA8` × фон `#1E1E1E`, смешение 35% | 5.05:1 на bg / 4.35:1 на surface | эйбрау-подписи `[ В СКОБКАХ ]`, крупные подписи |
| `--color-accent-hover` | `#75CA9A` | мята × фон, смешение 10% | текст `#10331F` на заливке: 7.04:1 | hover CTA |
| `--color-accent-active` | `#6CB78C` | мята × фон, смешение 20% | текст `#10331F` на заливке: 5.80:1 | active/pressed CTA |
| `--color-accent-text` | `#10331F` | буквальный hex, уже встречающийся инлайн в `room.html` (`.chat-input-row button`, `.tb-btn.is-positive-active`, `.badge-count`) как цвет текста на `--color-room-mic-on` | 8.45:1 на мяте | текст на CTA/бренд-заливках мятой |
| `--color-success-bg` | `rgba(127,221,168,.14)` | мята, низкая непрозрачность поверх тёмной поверхности | — | подложка бейджей «идёт сейчас»/«свободна»/«активен» |
| `--color-danger-bg` | `rgba(235,147,161,.14)` | розовый (mic-off), низкая непрозрачность | — | подложка бейджей «отменена» |
| `--color-warning-bg` | `rgba(214,168,61,.16)` | янтарь (speaker-ring), низкая непрозрачность | — | подложка бейджей «запланирована» |
| `--shadow-sm/md/lg/glass` | `rgba(0,0,0,.35.5)` | по образцу `--shadow-room-panel` комнаты (тёмные тени вместо индиго-тонированных) | — | тени карточек/модалок/glass-панелей |
**Компонентные исключения (не токены, точечные правки в самих макетах).**
Пять мест, где CSS светлого макета использовал литеральный hex/rgba вместо
`var()` и поэтому не мог автоматически перекраситься — задокументированы в
`design/mockups/dark/README.md` (раздел «Точечные правки поверх скопированного
CSS»), в `tokens.css` не заводились как отдельные токены:
1. Аватары/`.weekday-chip.is-selected`/`.badge-role-admin` — заливка
`var(--color-ink-700)` + жёсткий `color: #fff` заменены на
`var(--color-accent-text)` (белый на мятной заливке нечитаем).
2. `.btn-danger` — заливка `var(--color-danger-solid)` вместо
`var(--color-danger)` (см. таблицу выше).
3. `.copy-toast` (тултип «Скопировано») — фон был `var(--color-ink-900)`
(тёмный в светлой теме → стал бы светлым в тёмной, ломая тёмный чип-
тултип); в макетах заменён на буквальный `#333333`
(= `--color-room-surface-raised`). При переносе в `frontend/` (реальный
компонент на React, не статичная копия страницы) рекомендуется завести
токен-алиас `--color-surface-raised` под обе темы, а не повторять этот
литерал — тултип не единственное будущее место такой потребности.
4. Декоративные градиенты/rgba-подложки (бренд-панель `login.html`, фон
`body` в `lobby.html`, `.day-col.is-today`/`.cancel-alert` в
`calendar.html`, box-shadow фокуса/ошибки форм, glass-обводка) — тональные
аналоги новой палитры (мятное свечение вместо индигового, янтарный
focus-glow вместо индигового, розовый error-glow); та же практика уже
используется в светлых макетах (см. `auth.html` — там эти значения тоже
не токенизированы).
5. `color-scheme: dark` — добавлен в активационный блок `tokens.css` (см.
§0.1), чтобы нативные элементы форм (скроллбар, чекбоксы) тоже
рендерились в тёмной палитре браузера.
**Доступность.** Текст ≥ 4.5:1 везде, кроме крупных/декоративных элементов
(правило §5, порог 3:1). Статус не передаётся только цветом (см. §1.2a).
Палитра пастельная, той же светлоты, что и утверждённая палитра комнаты —
неоновых/кислотных акцентов не вводилось. Регрессия — `design/tools/contrast.py`.
### 1.3 Проверка контраста
Палитра пастельная (не неоновая) во всей системе — все текстовые пары
проходят WCAG 2.1 (≥4.5:1, кроме отмеченных ⚠︎ декоративных/крупных
элементов, ≥3:1). Регрессионная проверка — скрипт `design/tools/contrast.py`
(явный список пар с ratio и порогом), запуск: `python3 design/tools/contrast.py`
— 24/24 пар PASS.
### 1.4 Логотип
Логотип — стилизованный **объектив камеры**, отсылка к видео как сути
продукта.
**Файлы:**
- `design/logo.svg` — мастер-версия, квадратный viewBox `0 0 64 64`,
масштабируется без искажений. Для крупных начертаний: центральный медальон
лобби, промо-материалы, шапка письма при экспорте в PNG высокого разрешения.
- `design/favicon.svg` — упрощённая версия для мелких размеров (1632px):
меньше колец, толще штрихи, чтобы силуэт не «замыливался». Используется как
иконка в топбаре рядом со словом «VidConf» во всех макетах, а также как
browser favicon / app-иконка продакшен-сборки.
**Конструкция (обе версии):**
- центр — плоский шестигранник (`polygon`), закрытая диафрагма объектива,
залита сплошным цветом;
- вокруг — концентрические окружности (4 в мастер-версии, 1 в favicon),
каждая нарисована как `<circle>` со `stroke-dasharray`/`stroke-dashoffset`,
что даёт разорванные на сегменты дуги неровной длины и разной толщины
штриха — техно/HUD-эстетика, ощущение вращения диафрагменных лепестков.
**Цвет:** строго монохром, один тон — `currentColor`, наследующий цвет
текста в месте использования (в проекте это всегда `var(--color-ink-700)`,
`#3B4D95`). Никаких градиентов, бликов или цветов из референса-эскиза.
Контраст `#3B4D95` на `--color-bg` (`#F1F1F1`) — 6.93:1, на `--color-surface`
(`#FFFFFF`) — 7.83:1 (обе пары — в `design/tools/contrast.py`, порог для
графики 3:1 — проходят с большим запасом даже как текстовый).
**Использование в макетах:**
- топбар/сайдбар (`.brand-mark`) — `favicon.svg` инлайн, `22×22px`
(соразмерно высоте строки словесного знака, заменяет прежний `.logo-dot`)
во всех макетах `design/mockups/*.html`, где есть слово «VidConf»;
- центральный медальон лобби (`.hub-logo`) — полная `logo.svg`, `56×56px`
внутри медальона `156×156px`, вместе со словом «VidConf» под знаком (см.
§4.12 «Карточка-действие хаба (лобби)» — геометрия перекрытия медальона);
- `email-summary.html` — инлайн SVG допустим только для HTML-макета;
в продакшен-письме обязателен растровый PNG (почтовые клиенты в массе не
рендерят инлайн-SVG), см. комментарий в файле рядом с разметкой.
### 1.5 Аватары (инициалы)
Круглый аватар с инициалами (`.avatar`, компонент `Avatar` §4) — заливка
**всегда** `var(--color-ink-700)` (основной, current-user — user-pill топбара,
сайдбар админки) либо `var(--color-ink-500)` (второстепенные, списки
участников — таблицы админки, участники встречи в письме-саммари). Текст
инициалов — **всегда светлый, `#FFFFFF`**, никогда не тёмный `ink-900`/
`ink-700`: тёмные инициалы на индиго-заливке дают нечитаемый, «слипшийся»
результат. Правило зафиксировано как регрессионные пары в
`design/tools/contrast.py`:
| Заливка | Текст | Контраст |
|---|---|---|
| `--color-ink-700` (`#3B4D95`) | `#FFFFFF` | 7.83:1 |
| `--color-ink-500` (`#535F94`) | `#FFFFFF` | 6.12:1 |
Обе пары — с большим запасом выше порога 4.5:1 для текста. Дополнительно:
`font-weight` инициалов — не ниже 600 (используется `--text-caption`,
`600 12px`), `text-transform: none` (кириллические инициалы не должны
проходить через uppercase-трансформацию родителя).
**⚠ Предупреждение о каскаде CSS-специфичности.** В
`calendar.html` и `my-conferences.html` правило `.avatar-btn span { color:
var(--color-ink-900); }` (специфичность 0,1,1 — класс + тип-селектор) имело
специфичность ВЫШЕ, чем `.avatar { color: #fff; }` (0,1,0 — один класс), и
перебивало белый цвет у `<span class="avatar">` (он тоже `span` внутри
`.avatar-btn`, значит попадал под общее правило `.avatar-btn span`) —
инициалы рендерились тёмным `ink-900` на индиго-заливке, нечитаемо.
Специфичность решает независимо от порядка правил в файле, поэтому простое
переупорядочивание CSS не спасает. **Правило на будущее:** имя рядом с
аватаром — всегда через адресный класс (`.user-name`, не голый `span`),
никогда `.wrapper span` без уточнения — такой селектор непреднамеренно
задевает `<span class="avatar">`, если он лежит в той же обёртке. Проверено
headless-рендером (`getComputedStyle(...).color`) после фикса — `rgb(255,
255, 255)` в обоих файлах; заодно проверены `admin.html`, `lobby.html`
(структура другая — `.avatar-btn div span`/`.user-pill .avatar`, коллизии не
было) — там уже было `rgb(255, 255, 255)` и до правки.
---
## 2. Типографика
| Роль | Шрифт | Fallback-стек | Где |
|---|---|---|---|
| Дисплей (display) | **Unbounded** | `'Arial Rounded MT Bold', -apple-system, 'Segoe UI', sans-serif` | H1 лендинг-подобных блоков, крупные цифры дашборда, логотип |
| Текст (body/UI) | **Inter** | `-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif` | вся UI-типографика: формы, таблицы, кнопки, параграфы |
| Моно (mono) | **JetBrains Mono** | `'SFMono-Regular', Menlo, Consolas, monospace` | код комнаты, ID брони, таймер конференции, .ics-детали |
Шрифты подключаются как **самостоятельно захостенные** (`frontend/public/fonts`,
`@font-face` с относительным путём), CDN Google Fonts не используется нигде
(в т.ч. в макетах) согласно правилу проекта «секреты и зависимости — не из
внешних сетей на рантайме». В `tokens.css` объявлены `@font-face` с путями
`../fonts/...`, отсутствие файлов в макетах безопасно деградирует в fallback-стек
без сетевых запросов.
Unbounded выбран за геометричные, слегка скруглённые формы букв — это то же
семейство характера, что у дисплейного заголовка в `referens.png` (широкие O/G/W,
единая толщина штриха). Inter — для плотного кириллического текста интерфейса
(отличная читаемость в мелких кеглях, стандарт де-факто для shadcn/ui).
### Шкала
| Токен | Размер / line-height | Начертание | Применение |
|---|---|---|---|
| `--text-display-2xl` | 64px / 1.05 | Unbounded 700 | Hero-заголовок (не используется в MVP-экранах, зарезервировано) |
| `--text-display-xl` | 48px / 1.1 | Unbounded 700 | Крупные цифры (админ-дашборд, счётчики) |
| `--text-display-lg` | 34px / 1.2 | Unbounded 600 | H1 экрана (заголовок лобби, «VidConf» в auth) |
| `--text-h1` | 28px / 1.25 | Inter 700 | заголовок карточки/секции |
| `--text-h2` | 22px / 1.3 | Inter 600 | подзаголовок |
| `--text-h3` | 18px / 1.35 | Inter 600 | заголовок компонента (карточка комнаты, панель) |
| `--text-body-lg` | 16px / 1.5 | Inter 400/500 | основной текст форм |
| `--text-body` | 14px / 1.5 | Inter 400/500 | таблицы, списки, вторичный UI |
| `--text-caption` | 12px / 1.4, uppercase, `letter-spacing: .04em` | Inter 600 | эйбрау-подписи `[ В КВАДРАТНЫХ СКОБКАХ ]`, статус-бейджи |
| `--text-mono-sm` | 13px / 1.4 | JetBrains Mono 500 | код комнаты/брони |
---
## 3. Spacing, радиусы, тени, границы
### Spacing (8pt-грид с базой 4px)
`--space-1: 4px · --space-2: 8px · --space-3: 12px · --space-4: 16px ·
--space-5: 20px · --space-6: 24px · --space-8: 32px · --space-10: 40px ·
--space-12: 48px · --space-16: 64px · --space-20: 80px · --space-24: 96px`
### Радиусы
Крупные скругления карточек взяты из `referens.png` (визуально 2432px).
`--radius-sm: 8px` (инпуты, чипы) · `--radius-md: 12px` (кнопки не-pill, теги) ·
`--radius-lg: 16px` (плитки участников в комнате, пипетка collor_ico.png) ·
`--radius-xl: 20px` (малые карточки) · `--radius-2xl: 24px` (карточки комнат/лобби) ·
`--radius-3xl: 32px` (крупные панели, hero-блоки) · `--radius-full: 999px`
(pill-кнопки, аватары, бейджи-статусы)
### Тени
- `--shadow-sm` `0 1px 2px rgba(46,52,84,.06)` — лёгкий отрыв карточки от фона.
- `--shadow-md` `0 4px 16px rgba(46,52,84,.08)` — карточка при hover.
- `--shadow-lg` `0 12px 32px rgba(46,52,84,.12)` — модалки, поповеры.
- `--shadow-glass` `0 8px 24px rgba(59,77,149,.15)` + `backdrop-filter: blur(16px)` — glass-панели (стат-виджеты).
- `--shadow-speaker-glow` `0 0 0 3px var(--color-room-speaker-ring), 0 0 24px rgba(214,168,61,.35)` — свечение активного спикера (доп. к рамке).
- `--shadow-room-panel` `0 8px 24px rgba(0,0,0,.4)` — панель чата/поповеры в тёмной теме.
### Границы
`--border-thin: 1px` (базовая) · `--border-md: 1.5px` (pill-кнопки) ·
`--border-thick: 2px` (hover/focus состояния) · `--border-speaker: 3px`
(рамка активного спикера).
### Breakpoints (desktop-first)
`--bp-desktop: 1440px` — расчётная ширина макета. `--bp-min: 1280px` — минимальная
поддерживаемая ширина (контент перетекает, колонки не ломаются
до 1280px; мобильная адаптация — вне скоупа).
### Motion
Только микро-переходы состояний (hover/focus/toggle), без декоративных
анимаций: `--transition-fast: 120ms ease`, `--transition-base: 200ms ease`.
---
## 4. Компоненты и их состояния
### 4.1 Кнопка Primary (pill CTA, светлая тема)
Форма: `--radius-full`, паддинг `12px 24px`, шрифт `--text-body-lg` / Inter 600.
| Состояние | Фон | Текст | Обводка |
|---|---|---|---|
| default | `--color-accent` | `--color-accent-text` | нет |
| hover | `--color-accent-hover` | тот же | нет, `--shadow-md` |
| active/pressed | `--color-accent-active` | тот же | нет |
| focus-visible | как default | тот же | `2px --color-focus-ring`, offset 2px |
| disabled | `--color-ink-300` фон 40% | `--color-surface` | нет, `cursor: not-allowed` |
### 4.2 Кнопка Secondary (pill outline, светлая тема)
Прозрачный фон, обводка `1.5px --color-border-pill`, текст `--color-ink-700`.
Hover: фон `--color-bg-alt`, обводка `--color-ink-700`. Focus-visible: кольцо
`--color-focus-ring`. Disabled: обводка `--color-border`, текст `--color-ink-300`.
### 4.3 Кнопка Danger (светлая тема, напр. «Удалить комнату» в админке)
Фон `--color-danger`, текст `#FFFFFF`, форма `--radius-full`. Hover — темнее на 8%.
Всегда сопровождается иконкой (`Trash2`), не только цветом.
### 4.4 Инпут / форма
Фон `--color-surface`, обводка `1px --color-border`, радиус `--radius-md`,
паддинг `10px 14px`, текст `--text-body-lg`.
| Состояние | Обводка | Доп. |
|---|---|---|
| default | `--color-border` | — |
| hover | `--color-border-strong` | — |
| focus | `2px --color-focus-ring` | подпись-хинт индиго |
| error | `2px --color-danger` | текст ошибки под полем, иконка `AlertCircle` слева |
| disabled | `--color-border`, фон `--color-bg-alt` | текст `--color-ink-300` |
### 4.5 Карточка (светлая тема)
Фон `--color-surface`, радиус `--radius-2xl`, обводка `1px --color-border`,
тень `--shadow-sm` (по умолчанию) → `--shadow-md` (hover, если карточка кликабельна).
Glass-вариант (стат-виджеты): `--color-surface-glass` + `backdrop-filter: blur(16px)`
+ `--shadow-glass`, обводка `1px rgba(255,255,255,.6)`.
### 4.6 Бейдж статуса конференции (календарь, «Мои конференции»)
Бейдж описывает жизненный цикл КОНФЕРЕНЦИИ (комнат в системе нет). Pill (`--radius-full`),
паддинг `4px 12px` (мелкий вариант в сетке календаря — `2px 9px`, `font-size: 11px`),
`--text-caption`, иконка 1214px слева от текста.
| Статус | Фон | Текст | Иконка |
|---|---|---|---|
| Запланирована (разовая) | `--color-warning-bg` | `--color-warning` | `Clock` |
| Закреплена · повторяется | `--color-bg-alt`, обводка `1px --color-border-strong` | `--color-ink-700` | `Repeat` |
| Идёт сейчас | `--color-success-bg` | `--color-success` | `Play` |
| Завершена | `--color-bg-alt` | `--color-ink-500` | `CheckCircle2` |
| Отменена | `--color-danger-bg` | `--color-danger` | `X`, заголовок карточки — `line-through` |
Закрытость паролем — НЕ отдельный статус жизненного цикла (ортогональна ему:
запланированная, закреплённая или идущая сейчас конференция может быть
закрытой одновременно). Отображается отдельно — маленькая иконка `Lock`
(1315px, `--color-ink-700`/`--color-room-text-secondary`) инлайном перед
названием конференции, не бейджем.
### 4.7 Плитка участника (комната, тёмная тема)
Фон `--color-room-tile`, радиус `--radius-lg`, обводка `1px --color-room-tile-border`.
Имя — верхний левый угол, `--text-body` / `--color-room-text-primary`, поверх
полупрозрачной подложки-градиента снизу-вверх для читаемости на видео.
Иконка микрофона — нижний левый угол, 20px, круглая подложка `rgba(0,0,0,.35)`.
| Состояние | Обводка/эффект | Микрофон |
|---|---|---|
| default (не говорит) | `--color-room-tile-border` | `Mic` зелёный, если не muted |
| активный спикер | `--border-speaker` `--color-room-speaker-ring` + `--shadow-speaker-glow` | форма рамки — единственный, кто её имеет |
| muted | обводка как default | `MicOff` `--color-room-mic-off`, иконка перечёркнута (не только цвет) |
| видео выключено | вместо видео — аватар-заглушка на `--color-room-tile-hover` | значок `VideoOff` в углу |
| pinned/развёрнута | увеличенный размер в сетке, иконка `Pin` активна | — |
| hover (крупная плитка) | показывается toolbar-оверлей (`Maximize2`, `MoreVertical`) | — |
### 4.8 Нижний тулбар комнаты (тёмная тема)
Фон `--color-room-surface`, высота 72px, иконки контурные 24px, подпись под
иконкой `--text-caption` `--color-room-text-secondary`.
| Кнопка | Цвет по умолчанию | Toggled-off/mute | Позитивное действие |
|---|---|---|---|
| Микрофон | `--color-room-text-primary` outline | фон `--color-room-mic-off` заливка, иконка `MicOff` | — |
| Камера | `--color-room-text-primary` outline | фон `--color-room-mic-off`, иконка `VideoOff` | — |
| Демонстрация экрана | `--color-room-text-primary` outline | — | active-состояние: иконка/подпись `--color-room-mic-on` |
| Чат | `--color-room-text-primary` outline | — | активна (панель открыта): подложка `--color-room-surface-raised` |
| Участники | `--color-room-text-primary` outline | — | бейдж-счётчик `--color-room-mic-on` |
| Выйти | обводка `--color-room-danger` | — | всегда красная, hover → заливка `--color-room-danger-bg` |
### 4.9 Панель чата (комната)
Свёрнута: не занимает места, доступна кнопкой в тулбаре (см. 4.8).
Развёрнута: правая колонка 320px, фон `--color-room-surface-raised`,
разделитель `1px --color-room-tile-border`, сетка участников сужается.
Сообщения — пузыри `--color-room-tile`, имя автора `--color-room-mic-on`
(лёгкий акцент, не критично для контраста т.к. декоративно), текст
`--color-room-text-primary`.
### 4.10 Ячейка календаря-менеджера
Календарь не бронирует переговорные (нет привязки к комнате, нет конфликтов
брони по `EXCLUDE`-констрейнту БД) — это менеджер запланированных/закреплённых
КОНФЕРЕНЦИЙ. Единица сетки —
дневная колонка (`--radius-lg`, фон `--color-bg-alt`, `min-height: 460px`),
внутри — стопка карточек-конференций («чипов», `--radius-md`, `--text-body`).
| Состояние | Фон/обводка | Текст/иконка |
|---|---|---|
| День без конференций | колонка `--color-bg-alt` | подпись-заглушка `--color-ink-300` по центру («Свободный день» / «Ещё нет конференций») |
| Сегодняшняя колонка | лёгкая подсветка `rgba(212,242,227,.16)` (тон `--color-accent`) | заголовок дня `--color-ink-700` |
| Чип конференции (default) | `--color-surface`, обводка `1px --color-border` | бейдж статуса §4.6 сверху, hover → `--shadow-sm` + `--color-border-strong` |
| Чип отменённой конференции | как default, `opacity: .6` | заголовок `line-through`, `--color-ink-500` |
| Закрытая паролем | как default | иконка `Lock` инлайн перед названием (см. §4.6) |
### 4.11 Таблица (админка)
Заголовок `--color-bg-alt`, `--text-caption` uppercase `--color-ink-500`.
Строки — разделитель `1px --color-border`, hover-строка `--color-bg-alt`.
Действия — icon-button (`Pencil`, `Trash2`, `Mail`) 32px, `--radius-md`.
### 4.12 Карточка-действие хаба (лобби)
Лобби — хаб из 4 плиток-действий вокруг медальона VidConf
(`design/mockups/lobby.html`), а не список комнат. Фон `--color-surface`,
радиус `--radius-3xl`, обводка `1px --color-border`, тень `--shadow-sm`.
**Фиксированная высота** (не `min-height`) у всех 4 плиток — обязательное
условие строгой симметрии сетки 2×2 независимо от длины текста внутри.
Тайл `816×340px` (при доступной ширине ≥ ~1728px; на меньших вьюпортах,
вплоть до `--bp-min: 1280px`, сетка пропорционально сжимается через
`width: min(1664px, 100%)` на контейнере `.hub`, оставаясь строго
симметричной 2×2, без переполнения); контейнер `.hub``1664px`, gap между
тайлами — `--space-8` (32px). Типографика внутри: заголовок `40px/1.2`
(`54px/1.1` для акцентной плитки «Создать»), подпись-объект `21px`,
примечание `17px`.
Медальон-иконка (68px, круг, обводка `--border-thick --color-ink-700`)
вынесен на верхнюю кромку карточки (`position: absolute; top: -34px`),
под него на самой карточке зарезервирован `margin-top: 34px`. Заголовок
`--color-ink-700`, uppercase; подзаголовок-объект `--color-ink-900`;
примечание `--color-ink-500`.
Главное действие («Создать») — акцентный вариант на пастельной мяте
(`--color-accent`), текст и медальон — `--color-accent-text`.
| Состояние | Фон (обычная / акцентная) | Эффект |
|---|---|---|
| default | `--color-surface` / `--color-accent` | `--shadow-sm` |
| hover | тот же / `--color-accent-hover` | `--shadow-lg`, `translateY(-4px)` |
| active | тот же / `--color-accent-active` | `translateY(-1px)` |
| focus-visible | тот же | кольцо `--color-focus-ring`, offset 2px |
**Медальон VidConf в центре** — декоративный (`pointer-events: none`,
`aria-hidden`), диаметр `156px`, фон `--color-surface`
(непрозрачный), обводка `--border-thick --color-ink-700`, тень
`--shadow-lg`, `z-index: 20` (гарантированно поверх карточек). Содержимое —
знак объектива `logo.svg` (`56×56px`, §1.4) + слово «VidConf» под ним
(`600 16px var(--font-display)`, `--color-ink-700`).
Медальон не просто сидит в промежутке сетки, а **лежит поверх** равных по
величине частей видимых углов всех 4 карточек. Геометрия фиксированная (в пикселях,
не в процентах — высота `.hub` всегда `2×340 + 32 = 712px`, независимо от
ширины вьюпорта):
- по горизонтали асимметрии нет — центр строго на границе колонок, `16px`
(половина `gap`) до ближнего края каждой карточки;
- по вертикали — видимый низ карточек ряда 1 на `y = 340px` от верха `.hub`,
видимый верх карточек ряда 2 на `y = 406px` (`gap` 32px + `margin-top`
34px карточки под собственный медальон-иконку); равноудалённая от обеих
точка — `y = 373px` (`33px` до каждой), а **не** геометрический центр
сетки `356px` (`712 / 2`), — из-за несимметричного резерва под
action-icon только у нижнего ряда.
Медальон центрирован в `(50%, 373px)` (`top: 373px; left: 50%;
transform: translate(-50%, -50%)`), а не `(50%, 50%)`. Такая точка —
зеркально-симметричная по обеим осям относительно всех 4 карточек (что
подтверждается тем, что сдвиг `16px`/`33px` — общий для обеих карточек по
каждой оси, а не индивидуальный для одной), поэтому перекрытие получается
одинаковым на все 4 угла **по построению**, без ручной подгонки под каждый
угол. Проверено headless-рендером (Playwright/Chromium, 1280/1440/1728px):
перекрытие видимой (с учётом скругления `--radius-3xl`, 32px) части каждой
из 4 карточек — **29.2px**, идентично на всех четырёх и на всех проверенных
ширинах вьюпорта (симметрия по x/y — расхождение 0px).
### 4.13 Карточка создания конференции (календарь)
Фон `--color-surface`, радиус `--radius-2xl`, паддинг `--space-6`, sticky
в двухколоночном layout календаря. Поля «Название / Дата / Время /
Участники» — стандартный инпут §4.4. Два тумблера общего компонента
`.switch`/`.slider` (заливка `--color-accent-active` во включённом состоянии):
- **«Закрытая по паролю»** — раскрывает поле пароля под тумблером.
- **«Закрепить постоянную конференцию»** — раскрывает блок повторений
(§4.14) со всеми 4 вариантами сразу.
Оба раскрытия — чистый CSS через `:has()` (без JS): тумблер и раскрываемый
блок — сиблинги внутри общего `.pin-block`/`.toggle-block`,
`:has(input:checked) .sub-block { display: … }`. При адаптации на
`frontend/` — то же поведение через `useState`/controlled-компонент.
### 4.14 Блок повторений (recurrence)
Раскрывается под тумблером «Закрепить постоянную конференцию» (§4.13),
отделён `1px dashed --color-border`. Список из 4 вариантов
(`.recurrence-option`, radio-группа — выбран ровно один):
| Вариант | Доп. поля | Обязательная подсказка (edge cases) |
|---|---|---|
| По дням недели, еженедельно | 7 круглых чипов дней недели (34px, множественный выбор) | ближайшие 2 даты вхождения |
| Раз в 2 недели | дата начала + день недели | ближайшие 2 даты вхождения |
| Раз в месяц | число месяца (131) | «если в месяце нет такого числа — переносится на последний день месяца» (31-е число, 29 февраля) |
| Каждые N дней | целое N + дата начала | ближайшие даты вхождения («через N, 2N, 3N дней») |
Строка варианта: `--radius-md`, обводка `1px --color-border` → при выборе
обводка `--color-ink-700` + фон `--color-bg-alt` (через `:has(input:checked)`,
см. §4.13). Под-поля выбранного варианта раскрываются тем же CSS-приёмом;
у невыбранных — скрыты (`display: none`), а не задизейблены — чтобы список
из 4 строк был виден целиком сразу после раскрытия тумблера. Выбранный день недели/чип — заливка `--color-ink-700`, текст
белый. Подсказки — `12px`, `--color-ink-500`.
### 4.15 Пилюли «ссылка / номер» с копированием
Новый компонент «Моих конференций»: у каждой конференции есть постоянная
ссылка и человеко-диктуемый номер, доступные для копирования одним кликом.
Подпись-заголовок над пилюлей — `--text-caption`, `--color-ink-400`
(«Ссылка» / «Номер»). Пилюля — `--radius-full`, фон `--color-bg-alt`,
обводка `1px --color-border`; значение — `--text-mono-sm` `--color-ink-700`,
обрезается `text-overflow: ellipsis` (для длинных ссылок). Кнопка
копирования — круг 28px справа внутри пилюли.
| Состояние кнопки копирования | Иконка | Доп. |
|---|---|---|
| default | `Copy`, `--color-ink-500` | — |
| hover | `Copy`, `--color-ink-700` | `--shadow-sm` |
| скопировано (~1.5s) | `Check`, `--color-success` | всплывающая подсказка «Скопировано» над кнопкой |
| focus-visible | как default | кольцо `--color-focus-ring` |
Статус «скопировано» не передаётся только сменой цвета иконки — обязательно
дублируется сменой формы иконки (`Copy``Check`) и текстовым тултипом.
### 4.16 Селект (кастомный выпадающий список)
Заменяет нативный `<select>` в полях
«Рассылка саммари» (карточка создания/редактирования конференции, §4.13) и
«Получатели по умолчанию» (настройки админки), а также любой будущий
одиночный выбор из списка. Собран из уже утверждённых примитивов — триггер
переиспользует геометрию инпута (§4.4), дроплист переиспользует паттерн
`.menu` (выпадающее меню бургера лобби, `design/mockups/lobby.html`), пункт
списка — паттерн `.menu a`. Ничего нового в токенах не заводится.
**Триггер (закрытое состояние).** Высота `44px` (паддинг `10px 14px` +
`--text-body-lg` при `line-height: 1.5` — идентично инпуту §4.4, чтобы
селект и текстовые поля в одной форме были на одной высоте), фон
`--color-surface`, обводка `1px --color-border`, радиус `--radius-md`.
Значение слева, шеврон `ChevronDown` (18px, `--color-ink-500`) прижат
вправо. Текст — `--text-body-lg` `--color-ink-900`; если значение не
выбрано — плейсхолдер `--color-ink-300` (та же роль токена, что у
плейсхолдера инпута).
| Состояние | Обводка/фон | Текст/шеврон | Доп. |
|---|---|---|---|
| default | `--color-border` | `--color-ink-900` / шеврон `--color-ink-500` | — |
| hover | `--color-border-strong` | те же | курсор `pointer` |
| focus-visible (закрыт) | `2px --color-focus-ring` | те же | как фокус инпута §4.4 |
| открыт | `2px --color-focus-ring`, нижние углы дроплиста продолжают радиус триггера визуально (панель — отдельный блок, см. ниже) | шеврон повёрнут `rotate(180deg)`, `--transition-fast` | `aria-expanded="true"` |
| error | `2px --color-danger` | текст `--color-ink-900` | текст ошибки под полем + `AlertCircle` слева от значения, как у инпута §4.4 |
| disabled | `--color-border`, фон `--color-bg-alt` | текст и шеврон `--color-ink-300` | курсор `not-allowed`, `aria-disabled="true"` |
**Дроплист (открытое состояние).** `position: absolute`, `top: calc(100% + 6px)`,
ширина равна триггеру (`width: 100%`, в отличие от `.menu`, у которого
`min-width` фиксирован — селект обязан совпадать по ширине с полем, иначе
теряется визуальная связь триггер↔панель). Фон `--color-surface`, обводка
`1px --color-border`, радиус `--radius-lg`, тень `--shadow-lg`, внутренний
паддинг `--space-2` (8px), `z-index: 30` (над соседними карточками формы).
Список длиннее ~7 пунктов — `max-height: 280px; overflow-y: auto` со
стандартным скроллбаром (не кастомизируется, см. `color-scheme` в
`tokens.css`, влияет на нативный скроллбар браузера в обеих темах).
Пункт списка (`role="option"`): паддинг `10px 14px`, радиус `--radius-sm`,
`--text-body-lg` 400, текст `--color-ink-900`, `display: flex; justify-content: space-between`
(значение слева, место под иконку `Check` справа).
| Состояние пункта | Фон | Текст | Доп. индикатор |
|---|---|---|---|
| default (не выбран, не наведён) | прозрачный | `--color-ink-900` | — |
| hover / клавиатурная подсветка (`aria-activedescendant`) | `--color-bg-alt` | `--color-ink-700` | — |
| выбран (`aria-selected="true"`) | прозрачный (или `--color-bg-alt`, если одновременно наведён/подсвечен — состояния комбинируются аддитивно) | `--color-ink-900`, `font-weight: 600` | иконка `Check` (16px, `--color-ink-700`) справа — «выбрано» никогда не передаётся только фоном/цветом |
| disabled-пункт | прозрачный | `--color-ink-300` | `cursor: not-allowed`, `aria-disabled="true"`, без hover-эффекта |
Опциональный разделитель групп внутри списка — тот же приём, что
`.menu-divider`: `1px --color-border`, `margin: var(--space-2) 6px`.
**Клавиатурная доступность и ARIA.** Паттерн — listbox-combobox
(`role="combobox"` на триггере + `role="listbox"` на панели), без нативного
`<select>`, чтобы стилизация дроплиста была полностью в токенах проекта.
| Действие | Клавиша | Поведение |
|---|---|---|
| Открыть список | `Enter`, `Space`, `ArrowDown`, `ArrowUp` (фокус на триггере, список закрыт) | раскрывает дроплист; подсветка ставится на выбранный пункт (если значения ещё нет — на первый) |
| Навигация | `ArrowDown` / `ArrowUp` (список открыт) | двигает клавиатурную подсветку на следующий/предыдущий пункт (циклически не переходит с последнего на первый), обновляет `aria-activedescendant` триггера — фокус физически остаётся на триггере |
| К началу/концу | `Home` / `End` | подсветка на первый/последний доступный (не disabled) пункт |
| Быстрый переход | ввод буквы | подсветка прыгает на ближайший следующий пункт, начинающийся с введённого символа (стандартное ожидание пользователей от `<select>`) |
| Выбрать и закрыть | `Enter`, `Space` (на подсвеченном пункте) | фиксирует значение, закрывает панель, фокус остаётся на триггере, `aria-expanded="false"` |
| Закрыть без изменений | `Esc` | закрывает панель, значение не меняется, фокус остаётся на триггере |
| Закрыть без изменений | клик вне триггера/панели, `Tab` | панель закрывается, значение не меняется (если пункт не был явно выбран через `Enter`/клик) |
ARIA-контракт: триггер — `role="combobox"`, `aria-haspopup="listbox"`,
`aria-expanded`, `aria-controls="<id панели>"`, `aria-labelledby`/`aria-label`
(подпись поля, как у инпута §4.4); панель — `role="listbox"`,
`aria-activedescendant` на триггере указывает на `id` текущего подсвеченного
`role="option"`; каждый пункт — `aria-selected`, disabled-пункты —
`aria-disabled="true"` и исключены из навигации стрелками/type-ahead.
**Обе темы.** Компонент целиком собран из уже описанных выше токенов
(`--color-surface`, `--color-border(-strong)`, `--color-ink-*`,
`--color-focus-ring`, `--color-danger`, `--shadow-lg`, `--radius-md/lg/sm`),
которые уже переключаются между светлой и тёмной оболочкой по механизму
§0.1 — отдельной тёмной версии спеки не требуется. Прецедент — `.menu`
(дроплист бургер-меню лобби): его CSS в `design/mockups/lobby.html` и
`design/mockups/dark/lobby.html` побайтово идентичен, меняется только
подстановка переменных из `tokens.css`; Select использует ту же панель
целиком на var(), поэтому корректно рендерится в тёмной теме без правок.
В тёмной оболочке фон панели/триггера — графитовый `--color-surface`
(`#2A2A2A`), подсветка пункта — `--color-bg-alt` (`#242424`), выбранный
пункт и hover-текст — пастельная мята `--color-ink-700` (`#7FDDA8`), кольцо
фокуса — янтарь `--color-focus-ring` (`#D6A83D`), см. §1.2b.
---
## 5. Доступность
- Текст ≥ 4.5:1 везде, кроме крупных декоративных элементов (отмечены ⚠︎ выше,
≥ 3:1 по правилу WCAG для large text).
- Статус всегда дублируется не цветом: иконка (статус конференции §4.6,
mic on/off, «скопировано» при копировании ссылки/номера) + текстовая подпись.
- `:focus-visible` — везде видимое кольцо (`--color-focus-ring` светлая тема,
`--color-room-focus-ring` тёмная), никогда `outline: none` без замены.
- Интерактивные элементы ≥ 40×40px области клика (кнопки тулбара — 48×48px).
- Все декоративные SVG-иконки — `aria-hidden="true"`, интерактивные — с
`aria-label` на кнопке-обёртке.
- Логотип (§1.4) в топбаре — `role="img" aria-label="VidConf"` на SVG внутри
`.brand-mark`, где рядом уже есть текстовое «VidConf» — SVG физически
декоративен (`aria-hidden="true"` в разметке), словесный знак читает
скринридер. Медальон лобби — весь блок `aria-hidden="true"` (дублирует
бренд, не несёт уникальной информации).
- Аватары с инициалами (§1.5) — текст инициалов всегда светлый (`#FFFFFF`)
на заливке `--color-ink-700`/`--color-ink-500`, ≥ 4.5:1 (см.
`design/tools/contrast.py`).
---
## 6. Имплементация для frontend-dev
### shadcn/ui
Тема подключается через CSS-переменные `tokens.css`, а не через хардкод в
`tailwind.config`. Компоненты shadcn/ui кастомизировать так:
| shadcn/ui компонент | Что переопределить | Токены |
|---|---|---|
| `Button` | варианты `default` (pill CTA), `outline` (pill secondary), `destructive`, `ghost` (иконки тулбара) | `--color-accent*`, `--color-border-pill`, `--color-danger*`, `--radius-full` |
| `Input`, `Textarea` | border/focus/error-состояния | `--color-border`, `--color-focus-ring`, `--color-danger` |
| `Card` | радиус, тень, glass-вариант отдельным классом `.card-glass` | `--radius-2xl`, `--shadow-sm/md`, `--shadow-glass` |
| `Badge` | варианты `success` / `danger` / `warning` / `neutral` под статусы конференций (§4.6) | цвета из 1.1 |
| `Dialog` / `Sheet` | модалка брони, панель чата (Sheet справа в тёмной теме) | `--shadow-lg`, `--color-room-surface-raised` |
| `Select` (кастомный, §4.16, не нативный `<select>`) | триггер как `Input` (§4.4), дроплист — паттерн `.menu` (лобби) | триггер `--radius-md`; панель `--radius-lg` + `--shadow-lg`; пункт `--radius-sm`; `--color-border`, `--color-focus-ring` |
| `Tabs` | Вход/Регистрация на auth | `--color-ink-700` активная вкладка, подчёркивание `--color-accent-active` |
| `Table` | админка | `--color-bg-alt` заголовок, `--color-border` разделители |
| `Toggle`/`ToggleGroup` | переключатель layout сетки в комнате, кнопки mic/camera | активное состояние `--color-room-mic-on` / `--color-room-mic-off` |
| `Calendar` (FullCalendar, не shadcn) | обёртка токенами, ячейки — см. 4.10 | — |
| `Avatar` | участники комнаты, аватары в лобби/админке | `--radius-full`; заливка/текст инициалов — см. §1.5 (заливка `ink-700`/`ink-500`, текст всегда `#FFFFFF`) |
| `Tooltip` | подсказки тулбара | `--color-room-surface-raised` |
Тёмная тема room включается локальным скоупом (напр. `data-theme="room"` на
контейнере страницы конференции), а не глобальным `dark:` — остальная оболочка
всегда светлая независимо от системной темы ОС.
### Иконки — lucide-react, контурные, `stroke-width: 2`
| Экран | Иконки |
|---|---|
| **auth** | `Mail`, `Lock`, `Eye`/`EyeOff`, `User`, `CheckCircle2` (подтверждён email), `ArrowRight` |
| **lobby** (хаб действий) | `Video` («Создать»), `CalendarDays` («Календарь»), `Link2` («Подключиться»), `User` («Мои»), бургер-меню: `Download`, `Info`, `LayoutDashboard`, `LogOut` |
| **room** | `Mic`, `MicOff`, `Video`, `VideoOff`, `ScreenShare`, `MessageSquare`, `PhoneOff` (выход), `Users`, `Settings`, `LayoutGrid`, `Columns`, `Grid2x2`, `Maximize2`, `MoreVertical`, `Pin`, `Send`, `ChevronLeft`/`ChevronRight` (лента превью) |
| **calendar** (менеджер конференций) | `ChevronLeft`/`ChevronRight`, `CalendarDays`, `Clock`, `Lock`, `AlertTriangle`, `Check`, `Plus`, `Repeat` (закреплена), `Play` (идёт сейчас), `X` (отменена/отмена), `ChevronDown` (селект «Рассылка саммари», §4.16) |
| **my-conferences** | `Repeat`, `Clock`, `CheckCircle2`, `Users`, `Copy`/`Check` (копирование), `Play` («Войти»), `Pencil`, `FileText` (саммари), `Lock` |
| **join** | `Link2`, `Hash`, `User`, `Mail`, `Lock`, `Eye`/`EyeOff`, `ArrowRight`, `AlertCircle`, `Video` (карточка целевой конференции) |
| **admin** | `LayoutDashboard`, `Video`, `Lock`, `Users`, `Mail`, `Trash2`, `Pencil`, `Search`, `Plus`, `Send`, `ChevronDown`/`Check` (селект «Получатели по умолчанию», §4.16) |
| **email-summary** | инлайн-SVG (email-клиенты не грузят icon-шрифты/JS): `CheckCircle2`, `ListChecks`, `HelpCircle`, `BarChart3`, стрелка CTA |
В HTML-макетах иконки встроены как инлайн-SVG (без иконных шрифтов и CDN);
во frontend — заменяются на `lucide-react` компоненты 1:1 по смыслу.
### Файлы
`design/tokens.css` — источник истины по значениям (используется напрямую или
транслируется в `tailwind.config.*`, см. `design/tailwind.md`).
### Макеты (`design/mockups/`, единый источник истины по разметке)
- `auth.html` — форма входа/регистрации
- `lobby.html`хаб действий (2×2 плитки)
- `calendar.html` — менеджер запланированных конференций
- `my-conferences.html` — мои конференции (закреплённые и предстоящие)
- `join.html` — подключение по ссылке/номеру → представиться → пароль
- `room.html` — конференция (тёмная тема)
- `admin.html` — администрирование
- `email-summary.html` — экспорт-письмо с транскриптом и резюме (всегда
светлое — почтовые клиенты в массе не поддерживают тёмную тему/CSS-переменные,
не участвует в переключателе тем оболочки)
- `dark/*.html` — тёмный вариант оболочки (6 экранов: `login`, `lobby`,
`calendar`, `join`, `my-conferences`, `admin`); палитра — §1.2b, см. `dark/README.md` за происхождением цветов