# Design ## Context Фронтенд — SvelteKit 2 + Svelte 5, `adapter-static` в режиме SPA (`ssr=false`), собирается в статику и вшивается в Go-бинарник через `go:embed`. Стили — Tailwind v4, семантические токены через CSS-переменные и `data-theme` на `` (темы light/dark/neutral + system). Внешние CDN-запросы нежелательны: приложение self-hosted и должно работать полностью автономно. Сейчас все «иконки» — эмодзи и юникод-глифы, рендерящиеся системным шрифтом (мотивация — в proposal.md). ## Goals / Non-Goals **Goals:** - Единый набор векторных иконок, самодостаточный (без внешних запросов и без иконочных шрифтов). - Иконки наследуют цвет текущей темы и не влияют на размеры/раскладку при переключении тем. - Переиспользуемый компонентный API, спрайт для дедупликации повторяющихся иконок. - Доступность: корректное поведение для декоративных и смысловых иконок и для тумблеров. **Non-Goals:** - Не менять доменную логику, API, БД и backend. - Не вводить анимации/переходы иконок. - Не добавлять внешние зависимости (иконочные пакеты, Vite-плагины). - Не делать полный редизайн макета — только замена иконок и ведущие иконки контролов. ## Decisions ### 1. Inline SVG через спрайт `` + `` В `+layout.svelte` монтируется один скрытый `` со всеми ``; места использования рисуют ``. - **Почему:** нет внешних запросов, работает внутри статичного бандла, `currentColor` наследуется через ``, а данные пути хранятся один раз в DOM (важно для сеток, где `favorite`/`mode_comment` повторяются на каждой плитке). - **Альтернативы (отклонены):** иконочный шрифт через CDN — внешний запрос, FOUT, ломает автономность; внешний файл-спрайт `` — межфайловые `` имеют ограничения в ряде браузеров; «иконка = компонент с полным inline-путём» — дублирование path-данных в DOM при массовом повторении. ### 2. Вендорим данные иконок в `registry.ts`, без зависимостей Реестр — объект `имя -> { path }`, где `path` — сырая inner-разметка (``, иногда с `fill-rule`/`clip-rule`). Источник — Google Material Symbols (через Iconify, prefix `material-symbols`); базовые имена дают стиль **filled**, поэтому набор вендорится в filled-стиле, а для состояний-пар берутся отдельные ключи (`favorite` = filled, лайкнут; `favorite-outline` = контурный, не лайкнут). Примечание: контурного варианта для всех иконок в источнике нет, поэтому единый filled-набор согласованнее, чем смесь стилей. - **Почему:** ноль зависимостей и supply-chain, малый бандл, полный контроль, согласуется с философией «один статичный бинарник». - **Альтернативы (отклонены):** `@mdi/js` — готовые path-строки, но новая зависимость и другой визуальный набор; Iconify + `unplugin-icons` — мощный DX, но тяжёлый тулчейн ради ~25 иконок. - **Задел:** при росте набора переход на `@mdi/js` не меняет компонентный API — только источник данных реестра. ### 3. Компонентный API - `Icon.svelte`: пропсы `name`, необязательный `label`; размер и цвет задаются классами (`size-4/5/6/16`, `text-*`). По умолчанию `aria-hidden="true" focusable="false"`; при заданном `label` — `role="img"` + `aria-label` и `aria-hidden` снимается. - `IconSprite.svelte`: рендерит все `` из реестра один раз, монтируется в `+layout.svelte`. - `IconButton.svelte`: иконочная кнопка с обязательным `label` (попадает в `aria-label`). - **Неизвестное имя:** в dev — `console.warn` и пустой бокс (не падать); в prod — пусто. ### 4. Стиль и метрики - `fill="currentColor"`, шкала размеров: `size-4` (в тексте/счётчиках), `size-5` (кнопки/навигация), `size-6` (панель лайтбокса), `size-16` (плейсхолдеры). - Пара состояний лайка: `favorite-outline` (не лайкнут) / `favorite` (залитая, лайкнут). ### 5. Маппинг иконок тем `light_mode`, `dark_mode`, `palette` (нейтральная), `brightness_auto` (системная). ### 6. Доступность - Декоративные иконки рядом с текстом/внутри кнопки с текстом — `aria-hidden`. - Одиночные иконочные кнопки (тумблеры тем) — `aria-label` на `