Web: bring theme toggle to the login page for consistent UX

The app and stats pages already use an explicit data-theme attribute
approach — reading the saved "repeater-theme" key from localStorage
and falling back to the OS preference — with a ☾/☀ toggle button in
the UI.  The login page was the only page still relying solely on the
CSS @media (prefers-color-scheme) query with no manual override.

This commit mirrors the same pattern on the login page so the theme
switcher works consistently across all pages:

- Add --surface2 CSS variable to both light and dark theme blocks
- Replace @media (prefers-color-scheme: dark) with :root[data-theme="dark"]
- Add a theme toggle button (☾/☀) in the login card header
- Add getPreferredTheme(), applyTheme(), and toggleTheme() helpers,
  identical in behaviour to those already present on the app page
This commit is contained in:
Valentin V. Bartenev
2026-05-09 17:40:51 +03:00
parent 34192fa884
commit d49af5d785
+37 -12
View File
@@ -225,18 +225,18 @@ const char kWebPanelLoginHtml[] PROGMEM = R"HTML(
--text-muted:#4b5563; --text-muted:#4b5563;
--border:rgba(0,0,0,.08); --border:rgba(0,0,0,.08);
--surface:#ffffff; --surface:#ffffff;
--surface2:#f0f3f7;
} }
@media (prefers-color-scheme: dark) { :root[data-theme="dark"] {
:root { color-scheme: dark;
color-scheme: dark; --accent:#36a167;
--accent:#36a167; --accent-hover:#49c27d;
--accent-hover:#49c27d; --background:#222222;
--background:#222222; --text:#e6eaf0;
--text:#e6eaf0; --text-muted:#9aa4b2;
--text-muted:#9aa4b2; --border:rgba(255,255,255,.08);
--border:rgba(255,255,255,.08); --surface:#303030;
--surface:#303030; --surface2:#2a2a2a;
}
} }
html { min-height:100%; background:linear-gradient(180deg,var(--background),var(--surface)); background-repeat:no-repeat; background-attachment:fixed; } html { min-height:100%; background:linear-gradient(180deg,var(--background),var(--surface)); background-repeat:no-repeat; background-attachment:fixed; }
body { min-height:100vh; margin:0; display:grid; place-items:center; background:transparent; color:var(--text); font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace; } body { min-height:100vh; margin:0; display:grid; place-items:center; background:transparent; color:var(--text); font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono",monospace; }
@@ -249,12 +249,17 @@ const char kWebPanelLoginHtml[] PROGMEM = R"HTML(
button { border:none; background:var(--accent); color:#fff; cursor:pointer; font-weight:700; } button { border:none; background:var(--accent); color:#fff; cursor:pointer; font-weight:700; }
button:hover { background:var(--accent-hover); } button:hover { background:var(--accent-hover); }
#status { min-height:1.4em; margin-top:12px; color:#c94a4a; white-space:pre-wrap; } #status { min-height:1.4em; margin-top:12px; color:#c94a4a; white-space:pre-wrap; }
.themebtn { background:var(--surface2,var(--background)); color:var(--text); border:1px solid var(--border); }
.themebtn:hover { background:var(--surface); }
</style> </style>
</head> </head>
<body> <body>
<main> <main>
<section class="card"> <section class="card">
<h1>Repeater Config</h1> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<h1 style="margin:0">Repeater Config</h1>
<button id="themeToggle" class="themebtn" style="width:auto;min-height:unset;padding:10px 14px;">☾</button>
</div
<p>Use the repeater admin password to unlock the panel.</p> <p>Use the repeater admin password to unlock the panel.</p>
<input id="password" type="password" placeholder="Admin password" autocomplete="current-password"> <input id="password" type="password" placeholder="Admin password" autocomplete="current-password">
<button id="loginBtn">Unlock</button> <button id="loginBtn">Unlock</button>
@@ -262,6 +267,26 @@ const char kWebPanelLoginHtml[] PROGMEM = R"HTML(
</section> </section>
</main> </main>
<script> <script>
const rootEl = document.documentElement;
const themeToggleEl = document.getElementById("themeToggle");
function getPreferredTheme() {
const saved = localStorage.getItem("repeater-theme");
if (saved === "light" || saved === "dark") return saved;
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
function applyTheme(theme) {
rootEl.dataset.theme = theme;
themeToggleEl.textContent = theme === "dark" ? "" : "";
themeToggleEl.title = theme === "dark" ? "Switch to light mode" : "Switch to dark mode";
}
function toggleTheme() {
const next = rootEl.dataset.theme === "dark" ? "light" : "dark";
localStorage.setItem("repeater-theme", next);
applyTheme(next);
}
applyTheme(getPreferredTheme());
themeToggleEl.onclick = toggleTheme;
const statusEl = document.getElementById("status"); const statusEl = document.getElementById("status");
function getPreferredPage() { function getPreferredPage() {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);