Files
pipu-music/docs/design.md
T
mrleo1nidandCursor 066ba24834
ci / api / api (push) Successful in 40s
ci / db / db (push) Successful in 1m3s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Failing after 1m48s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 46s
ci / ts / ts (packages + web) (push) Failing after 4m57s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 5m7s
feat: finish the upload, playback, and test gaps from the docs
Quality upgrades, batch covers, staging preview, and resume after reload are in the queue. Loudness and crossfade work on web and Android, and the documented checks now have suites.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 09:37:53 +03:00

15 KiB
Raw Blame History

Дизайн и дизайн-система

Источник — кликабельные прототипы в Разработка дизайна приложения/:

Файл Что внутри
Pipu Web.dc.html Веб: вход, setup-экраны, главная, поиск, исполнитель, альбом, коллекция, профиль, вся админка, диалоги, очередь плеера
Pipu Android.dc.html Android: подключение к серверу, вход, главная, поиск, коллекция (со «Скачанным»), альбом, исполнитель, профиль, полноэкранный плеер, листы (качество, пароль, очередь, обновление), офлайн-режим
Wave Options.dc.html Три варианта кнопки «Моя волна»
wave.js Реализация кнопки волны (Canvas 2D, React) — референс для продакшен-компонента
pipu-data.js Демо-данные прототипа
support.js, android-frame.jsx Рантайм прототипа и рамка устройства — в продукт не переносятся

Просмотр прототипов: прототипам нужен HTTP-сервер (из file:// скрипты не работают). В репозитории есть конфиг .claude/launch.json (python -m http.server 8765 --directory docs), вручную — та же команда, затем http://localhost:8765/.

1. Принятые решения по вариантам прототипа

Развилка Решение
Кнопка «Моя волна» Сфера с шумом (orb) и Жидкая форма (liquid) — пользователь выбирает в профиле, по умолчанию orb. Вариант «Кольца» не реализуем.
Каркас веба Classic: сайдбар 240 px, топбар 60 px, разделители-линии, без «парящих» панелей
Плеер веба Оба: bar (полоса 88 px внизу, по умолчанию) и island (плавающий остров) — выбор в профиле
Главная Center: волна по центру, под ней чипы «Занятие» и «Настроение»
Очередь на добавление (админ) grouped — группировка по альбомам (вариант прототипа по умолчанию)
Полноэкранный плеер 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 светлая)
Пружины (волна, нажатия) Демпфированная пружина как в wave.js (v += (target − x)·0.35; v *= 0.62)
prefers-reduced-motion Ambient статичен, волна замедлена ×0.15 без пульса

3. Кнопка «Моя волна»

Продакшен-компонент — порт wave.js на TypeScript, Canvas 2D (прототип показал, что этого достаточно; 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.
  • Пустые состояния — иконка, короткий текст и действие там, где список может быть пустым (поиск, коллекция, очередь).
  • Строки интерфейса есть на русском и английском; длинные подписи проверяются на обоих языках.