Файл index.html — чистая статика, без сборки и внешних зависимостей (иконки inline‑SVG, шрифты системные). Варианты:
nginx, S3/OSS, GitHub Pages.localStorage и переживают перезагрузку.Рекомендуется WebSocket (real‑time: прогресс, заряд, смена трека). HTTP‑опрос состояния тоже поддерживается — тот же JSON.
| Направление | Формат | Куда в коде |
|---|---|---|
| Бэк → UI | {event,data} | applyRemoteState(data) |
| UI → бэк | {action,...} | sendCommand(action,payload) |
player_state_update{
"event": "player_state_update",
"data": {
"source": "Deezer", // имя сервиса -> иконка+цвет в eyebrow
"is_playing": true,
"volume": 75, // 0..100
"battery_level": 85, // 0..100 заряд колонки
"current_track": {
"id": "tr_99821",
"title": "Blinding Lights",
"artist": "The Weeknd",
"album_cover_url": "https://...", // null/битый -> авто-плейсхолдер
"duration_sec": 240,
"current_position_sec": 45,
"jev_confidence": 92 // 0..100 процент JEV
},
"queue": [ // порядок: prev, current, next
{ "album_cover_url": "https://..." },
{ "album_cover_url": "https://..." },
{ "album_cover_url": "https://..." }
]
}
}
Поля queue и current_track опциональны: если queue нет — карусель строится по локальному списку; если нет current_track — берутся данные из queue[currentIndex].
| Действие | Payload | Триггер в UI |
|---|---|---|
play_pause | — | кнопка Play |
stop | — | кнопка Stop |
next_track / prev_track | — | кнопки, клик по боковой обложке, свайп |
seek | position_sec | клик/драг по прогресс‑бара |
set_volume | level 0..100 | слайдер громкости |
search_and_play | query, source | Enter / кнопка поиска |
source в поиске: all | novidrome | yandex | deezer (значение чипа над полем).Транспортные функции уже написаны — останется только вставить WebSocket. Внизу JS найдите блок // ===== ТРАНСПОРТ =====:
// 1) открыть соединение
const ws = new WebSocket('wss://hermes.local/ws');
// 2) входящие -> в UI
ws.onmessage = (e) => {
const m = JSON.parse(e.data);
if (m.event === 'player_state_update') applyRemoteState(m.data);
if (m.event === 'search_result') finishSearch(!!m.ok);
if (m.event === 'set_theme') applyTheme(m.theme);
if (m.event === 'set_search_source') setActiveSource(m.source);
};
// 3) исходящие -> в бэк (заменить console.log внутри sendCommand)
function sendCommand(action, payload = {}) {
ws.send(JSON.stringify({ action, ...payload, ts: Date.now() }));
}
После этого поставьте DEMO_MODE = false (низ JS) и удалите демо‑таймеры, помеченные // DEMO — состояние будет приходить только от бэка.
Иконки не нужны отдельными файлами — они лежат в одном объекте SOURCE_REGISTRY и оттуда же рендерятся чипы поиска и eyebrow над треком. Расширение = одна запись:
SOURCE_REGISTRY.apple = {
label: 'Apple Music', short: 'Apple',
color: '#FA57C1',
chipBg: 'rgba(250,87,193,.16)',
icon: '<svg viewBox="0 0 24 24" ...>...</svg>', // ваш inline-SVG
match: ['apple'] // подстроки имени от бэка
};
| ключ | иконка | цвет | match (регистронезав.) |
|---|---|---|---|
all | сетка 2×2 | акцент темы | — (только поиск) |
novidrome | столбцы EQ | #FF6B4A | novid |
yandex | жёлт. квадрат + нота | #FFCC00 | яндекс / yandex |
deezer | цветной EQ | #A238FF | deezer |
| fallback | нота | серый | любое неизвестное имя |
Неизвестный сервис из бэка не ломает UI: показывается его имя как есть + резервная иконка.
6 тёмных тем: cyberpunk (дефолт), midnight, forest, sunset, ocean, nord. Каждая — набор CSS‑переменных; иконки‑плейсхолдеры и акценты автоматически подхватывают цвета активной темы. Выбор сохраняется в localStorage. Бэк может переключать тему событием {event:'set_theme',theme:'nord'}.
Если album_cover_url = null/пустая или картинка не загрузилась (404/битый DNS) — UI мгновенно ставит стильный градиентный плейсхолдер «винил + нота» в цветах темы; при воспроизведении пластинка вращается. Битые URL кэшируются, чтобы со второго раза плейсхолдер появлялся без задержки сети.