# Архитектура тем оболочки VidConf **Ссылки:** `design/DESIGN_SYSTEM.md` §0–§0.1, `design/mockups/dark/README.md`, `frontend/README.md` раздел «Темы оболочки» ## Суть Оболочка VidConf (auth, лобби, календарь, join, админка) поддерживает две темы: - **Светлая** — дефолт, индиго-на-белом - **Тёмная** — графит-мята-роза-янтарь Переключатель (☀️/🌙) в UI; автодетект системной темы (`prefers-color-scheme`); выбор сохраняется в `localStorage`. Комната конференции — всегда своя тёмная тема, не затрагивается оболочкой. ## Механизм переключения ### Атрибут `data-theme` на `` ``` :root → светлая (индиго-на-белом) :root[data-theme="dark"] → тёмная (мята-на-графите) :root[data-theme="light"] → светлая (явный выбор) [data-theme="room"] → комната (отдельный набор токенов, не переключается) ``` **Приоритет:** 1. Явный выбор (`data-theme="light"` или `data-theme="dark"`), если есть 2. Системная тема (`@media (prefers-color-scheme: dark)`), если явного выбора нет 3. Светлая по дефолту, если ОС не поддерживает `prefers-color-scheme` ### Токены CSS (design/tokens.css) ```css /* Светлая — базовая */ :root { --color-bg: #F1F1F1; --color-ink-900: #2E3454; --color-ink-700: #3B4D95; /* индиго-бренд */ --color-accent: #D4F2E3; /* пастель-мята CTA */ /* … ещё 20+ токенов */ } /* Тёмная — явный выбор пользователя */ :root[data-theme="dark"] { --color-bg: #1E1E1E; --color-ink-900: #E8E8E8; --color-ink-700: #7FDDA8; /* мята вместо индиго */ --color-accent: #7FDDA8; /* мята CTA */ /* … новые значения, тот же набор переменных */ } /* Тёмная — автодетект ОС (если выбора нет) */ @media (prefers-color-scheme: dark) { :root:not([data-theme]) { /* тот же набор, что и [data-theme="dark"] */ } } /* Комната — независимый третий скоуп */ [data-theme="room"] { --color-room-bg: #1E1E1E; --color-room-mic-on: #7FDDA8; /* пастель-мята */ --color-room-mic-off: #EB93A1; /* пастель-роза */ /* … отдельный неймспейс, не трогается переключателем */ } ``` **Ключевой момент:** весь CSS оболочки ссылается на переменные через `var()`, поэтому его не нужно менять под разные темы. Переиспользование `var()` обеспечивает переключение автоматически. ### Сохранение выбора (localStorage) Ключ: `vidconf-theme` (строго совпадает между `frontend/index.html` и `src/hooks/useTheme.ts`). Значения: - `'light'` → `data-theme="light"` на `` - `'dark'` → `data-theme="dark"` на `` - `null` / отсутствует → `data-theme` **не проставляется**, действует системная тема ## Реализация (React) ### Hook: `src/hooks/useTheme.ts` ```typescript export function useTheme() { // Читает явный выбор из localStorage при монтировании const [explicit, setExplicit] = useState<'light' | 'dark' | null>(() => readStoredTheme()) // Живое отслеживание системной темы (matchMedia listener) const [systemPrefersDark, setSystemPrefersDark] = useState(...) // Вычисляет текущую активную тему: явный выбор ИЛИ системная const resolved: 'light' | 'dark' = explicit ?? (systemPrefersDark ? 'dark' : 'light') // Проставляет data-theme на и сохраняет в localStorage const setTheme = (theme: 'light' | 'dark') => { /* … */ } return { resolved, setTheme } } ``` **Особенность:** читает из localStorage ДО первого рендера (через инициализатор `useState`), чтобы синхронизировать с инлайн-скриптом в index.html. ### Компонент: `src/components/ui/ThemeToggle.tsx` ```tsx export function ThemeToggle({ className = '' }: { className?: string }) { const { resolved, setTheme } = useTheme() return (
) } ``` **Монтирование:** - `ShellTopbar.tsx` (лобби, календарь, админка, мои конференции) - `AuthLayout.tsx` (login, register, verify-email) - `JoinPage.tsx` (вход по номеру/ссылке) ### Анти-FOUC: инлайн-скрипт (frontend/index.html) ```html ``` Выполняется **до** бандла React, до `
`. Исключает вспышку светлой темы при загрузке тёмного интерфейса. ## Палитра тёмной оболочки Не новые цвета, а переиспользование уже утверждённых токенов комнаты: | Роль | Светлая | Тёмная | Hex | |---|---|---|---| | Основной текст | `--color-ink-900` (индиго) | `--color-room-text-primary` | `#E8E8E8` | | Бренд/заголовки | `--color-ink-700` (индиго) | `--color-room-mic-on` (мята) | `#7FDDA8` | | CTA заливка | `--color-accent` (пастель-мята) | `--color-room-mic-on` (мята) | `#7FDDA8` | | Статус ошибки | `--color-danger` (красный) | `--color-room-mic-off` (роза) | `#EB93A1` | | Danger-кнопка (заливка) | `--color-danger` | `--color-danger-solid` | `#B85468` | | Warning/focus | `--color-warning` (янтарь) | `--color-room-speaker-ring` (янтарь) | `#D6A83D` | **Новые токены для оболочки** (не в комнате, но получены смешиванием утверждённых цветов): - `--color-accent-text` (`#10331F`) — текст на мятных кнопках - `--color-accent-hover` / `--color-accent-active` — состояния CTA - `--color-ink-400` — подписи в скобках - `--color-success-bg` / `--color-danger-bg` / `--color-warning-bg` — подложки бейджей Подробный расчёт (WCAG 2.1, контраст ≥ 4.5:1) — см. `design/mockups/dark/README.md`. ## Комната конференции (инвариант) `[data-theme="room"]` — скоуп контейнера экрана конференции (обычно на `
`): - Использует отдельный набор токенов `--color-room-*` (§1.2a `DESIGN_SYSTEM.md`) - **Никогда** не переключается на светлую тему - **Не слушает** `prefers-color-scheme` и `data-theme` на `` - Всегда тёмная, независимо от выбора пользователя в лобби Пример в `RoomPage.tsx`: ```tsx return
{/* весь контент комнаты — видео, участники, чат, тулбар */}
``` ## CSS страниц оболочки Каждая страница оболочки (auth, lobby, calendar, join, my-conferences, admin) имеет свой файл стилей: - `frontend/src/styles/auth.css` - `frontend/src/styles/lobby.css` - `frontend/src/styles/calendar.css` - `frontend/src/styles/join.css` - `frontend/src/styles/my-conferences.css` - `frontend/src/styles/admin.css` Они содержат точечные правки сверх токенов (декоративные градиенты, тени, layout-контроль), но **не задают цвета** — цвета задаются через `var()` из `tokens.css`. При переключении темы CSS-переменные меняются автоматически. Отдельные патчи под тёмную тему нужны там, где значение задано литеральным hex, а не через `var()` — см. комментарии в файлах. ## Тестирование ```bash # Проверить, что localStorage-ключ совпадает grep -n 'vidconf-theme' frontend/index.html grep -n 'STORAGE_KEY' frontend/src/hooks/useTheme.ts # Запустить dev сервер cd frontend && npm run dev # Выключить в DevTools: localStorage → удалить vidconf-theme # Проверить, что тема следует prefers-color-scheme ОС # Кликнуть на переключатель (☀️/🌙) # Проверить, что localStorage получил видconf-theme='dark' или 'light' # Перезагрузить (F5) — тема должна восстановиться без вспышки # Открыть RoomPage — комната должна остаться тёмной, несмотря на светлую оболочку ``` ## Ссылки - `design/DESIGN_SYSTEM.md` — полная дизайн-система (§0–§1.2b) - `design/tokens.css` — авторитетный набор переменных (синхронизировано с frontend/src/styles/tokens.css) - `design/mockups/dark/` — макеты тёмной оболочки (утверждены) - `frontend/README.md` — раздел «Темы оболочки» - `design/tools/contrast.py` — скрипт проверки контраста WCAG 2.1 (покрывает и тёмные пары)