# Дизайн и дизайн-система Источник — кликабельные прототипы в [`Разработка дизайна приложения/`](Разработка%20дизайна%20приложения/): | Файл | Что внутри | |---|---| | `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](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` / `#737F9C` | `#0A0F1E` / `#4B5675` / `#677290` | Текст: основной / вторичный / подписи | | `accent` / `accent2` | `#2F80FF` / `#5E9BFF` | `#1F6FF2` / `#1F6FF2` | Основной акцент, ссылки | | `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 не нужен). ```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. Пробелы дизайна (дорабатываем при реализации) - **Адаптивность веба**: прототип рассчитан на десктоп (≥ 1200 px). Для < 1024 px сайдбар сворачивается в иконки, для < 768 px — нижняя навигация как в Android. Макетов нет — делаем по образцу Android-версии. - **Пустые состояния и ошибки** есть не везде (например, хранилище недоступно, нет сети в вебе) — по образцу существующих (иконка + короткий текст + действие). - **Онбординг вкуса** (выбор жанров / исполнителей при первом входе) в прототипе отсутствует — макет нужен до M6. - **Английская локализация**: в прототипе только русские строки; при вёрстке проверять длинные строки на обоих языках.