Files
ayurishchevandClaude Opus 5.5 a6e70ffd1a Задача 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>
2026-09-27 17:44:48 +03:00

7.2 KiB
Raw Permalink Blame History

Тёмная тема 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), без изменений.