# 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` | светлый фон, глубокий индиго в типографике, крупные скругления (20–32px), 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"` на `` (или другом предке `: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` — упрощённая версия для мелких размеров (16–32px): меньше колец, толще штрихи, чтобы силуэт не «замыливался». Используется как иконка в топбаре рядом со словом «VidConf» во всех макетах, а также как browser favicon / app-иконка продакшен-сборки. **Конструкция (обе версии):** - центр — плоский шестигранник (`polygon`), закрытая диафрагма объектива, залита сплошным цветом; - вокруг — концентрические окружности (4 в мастер-версии, 1 в favicon), каждая нарисована как `` со `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` внутри `.avatar-btn`, значит попадал под общее правило `.avatar-btn span`) — инициалы рендерились тёмным `ink-900` на индиго-заливке, нечитаемо. Специфичность решает независимо от порядка правил в файле, поэтому простое переупорядочивание CSS не спасает. **Правило на будущее:** имя рядом с аватаром — всегда через адресный класс (`.user-name`, не голый `span`), никогда `.wrapper span` без уточнения — такой селектор непреднамеренно задевает ``, если он лежит в той же обёртке. Проверено 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` (визуально 24–32px). `--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`, иконка 12–14px слева от текста. | Статус | Фон | Текст | Иконка | |---|---|---|---| | Запланирована (разовая) | `--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` (13–15px, `--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 даты вхождения | | Раз в месяц | число месяца (1–31) | «если в месяце нет такого числа — переносится на последний день месяца» (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 Селект (кастомный выпадающий список) Заменяет нативный ``, чтобы стилизация дроплиста была полностью в токенах проекта. | Действие | Клавиша | Поведение | |---|---|---| | Открыть список | `Enter`, `Space`, `ArrowDown`, `ArrowUp` (фокус на триггере, список закрыт) | раскрывает дроплист; подсветка ставится на выбранный пункт (если значения ещё нет — на первый) | | Навигация | `ArrowDown` / `ArrowUp` (список открыт) | двигает клавиатурную подсветку на следующий/предыдущий пункт (циклически не переходит с последнего на первый), обновляет `aria-activedescendant` триггера — фокус физически остаётся на триггере | | К началу/концу | `Home` / `End` | подсветка на первый/последний доступный (не disabled) пункт | | Быстрый переход | ввод буквы | подсветка прыгает на ближайший следующий пункт, начинающийся с введённого символа (стандартное ожидание пользователей от ``) | триггер как `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` за происхождением цветов