Files
pipu-music/docs/design.md
T
mrleo1nid 78c006184b
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 2m30s
ci / ts / ts (packages + web) (push) Successful in 3m29s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 2m10s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 56s
ci / desktop / rust (desktop proxy) (push) Successful in 3m15s
feat(branding): add unified Pipu Music app icons
2026-10-22 07:48:00 +03:00

155 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Дизайн и дизайн-система
## 1. Внешний вид
| Развилка | Решение |
|---|---|
| Кнопка «Моя волна» | **Сфера с шумом (`orb`)** и **Жидкая форма (`liquid`)** — пользователь выбирает в профиле, по умолчанию `orb` |
| Каркас веба | **Classic**: сайдбар 240 px, топбар 60 px, разделители-линии, без «парящих» панелей |
| Плеер веба | **Оба**: `bar` (полоса 88 px внизу, по умолчанию) и `island` (плавающий остров) — выбор в профиле |
| Главная | **Center**: волна по центру, под ней чипы «Занятие» и «Настроение» |
| Очередь на добавление (админ) | Группировка по пачкам (обычно альбом) |
| Полноэкранный плеер Android | `cover` (крупная обложка) по умолчанию, `wave` (визуализация волны) — переключатель в профиле |
| Тема | Тёмная (по умолчанию) и светлая, переключатель в профиле; `system` — следовать ОС |
| Ambient-фон | Включён: размытые дрейфующие пятна, третье пятно окрашено в цвет обложки текущего трека; отключается в профиле и при `prefers-reduced-motion` |
Пользовательские настройки внешнего вида хранятся в `users.ui_prefs` и синхронизируются между устройствами
(см. [data-model.md](data-model.md)).
## 2. Токены
Источник истины — `packages/design-tokens/tokens.json`; из него генерируются CSS-переменные (веб) и TS-объект (React Native).
### Цвета
| Токен | Тёмная | Светлая | Назначение |
|---|---|---|---|
| `bg` | `#05070D` | `#F3F5FA` | Фон приложения |
| `bg2` | `#080B14` | `#EBEEF5` | Вторичный фон |
| `surface` | `#0B0F1A` | `#FFFFFF` | Карточки, панели |
| `surface2` | `#121827` | `#F0F3F9` | Поля ввода, вложенные блоки |
| `surface3` | `#1A2236` | `#E4E9F3` | Чипы, кнопки второго уровня |
| `elev` | `#111726` | `#FFFFFF` | Поповеры, меню |
| `hover` | `rgba(120,150,255,.07)` | `rgba(30,60,140,.06)` | Подсветка при наведении |
| `line` / `line2` | `rgba(130,160,255,.10)` / `.18` | `rgba(20,35,80,.10)` / `.17` | Разделители / рамки |
| `text` / `text2` / `text3` | `#EDF1FF` / `#A0ABC6` / `#7F8BA8` | `#0A0F1E` / `#4B5675` / `#5C6785` | Текст: основной / вторичный / подписи |
| `accent` / `accent2` | `#2F80FF` / `#5E9BFF` | `#1F6FF2` / `#1F6FF2` | Основной акцент, ссылки |
| `accent-fill` / `accent-fill2` | `#1A66E6` / `#1F6FF2` | `#1A63DB` / `#165ED6` | Заливка под белым текстом (кнопки, выбранные сегменты) и её hover — контраст ≥ 4.5:1 |
| `accent-soft` | `rgba(47,128,255,.15)` | `rgba(31,111,242,.10)` | Активный пункт меню, фокус |
| `violet` / `violet-soft` | `#9B7CFF` / `rgba(124,92,255,.16)` | `#6A47F0` / `.10` | Второй акцент (волна, бейджи) |
| `ok` / `ok-soft` | `#2FD39A` | `#0E9465` | Успех |
| `warn` / `warn-soft` | `#FFB547` | `#A86200` | Предупреждение |
| `danger` / `danger-soft` | `#FF5C70` | `#D32F47` | Ошибка, дизлайк, отклонение |
| `overlay` | `rgba(3,5,10,.66)` | `rgba(20,30,60,.32)` | Подложка диалогов |
| `glass` | `rgba(12,16,28,.78)` | `rgba(255,255,255,.84)` | Стеклянные карточки (вход, диалоги) + `backdrop-filter: blur(28px) saturate(1.3)` |
Палитра волны по умолчанию: `#2F80FF`, `#7C5CFF`, `#35D3FF`. Логотип — светлая «p»-нота на сфере с радиальным градиентом `#9CC6FF → #2F80FF → #6354E8 → #3A1FA8`.
Исходник значка — `apps/web/public/favicon.svg`; он же используется в шапке веб-приложения.
Команда `pnpm icons` пересобирает PNG/ICO/ICNS для desktop, favicon PNG, Apple touch icon
и Android-ресурсы в `apps/mobile/assets/`. Android использует отдельные адаптивный цветной
и монохромный слои с отступами для масок лаунчера; фон — `#05070D`.
После изменения значков установленные приложения нужно пересобрать и обновить.
### Типографика
| Роль | Шрифт | Где |
|---|---|---|
| Заголовки, логотип, надпись на волне | **Unbounded** 500–700, `letter-spacing: -0.01em` | Заголовки страниц 24–28 px, «Моя волна» |
| Основной текст | **Onest** 400–700 | Всё остальное; базовый размер 15 px, подписи 12–13 px |
| Моноширинный | **JetBrains Mono** 400/600 | Пути файлов, регэкспы, шаблоны, временные пароли, хэши |
| Иконки | **Material Symbols Rounded** (вариативный, `FILL 0..1`) | Залитые иконки — активное состояние |
Все шрифты — OFL / Apache 2.0. **Встраиваем в сборку** (`@fontsource/*`, сабсеты latin + cyrillic) — self-hosted
сервис не должен зависеть от Google Fonts. Иконки — сабсет Material Symbols только с используемыми глифами
(генерируется скриптом при сборке).
### Формы и тени
| Токен | Значение |
|---|---|
| Радиусы | 8 (мелкие кнопки), 10–12 (поля, кнопки), 16 (карточки), 24 (крупные карточки, диалоги), 999 (чипы, бейджи) |
| Высота поля / основной кнопки | 48 px |
| `shadow` | `0 24px 64px rgba(0,0,0,.55)` (светлая: `rgba(20,30,60,.18)`) |
| `glow` | `0 8px 28px rgba(47,128,255,.38)` — основная кнопка |
| Фокус | `border-color: accent` + `box-shadow: 0 0 0 3px accent-soft` |
### Анимация
| Что | Как |
|---|---|
| Появление контента | `pipuIn`: opacity + translateY(6px), 200 мс |
| Эквалайзер текущего трека | `pipuEq`: scaleY 0.25 → 1 |
| Ambient-фон | 3 пятна 55–70vmax, blur 30 px, дрейф 16 / 21 / 26 с, `hue-rotate` ±35° за 30 с, прозрачность `amb-op` (0.8 тёмная / 0.45 светлая) |
| Пружины (волна, нажатия) | Демпфированная пружина (`v += (target − x)·0.35; v *= 0.62`) |
| `prefers-reduced-motion` | Ambient статичен, волна замедлена ×0.15 без пульса |
## 3. Кнопка «Моя волна»
Кнопка рисуется на **Canvas 2D** (веб) и Skia (Android). WebGL не используется.
```ts
interface WaveButtonProps {
variant: 'orb' | 'liquid';
playing: boolean;
bpm?: number; // из TrackDto.features, по умолчанию 100
energy?: number; // 0..1, по умолчанию 0.6
colors?: [string, string, string]; // палитра обложки текущего трека, иначе палитра волны
theme: 'dark' | 'light';
reduced?: boolean; // prefers-reduced-motion
interactive?: boolean; // false — декоративный фон экрана входа
size: number; // 260–360 px на главной, 620 px на фоне входа
onClick?(): void;
}
```
Поведение:
- **Пульс в такт**: фаза растёт со скоростью `bpm/60`, импульс `(1 − phase)^4 · (0.35 + 0.65·energy)`; в покое — медленное «дыхание».
- **Hover**: скорость анимации ×(1 + 1.4·hover), формы смещаются к курсору; **press**: сжатие до 0.93 с отскоком;
клик / Enter / Space — запуск / пауза.
- Внутри — иконка `play_arrow` / `pause` (44 px) и надпись «Моя волна» (Unbounded 18 px), подпись контекста («Под ваш вкус», «Бегаю · Энергичное»).
- Канвас масштабируется по `devicePixelRatio`; анимация останавливается, когда вкладка скрыта.
- Android: тот же алгоритм на `@shopify/react-native-skia` (API Skia Canvas близок к Canvas 2D), общие формулы —
в `packages/core/wave-anim`.
## 4. Каркас и экраны
### Веб (Classic)
- Сетка: `grid-template-rows: 60px 1fr 88px` (плеер `bar`) или `60px 1fr` + остров снизу (`island`, отступ контента 132 px).
- Топбар: логотип, поиск по центру (выпадающий список с группами «Исполнители / Альбомы / Треки» и подсказкой
исправленной раскладки), аватар с меню профиля (тема, язык, выход).
- Сайдбар: Главная · Коллекция (История, Лайки + счётчик, Дизлайки + счётчик) · Админка (Пользователи, Хранилища,
Библиотека, Предложить треки, Очередь + бейдж, Настройки) — пункты по ролям.
- Плеер: обложка, название / исполнитель, лайк / дизлайк, назад / play (круглая белая кнопка) / вперёд, прогресс со временем,
очередь, громкость. Очередь — выезжающая справа панель; в режиме волны внизу пометка «Волна бесконечна…».
- Диалоги: стеклянные карточки по центру поверх `overlay`. Тосты — внизу над плеером.
Экраны и документы, где описана логика:
| Экран | Логика |
|---|---|
| Вход, смена учётных данных админа, обязательная смена пароля | [auth-and-roles.md](auth-and-roles.md) |
| Главная, поиск, исполнитель, альбом | [web.md](web.md), [library-and-storage.md](library-and-storage.md#поиск) |
| Коллекция: История (по дням) / Лайки / Дизлайки | [api.md](api.md) |
| Профиль: пароль, язык, тема, кроссфейд, выравнивание громкости (EBU R128), вид волны, вид плеера, выход | [playback.md](playback.md) |
| Пользователи: таблица, создание (пароль генерируется автоматически), временный пароль один раз, меню действий | [auth-and-roles.md](auth-and-roles.md) |
| Хранилища: NFS / local, опции, «Принимает новые треки», регэкспы с перетаскиванием, предпросмотр, шаблон раскладки с автоподбором и проверкой | [library-and-storage.md](library-and-storage.md) |
| Библиотека: состояние, скан, аудиоанализ, качество волны за 7 дней, ошибки | [recommendations.md](recommendations.md#1-цели-и-как-измеряем-успех) |
| Предложить треки, Очередь на добавление, Настройки очереди | [uploads.md](uploads.md) |
### Android
- Нижняя навигация: Главная · Поиск · Коллекция · Профиль; мини-плеер над ней; полноэкранный плеер (`cover` / `wave`).
- Подключение: адрес сервера → проверка (`/server-info`: версия, HTTPS, совместимость) → список сохранённых серверов.
- Поиск: недавние запросы (локально), подсказка про опечатки и раскладку.
- Коллекция → Скачанное: занято / свободно, «Скачивать новые лайки», «Скачать все лайки», очередь загрузок «по Wi-Fi».
- Профиль: пароль, язык, качество звука (лист), только Wi-Fi, кэш недавних треков (очистить), проверка обновлений
(точка-индикатор), сервер, «Админка — откроется в браузере» (для персонала), выход.
- Лист обновления: «Что нового» → разрешение на установку (один раз) → загрузка → проверка SHA-256 → «Установить».
- Офлайн-баннер при отсутствии сети.
## 5. Адаптив и онбординг
- **Адаптивность веба**: при ширине < 1024 px сайдбар сворачивается в иконки, при < 768 px — нижняя навигация.
- **Онбординг вкуса** показывается на главной, пока `GET /onboarding` отвечает `required`.
- **Пустые состояния** — иконка, короткий текст и действие там, где список может быть пустым (поиск, коллекция, очередь).
- Строки интерфейса есть на русском и английском; длинные подписи проверяются на обоих языках.