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.
13 KiB
13 KiB
Дизайн и дизайн-система
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).
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 не используется.
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 |
| Главная, поиск, исполнитель, альбом | web.md, library-and-storage.md |
| Коллекция: История (по дням) / Лайки / Дизлайки | api.md |
| Профиль: пароль, язык, тема, кроссфейд, выравнивание громкости (EBU R128), вид волны, вид плеера, выход | playback.md |
| Пользователи: таблица, создание (пароль генерируется автоматически), временный пароль один раз, меню действий | auth-and-roles.md |
| Хранилища: NFS / local, опции, «Принимает новые треки», регэкспы с перетаскиванием, предпросмотр, шаблон раскладки с автоподбором и проверкой | library-and-storage.md |
| Библиотека: состояние, скан, аудиоанализ, качество волны за 7 дней, ошибки | recommendations.md |
| Предложить треки, Очередь на добавление, Настройки очереди | uploads.md |
Android
- Нижняя навигация: Главная · Поиск · Коллекция · Профиль; мини-плеер над ней; полноэкранный плеер (
cover/wave). - Подключение: адрес сервера → проверка (
/server-info: версия, HTTPS, совместимость) → список сохранённых серверов. - Поиск: недавние запросы (локально), подсказка про опечатки и раскладку.
- Коллекция → Скачанное: занято / свободно, «Скачивать новые лайки», «Скачать все лайки», очередь загрузок «по Wi-Fi».
- Профиль: пароль, язык, качество звука (лист), только Wi-Fi, кэш недавних треков (очистить), проверка обновлений (точка-индикатор), сервер, «Админка — откроется в браузере» (для персонала), выход.
- Лист обновления: «Что нового» → разрешение на установку (один раз) → загрузка → проверка SHA-256 → «Установить».
- Офлайн-баннер при отсутствии сети.
5. Адаптив и онбординг
- Адаптивность веба: при ширине < 1024 px сайдбар сворачивается в иконки, при < 768 px — нижняя навигация.
- Онбординг вкуса показывается на главной, пока
GET /onboardingотвечаетrequired. - Пустые состояния — иконка, короткий текст и действие там, где список может быть пустым (поиск, коллекция, очередь).
- Строки интерфейса есть на русском и английском; длинные подписи проверяются на обоих языках.