Все цвета — CSS-переменные; тёмная палитра по prefers-color-scheme (режим «авто») и принудительно через data-theme; светлая тема не изменилась. Переключатель — пиктограмма в шапке (авто / светлая / тёмная), выбор в localStorage; theme.js ставит тему до отрисовки CSS (без вспышки, без ослабления CSP). QR 2FA на белой подложке. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7.2 KiB
Тёмная тема 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, цвета загрузки, инлайн-стили.
Изменения
-
Токены. Все цвета в
styles.cssперевести на CSS-переменные в:root. Для каждого прописанного цвета — семантический токен:--card,--input-bg,--shadow,--badge-*-bg/fg,--danger,--warn,--ok,--primary-soft,--overlay… Светлые значения остаются прежними: светлая тема визуально не меняется. -
Тёмная палитра. Та же структура токенов в двух местах:
@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. -
Цвета в
web/app.js. Прописанные цвета (utilColor, инлайнstyle="color:#…", фон полос загрузки) заменить наvar(--…)или классы. Логика выбора (порог загрузки → зелёный/жёлтый/красный) не меняется. Особые случаи:- QR-код 2FA остаётся тёмным на белом: сканеры хуже читают инвертированный. Его контейнеру — белый фон с отступом.
- Коды восстановления и моноширинные блоки — через токены.
-
Переключатель.
web/theme.js(новый), подключается в<head>web/index.htmlобычным синхронным<script src="theme.js">до CSS. Скрипт читаетlocalStorage["ipam.theme"](light/dark; нет значения — «авто») и ставитdata-themeна<html>, поэтому нет вспышки светлой темы. Инлайн-скрипт нельзя: CSPdefault-src 'self'вapp/main.pyего запрещает, ослаблять CSP не нужно.web/app.js: в меню логина пункт «Тема: <текущая>» → вложенный выбор «Авто / Светлая / Тёмная» либо циклическое переключение — выбрать то, что проще вписывается вpopMenu. Запись вlocalStorage(вtry/catch, как уstore), обновлениеdata-themeбез перезагрузки.<meta name="color-scheme" content="light dark">.
-
Документация.
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), без изменений.