ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m1s
ci / db / db (push) Successful in 1m16s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m12s
ci / api / api (push) Successful in 45s
ci / recommender / recommender (push) Successful in 1m50s
ci / ts / ts (packages + web) (push) Failing after 6m53s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m13s
- Introduced a new warning for users uploading non-FLAC or WAV formats, indicating that these formats should only be used as a last resort. - Added functionality to highlight files needing format attention in the upload queue. - Updated the UI to provide clearer guidance on acceptable audio formats during uploads. - Improved documentation to reflect changes in upload requirements and user interface updates.
149 lines
13 KiB
Markdown
149 lines
13 KiB
Markdown
# Дизайн и дизайн-система
|
||
|
||
## 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`. Логотип — радиальный градиент `#9CC6FF → #2F80FF → #3A1FA8`.
|
||
|
||
### Типографика
|
||
|
||
| Роль | Шрифт | Где |
|
||
|---|---|---|
|
||
| Заголовки, логотип, надпись на волне | **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`.
|
||
- **Пустые состояния** — иконка, короткий текст и действие там, где список может быть пустым (поиск, коллекция, очередь).
|
||
- Строки интерфейса есть на русском и английском; длинные подписи проверяются на обоих языках.
|