UI: unified design system, light/dark themes, redesigned pages and player UI
Tokens and components in styles.css, shared Ui (dialogs, toasts), icons, bottom tab bar on mobile, keyboard/gamepad navigation, library shelf with filters and conversion badges, hero game page, restyled player screens. Games list now returns per-game version counts. Adds docs/UI_DESIGN.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
51a7e77e56
commit
9ee3e1ff6e
27 files changed
+2399
-508
No files matched your search
@@ -0,0 +1,123 @@
|
||||
# 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`.
|
||||
- **Поля**: `<label className="field"><span>Подпись</span><input/> <span className="hint">…</span></label>`.
|
||||
Формы — `.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 + `<i/>`).
|
||||
- **Пустое состояние** — `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`. Для карточек используйте `<Link>`.
|
||||
- Уважайте `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` вместо кнопок, иконки без подписи.
|
||||
Reference in new issue
Block a user