# UI: дизайн-система PGames Документ для тех, кто добавляет или дорабатывает интерфейс. Прочитайте перед правкой `frontend/`. Код — источник истины, здесь только правила и карта. ## Принципы 1. **«Кинотека»**: спокойный тёмный фон, обложки — главные, один акцент (оранжевый). 2. **Одна система.** Цвета, отступы, радиусы — только через токены (`--accent`, `--space-3`…). Никаких захардкоженных `#hex` и «магических» px в страницах. 3. **Две темы** (тёмная по умолчанию, светлая, «как в системе»). Всё, что вы добавите, должно выглядеть нормально в обеих — проверяйте обе. 4. **Все устройства**: десктоп, планшет, телефон, ТВ с геймпадом. Мышь — не единственный сценарий. 5. **Без нативных диалогов.** `alert/confirm/prompt` запрещены (ломаются в полноэкранном режиме и на геймпаде) — используйте `useUi()`. 6. **Язык интерфейса — русский**, тон короткий и без жаргона. ## Карта файлов | Файл | Что внутри | |---|---| | `src/styles.css` | Весь CSS сайта: токены и темы, контролы, поверхности, шапка, страницы, адаптив | | `src/theme.ts` | `useTheme()` — выбор темы (`system`/`dark`/`light`), хранится в localStorage `pg-theme` | | `src/ui/Icon.tsx` | Набор иконок (stroke, 24px). Новая иконка — добавить путь в `PATHS` | | `src/ui/Ui.tsx` | `useUi()` → `confirm`, `prompt`, `toast` (провайдер в `main.tsx`) | | `src/ui/Bits.tsx` | `PageHeader`, `EmptyState`, `ErrorNote`, `Spinner`, `Cover` | | `src/ui/nav.ts` | Навигация стрелками и геймпадом (`useSpatialNav`) | | `src/ui/ThemeButton.tsx` | Кнопка темы для экранов без шапки | | `src/recent.ts` | «Продолжить игру» (localStorage `pg-recent`) | | `src/player/player.css` | Стили плеера (отдельная система, см. ниже) | ## Токены (`:root`) - Фоны: `--bg`, `--surface`, `--surface-2`, `--surface-3` (от дальнего к ближнему слою). - Границы: `--border`, `--border-strong`. Текст: `--text`, `--text-2`, `--muted`. - Акцент: `--accent`, `--accent-hover`, `--accent-ink` (цвет текста на акценте), `--accent-soft` (фон-подсветка). - Семантика: `--ok`, `--danger`, `--info` и их `*-soft`. - Геометрия: `--radius-s` 8 / `--radius` 12 / `--radius-l` 18; `--space-1…6` (4–36px); `--control-h` — высота контролов (растёт на тач-устройствах). - Тень: `--shadow-1/2/3`. Оверлей: `--overlay`. Светлая тема переопределяет токены в `:root[data-theme="light"]` и в `@media (prefers-color-scheme: light)` для `:root:not([data-theme])`. **Добавляя токен, добавьте его во все три блока.** ## Компоненты (CSS-классы) - **Кнопки** `.btn` + модификатор: `.btn-primary` (одна главная на экран), `.btn-ghost`, `.btn-danger` (контурная), `.btn-danger-solid` (в диалоге удаления), размеры `.btn-sm`, `.btn-lg`, `.btn-icon`, `.btn-block`. Иконка слева, 16–18px (в `-lg` 20–22). Кнопка только с иконкой обязана иметь `aria-label` и `title`. - **Поля**: ``. Формы — `.form`, ряд полей — `.form-row`. - **Карточка** `.card`; заметки `.note .note-error|ok|info` (через `ErrorNote`). - **Статусы** `.badge` + `badge-ok|warn|error|plain`, `busy` — пульсирующая точка для процесса. - **Чипы** `.chip` (`.on` — выбран), **теги** `.tag`. - **Таблицы** `.table` в `.table-wrap`. На телефоне таблицу с действиями заменяйте карточками (пример — `Users.tsx`, `.users-table` / `.user-cards`). - **Переключатель права** `.switch` (input + ``). - **Пустое состояние** — `EmptyState`, загрузка списков — `.skeleton`, загрузка действия — `Spinner`. - **Меню профиля** — `UserMenu` в `App.tsx`; админ-разделы живут там, не в основной навигации. ## Правила для страниц - Обёртка `.page` (`.narrow` 600px / `.medium` 960px для форм и админки). - Заголовок — `PageHeader` (title, subtitle, actions справа). - Одна главная кнопка (`btn-primary`) на экран/блок. Опасные действия — `btn-danger` и **всегда** через `ui.confirm({ danger: true })` с пояснением последствий. - Результат действия — `ui.toast(text, "ok" | "error")`. Ошибки загрузки страницы — `ErrorNote` в потоке. Не оставляйте «молчаливых» действий. - Любой список имеет три состояния: загрузка (скелетон/спиннер), пусто (`EmptyState` с подсказкой, что делать), данные. - Ввод имени/значения — `ui.prompt`, а не `window.prompt`. - Тексты: кнопки — глагол («Создать форк»), подтверждение — конкретное действие, не «Ок». ## Адаптив и устройства - Точка перехода на телефон — **860px**: верхняя навигация скрывается, появляется нижний `.tabbar` (до 5 пунктов, иконка + подпись), профиль — по аватару. Узкий телефон — **520px** (сетка в 2 колонки). - `@media (pointer: coarse)`: контролы выше (2.9rem). Минимальная зона касания 44px. - ТВ: от 1700px увеличивается шрифт, от 2400px ещё больше. Не фиксируйте размеры в px там, где можно `rem`. - **Фокус всегда виден** (`:focus-visible` — 3px акцентная рамка). Не убирайте `outline`. - Клавиатура/геймпад: стрелки и D-pad двигают фокус к ближайшему элементу (`nav.ts`), A — нажать, B — назад. Для этого интерактивные элементы должны быть настоящими `button`/`a`/`input`, а не `div onClick`. Для карточек используйте ``. - Уважайте `prefers-reduced-motion` (уже глобально сокращает анимации). ## Плеер (`src/player`) Разделяется на два слоя, их стили не смешиваются: 1. **Игровой слой** (`.pg-textbox`, `.pg-namebox`, `.pg-text`, `.pg-menu`, `.pg-choice`, NVL, centered) — должен выглядеть как оригинальная новелла. Цвета и шрифты может переопределять сама игра (`--pg-accent`, `--pg-text`, шрифты). Не привязывайте его к токенам сайта. 2. **Служебный UI плеера** (`.pg-quick`, `.pg-screen`, `.pg-main-panel`, слоты, настройки, `.pg-dialog`, `.pg-hud-btn`) — единый стиль «стеклянных» панелей поверх игры, одинаковый для всех игр. Нюансы: - Сцена масштабируется `transform: scale()`, размеры внутри — в `em` от `--pg-font`. Поэтому **кнопки для касания выносите за сцену** (как `.pg-hud-btn` в `.pg-root`, фиксированные px). - Подтверждения внутри плеера — `Engine.confirmAsk()`, не `confirm()`. - Новые экраны открывайте через `openScreen(title, build)`: заголовок, кнопка закрытия и Esc уже есть. - Кнопки в экранах — обычные `button`, чтобы работала навигация геймпадом (`nav.ts` ограничивается видимым `.pg-screen` / `.pg-menu`). - Управление: Esc — меню, Space/Enter — дальше, `A` — авто, `H` — скрыть UI, Ctrl/Tab — пропуск; геймпад: A/B/X/Y. ## Как добавить новый экран (чек-лист) 1. Страница в `src/pages`, маршрут и `RequirePerm` в `App.tsx`; пункт в нижнем `.tabbar` — только если это основной раздел (иначе в меню профиля). 2. `PageHeader`, состояния загрузки/пусто/ошибка, действия через `useUi`. 3. Только токены и существующие классы; новый повторяющийся паттерн — в `styles.css` (раздел по смыслу), не inline. 4. Проверка: обе темы; ширины 375 / 768 / 1280 / 1920; навигация только клавиатурой (Tab и стрелки); `npm run build` без ошибок. ## Чего не делать - `alert/confirm/prompt`, `window.open` для служебных окон. - Цвета и размеры «от руки», `!important` (исключение — уже существующие в плеере). - Анимации длиннее ~200 мс на интерактивных элементах. - Информация только цветом: у статуса всегда есть текст. - Скрытие фокуса, `div` вместо кнопок, иконки без подписи.