Первоначальная версия VidConf

This commit is contained in:
2026-07-23 01:04:01 +03:00
commit 896455381a
335 changed files with 61527 additions and 0 deletions

791
design/DESIGN_SYSTEM.md Normal file
View File

@@ -0,0 +1,791 @@
# 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` за происхождением цветов