Задача 038: тёмная тема UI

Все цвета — CSS-переменные; тёмная палитра по prefers-color-scheme (режим «авто»)
и принудительно через data-theme; светлая тема не изменилась. Переключатель —
пиктограмма в шапке (авто / светлая / тёмная), выбор в localStorage; theme.js ставит
тему до отрисовки CSS (без вспышки, без ослабления CSP). QR 2FA на белой подложке.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Opus 5.5 committed 2026-09-27 17:44:48 +03:00
1 parent d3495fb077
commit a6e70ffd1a
7 files changed
+221 -47

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
# Тёмная тема 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`), без изменений.
+27
View File
@@ -0,0 +1,27 @@
# Итог: тёмная тема UI (изменение 038)
План: `PLAN.md` (с разделом доработки по проверке пользователя). У UI появилась тёмная тема. По умолчанию тема следует настройке ОС и браузера (`prefers-color-scheme`),
её можно переключить пиктограммой в шапке: авто / светлая / тёмная. Выбор хранится в браузере (`localStorage["ipam.theme"]`).
## Что сделано
| Файл | Изменение |
|---|---|
| `web/styles.css` | Все цвета — CSS-переменные в `:root`, светлые значения прежние. Тёмная палитра в двух одинаковых блоках: `@media (prefers-color-scheme: dark){:root:not([data-theme="light"])}` для режима «авто» и `:root[data-theme="dark"]` для принудительной тёмной; `color-scheme`. Контраст — WCAG AA; в тёмной теме у основных кнопок тёмный текст на светло-синем фоне (белый дал бы ≈2.7:1) |
| `web/theme.js` (новый) | Синхронно в `<head>` до CSS ставит `data-theme` по сохранённому выбору — без вспышки светлой темы. Отдельный файл, так как CSP `default-src 'self'` запрещает инлайн-скрипты; CSP не менялся |
| `web/index.html` | `<meta name="color-scheme" content="light dark">`, подключение `theme.js` |
| `web/app.js` | Цвета (`utilColor`, полосы загрузки, инлайн-стили, иконка логотипа) — через токены. Кнопка-пиктограмма темы в шапке слева от логина (солнце / луна / монитор) с выпадающим списком «Авто (как в системе) / Светлая / Тёмная». QR 2FA — на белой подложке (`.qr-frame`) ради сканируемости |
| `README.md` | «Интерфейс» — тёмная тема и переключатель; строка 038 в истории |
## Доработка по проверке пользователя
Сначала переключатель был пунктами «Тема: …» в меню логина. По замечанию пользователя вынесен в отдельную пиктограмму на панели шапки;
в меню пользователя остались «Сменить пароль», «Двухфакторная аутентификация», «Выйти».
## Проверки
- Светлая тема не изменилась: во всех 49 объявлениях с цветом прежнего `styles.css` после подстановки светлых токенов значения совпадают (скриптовое сравнение).
- Литеральные цвета вне токенов:
- `.qr-frame` (белый) — QR 2FA;
- стрелка `<select>` — SVG в `url()`, куда переменную не подставить, поэтому она переопределена в тёмных блоках.
Оба тёмных блока идентичны; заголовок CSP прежний.
- `node --check` — чисто; стенд пересобран (`--force-recreate`), отдаёт новые `styles.css`, `app.js`, `theme.js`.
- Визуальная проверка обеих тем и переключателя — пользователь.