# Веб-клиент (`apps/web`) ## Стек | Задача | Выбор | |---|---| | Сборка | Vite, TypeScript (strict) | | UI | React 19 | | Роутинг | TanStack Router (типобезопасные пути и search-параметры) | | Серверное состояние | TanStack Query (кэш, инвалидация, бесконечные списки) | | Клиентское состояние | Zustand (плеер, очередь, UI) | | Стили | CSS Modules + CSS-переменные из `packages/design-tokens`; тёмная и светлая темы ([design.md](design.md#2-токены)) | | Шрифты и иконки | `@fontsource` Onest / Unbounded / JetBrains Mono; иконки — SVG Material Symbols Rounded (`@material-symbols/svg-400`), импортируются поштучно в `shared/ui/icons.ts` и рисуются CSS-маской (`currentColor`), поэтому в сборку попадают только используемые — всё встроено в сборку | | Компоненты-примитивы | Radix UI (доступные меню, диалоги, слайдеры, popover поиска); в M1 — собственные `Menu` / `Dialog` в `shared/ui`, переход на Radix вместе с поиском (M2) | | Анимация | Motion (переходы UI), Canvas 2D для кнопки волны (порт `wave.js` из прототипа) | | Формы | React Hook Form + Zod (в M1 формы простые — контролируемые поля, валидация на сервере с ошибками полей) | | i18n | i18next + react-i18next, словари из `packages/i18n` | | API | `packages/api-client` (сгенерирован из OpenAPI, `openapi-typescript` + `openapi-fetch`) | ## Структура (feature-sliced) ``` apps/web/src/ ├── app/ # провайдеры, роутер, глобальные стили, гарды ├── pages/ # login, setup-admin, setup-password, home, search, artist, album, │ # collection/{history,likes,dislikes}, settings, │ # admin/{users,sources,library,upload,upload-queue,settings} ├── widgets/ # app-shell (топбар, сайдбар), player-bar, wave-button, search-box, track-list ├── features/ # like-track, change-password, mood-picker, create-user, pattern-editor, ... ├── entities/ # track, album, artist, user — модели и маленькие UI-карточки └── shared/ # ui-kit, lib, hooks, config ``` Правило: слой может импортировать только из нижележащих (`pages → widgets → features → entities → shared`), проверяется `eslint-plugin-boundaries`. ## Ключевые компоненты ### App shell Макет — `Pipu Web.dc.html`, каркас **Classic** ([design.md](design.md#4-каркас-и-экраны)). - Сетка: топбар 60 px (логотип, поиск, меню профиля) / сайдбар 240 px (Главная, Коллекция → История / Лайки / Дизлайки, Админка по роли) / контент / плеер. - Плеер в двух видах, выбор в профиле: `bar` — полоса 88 px внизу (по умолчанию), `island` — плавающий остров. - Ambient-фон за всем интерфейсом: три размытых дрейфующих пятна, одно — в цвете обложки текущего трека. - Адаптивность (макетов нет): < 1024 px — сайдбар из иконок, < 768 px — нижняя навигация, как в мобильном. ### Поиск - `SearchBox` — Radix Popover + combobox по WAI-ARIA (стрелки, Enter, Esc). - Debounce 150 мс, отмена предыдущего запроса, `GET /search/suggest`. - Enter без выбранного элемента → `/search?q=…`; Enter / клик по элементу → исполнитель / альбом / запуск трека. - Горячая клавиша `/` или `Ctrl+K` — фокус в поиск. ### Плеер - Движок: `HTMLAudioElement` → Web Audio граф (`MediaElementSource → Gain (нормализация) → Analyser → destination`). `Analyser` даёт спектр для анимации кнопки волны (тот же origin — CORS не мешает). - Два элемента `