Files

60 lines
7.2 KiB
Markdown
Raw Permalink Normal View History

2026-09-27 17:44:48 +03:00
# Тёмная тема UI (изменение 038)
## Context
Сейчас у UI только светлая тема. Нужна тёмная. Решение пользователя: по умолчанию тема следует настройке ОС и браузера
(`prefers-color-scheme`), в меню логина есть переключатель «Тема: авто / светлая / тёмная». Выбор хранится в браузере (`localStorage`).
На экране входа тема тоже берётся по системе или по сохранённому выбору.
Сейчас в `web/styles.css` есть токены в `:root` (`--bg`, `--card`, `--line`, `--text`, `--muted`, `--primary`, `--head`, `--hover` …),
но около 36 цветов прописаны прямо в правилах: бейджи, баннеры, тени, `#fff` у кнопок и `.pop`, `.org-btn` и т. п.
Ещё около 16 цветов задано в `web/app.js`: `utilColor`, цвета загрузки, инлайн-стили.
## Изменения
1. **Токены.** Все цвета в `styles.css` перевести на CSS-переменные в `:root`. Для каждого прописанного цвета —
семантический токен: `--card`, `--input-bg`, `--shadow`, `--badge-*-bg/fg`, `--danger`, `--warn`, `--ok`, `--primary-soft`, `--overlay` …
Светлые значения остаются прежними: светлая тема визуально не меняется.
2. **Тёмная палитра.** Та же структура токенов в двух местах:
- `@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { … } }` — режим «авто»;
- `:root[data-theme="dark"] { … }` — принудительная тёмная.
Плюс `color-scheme: light` / `dark` — нативные поля ввода, скроллбары, автозаполнение.
Палитра: фон `#0f1419`–`#151b23`, карточки на ступень светлее, линии приглушённые, текст `#e6edf3`, `--muted` около `#9aa7b8`,
`--primary` светлее для контраста около `#6b9bff`. Бейджи — тёмный насыщенный фон и светлый текст.
Контраст текста не ниже WCAG AA.
3. **Цвета в `web/app.js`.** Прописанные цвета (`utilColor`, инлайн `style="color:#…"`, фон полос загрузки) заменить на
`var(--…)` или классы. Логика выбора (порог загрузки → зелёный/жёлтый/красный) не меняется.
Особые случаи:
- QR-код 2FA остаётся тёмным на белом: сканеры хуже читают инвертированный. Его контейнеру — белый фон с отступом.
- Коды восстановления и моноширинные блоки — через токены.
4. **Переключатель.**
- `web/theme.js` (новый), подключается в `<head>` `web/index.html` обычным синхронным `<script src="theme.js">` до CSS.
Скрипт читает `localStorage["ipam.theme"]` (`light`/`dark`; нет значения — «авто») и ставит `data-theme` на `<html>`,
поэтому нет вспышки светлой темы. Инлайн-скрипт нельзя: CSP `default-src 'self'` в `app/main.py` его запрещает, ослаблять CSP не нужно.
- `web/app.js`: в меню логина пункт «Тема: <текущая>» → вложенный выбор «Авто / Светлая / Тёмная»
либо циклическое переключение — выбрать то, что проще вписывается в `popMenu`. Запись в `localStorage`
(в `try/catch`, как у `store`), обновление `data-theme` без перезагрузки.
- `<meta name="color-scheme" content="light dark">`.
5. **Документация.** `README.md` («Интерфейс»: тёмная тема, режимы, где хранится выбор; строка 038 в истории);
`SUMMARY.md` по завершении.
## Исполнение
По принятой схеме: код пишет агент на Sonnet, он же пересобирает стенд с `--force-recreate`. Тесты не нужны: API не меняется.
Моя часть — ревью: не осталось прописанных цветов, светлые значения совпадают с прежними, CSP не нарушен.
Визуальная проверка — пользователь.
## Проверка
- `grep` по `#[0-9a-f]{3,6}` и `rgb(` в `styles.css` вне блоков токенов, а также в `app.js` — пусто или обоснованные исключения (QR).
- Светлая тема визуально как раньше. Тёмная: все экраны, диалоги, выпадающие списки, бейджи, журнал, экран входа, 2FA.
- Переключатель: авто следует ОС, выбор переживает перезагрузку, нет вспышки при загрузке.
- Стенд отдаёт новые `styles.css`, `app.js`, `index.html`; CSP-заголовок не менялся, в консоли нет нарушений CSP.
## Доработка по проверке пользователя: переключатель темы — отдельная пиктограмма в шапке
Переключатель не должен быть частью меню пользователя. Вместо пунктов «Тема: …» в `popMenu("user", …)`:
- **Кнопка.** Отдельная кнопка-пиктограмма в шапке (`shell()`) слева от логина; `iconBtn` или `btn ghost` без текста, `aria-label` и `title` —
«Тема: авто/светлая/тёмная». Пиктограмма отражает текущий режим: солнце — светлая, луна — тёмная, монитор — авто (по системе).
Иконки — inline SVG в стиле существующего набора `I` (`stroke="currentColor"`, 16px).
- **Выбор.** По клику — общий выпадающий список `popMenu("theme", […], "row-pop")`: «Авто (как в системе)», «Светлая», «Тёмная»,
у текущей — `cls:"sel"`. Выбор — ветка `id === "theme"` в `actions.pick`, существующая `setTheme()`, затем `draw()`.
- **Меню пользователя.** Возвращается к трём пунктам: «Сменить пароль», «Двухфакторная аутентификация», «Выйти».
- **Экран входа.** Шапки нет, тема берётся по системе или по сохранённому выбору (`theme.js`), без изменений.