feat(web): light/dark mode cu comutator persistat + anti-FOUC (PRD 5.3)
Tema light ca bloc [data-theme="light"] peste variabilele :root (dark nemodificat la octet). Comutator soare/luna in header pe toate paginile, default OS-aware (prefers-color-scheme, fallback dark), persistenta in localStorage doar la comutare explicita, script anti-FOUC in <head> pre-paint. Suprafetele de stare hardcodate convertite la color-mix in base.html + 7 fragmente _*.html (light lizibil, contrast WCAG AA). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,9 +13,26 @@
|
||||
// useTemplateFragments parseaza tot intr-un <template>, pastrand rand + OOB impreuna.
|
||||
htmx.config.useTemplateFragments = true;
|
||||
</script>
|
||||
<script>
|
||||
// Anti-FOUC (US-001 PRD 5.3): citeste preferinta tema din localStorage inainte de
|
||||
// primul paint; seteaza data-theme pe <html> sincron, fara blink dark->light.
|
||||
(function() {
|
||||
try {
|
||||
var t = localStorage.getItem('theme');
|
||||
if (!t) {
|
||||
t = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
||||
}
|
||||
document.documentElement.setAttribute('data-theme', t);
|
||||
} catch(e) {
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
:root { --bg:#0f1115; --card:#181b22; --ink:#e6e9ef; --muted:#8b93a7; --line:#262b36;
|
||||
--ok:#3ecf8e; --warn:#e6b34a; --err:#e5605e; --accent:#5b8def; }
|
||||
[data-theme="light"] { --bg:#f6f7f9; --card:#ffffff; --ink:#1a1d24; --muted:#5c6473; --line:#e2e5ea;
|
||||
--ok:#15803d; --warn:#b45309; --err:#dc2626; --accent:#2563eb; }
|
||||
* { box-sizing:border-box; }
|
||||
body { margin:0; font:15px/1.5 ui-sans-serif,system-ui,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
|
||||
background:var(--bg); color:var(--ink); -webkit-font-smoothing:antialiased; }
|
||||
@@ -24,7 +41,7 @@
|
||||
header .env { font-size:12px; color:var(--muted); border:1px solid var(--line); padding:2px 8px; border-radius:99px; }
|
||||
main { padding:24px; max-width:1100px; margin:0 auto; }
|
||||
.card { background:var(--card); border:1px solid var(--line); border-radius:10px; padding:16px 20px; margin-bottom:16px; }
|
||||
.banner { border-left:3px solid var(--err); background:#241a1a; }
|
||||
.banner { border-left:3px solid var(--err); background:color-mix(in srgb, var(--err) 12%, var(--card)); }
|
||||
.banner.hidden { display:none; }
|
||||
/* Tabelele de date au multe coloane; pe ecrane inguste scroll IN card, nu
|
||||
impinge layout-ul paginii (altfel toata pagina scrolleaza orizontal). */
|
||||
@@ -46,7 +63,7 @@
|
||||
text-align:center; transition:border-color .15s,background .15s; }
|
||||
.drop-zone.drag-over { border-color:var(--accent); background:rgba(91,141,239,.05); }
|
||||
/* Banner varianta warn (nu eroare) */
|
||||
.banner.warn { border-left-color:var(--warn); background:#201c0f; }
|
||||
.banner.warn { border-left-color:var(--warn); background:color-mix(in srgb, var(--warn) 12%, var(--card)); }
|
||||
/* Bara confirmare sticky */
|
||||
.sticky-bar { position:sticky; bottom:0; background:var(--card); border-top:1px solid var(--line);
|
||||
padding:12px 16px; display:flex; align-items:flex-start; gap:16px;
|
||||
@@ -59,7 +76,7 @@
|
||||
.cardlink { font-size:13px; padding:7px 10px; border-radius:6px; display:inline-flex;
|
||||
align-items:center; min-height:36px; white-space:nowrap; }
|
||||
.cardlink:hover { background:var(--line); }
|
||||
.flash { background:#16241c; border-left:3px solid var(--ok); padding:8px 12px; border-radius:6px;
|
||||
.flash { background:color-mix(in srgb, var(--ok) 12%, var(--card)); border-left:3px solid var(--ok); padding:8px 12px; border-radius:6px;
|
||||
margin:0 0 12px; font-size:13px; }
|
||||
.maprow { display:flex; gap:16px; align-items:center; padding:12px 0; border-bottom:1px solid var(--line);
|
||||
flex-wrap:wrap; }
|
||||
@@ -92,8 +109,48 @@
|
||||
<header>
|
||||
<h1>Gateway RAR AUTOPASS</h1>
|
||||
<span class="env">{{ rar_env }}</span>
|
||||
<span class="muted" style="margin-left:auto; font-size:13px;">v{{ version }}</span>
|
||||
<div style="margin-left:auto; display:flex; align-items:center; gap:8px;">
|
||||
<button id="tema-toggle"
|
||||
aria-label="Comuta tema (luminos/intunecat)"
|
||||
title="Comuta tema"
|
||||
style="background:transparent; border:1px solid var(--line); color:var(--ink); cursor:pointer; border-radius:6px; min-height:36px; min-width:36px; font-size:16px; padding:4px 8px; line-height:1; display:inline-flex; align-items:center; justify-content:center;">☀</button>
|
||||
<span class="muted" style="font-size:13px;">v{{ version }}</span>
|
||||
</div>
|
||||
</header>
|
||||
<main>{% block content %}{% endblock %}</main>
|
||||
<script>
|
||||
// Handler comutator tema (US-002 PRD 5.3): click toggle light<->dark, persista in localStorage.
|
||||
// Separare init (doar sincronizare iconita) de persistenta (doar la click explicit).
|
||||
// Motivatie: scrierea in localStorage la init ar ingloba imediat preferinta OS-aware ca alegere
|
||||
// explicita, impiedicand urmarirea ulterioara a modificarilor de tema ale sistemului de operare.
|
||||
(function() {
|
||||
var btn = document.getElementById('tema-toggle');
|
||||
if (!btn) return;
|
||||
// Sincronizeaza iconita si aria-label dupa tema curenta -- FARA efecte secundare in localStorage.
|
||||
function _syncIcon(t) {
|
||||
if (t === 'light') {
|
||||
btn.innerHTML = '☾';
|
||||
btn.setAttribute('aria-label', 'Comuta tema (intunecat)');
|
||||
btn.title = 'Comuta tema (intunecat)';
|
||||
} else {
|
||||
btn.innerHTML = '☀';
|
||||
btn.setAttribute('aria-label', 'Comuta tema (luminos)');
|
||||
btn.title = 'Comuta tema (luminos)';
|
||||
}
|
||||
}
|
||||
// Aplica o tema noua, seteaza data-theme si persista in localStorage -- apelat DOAR la click.
|
||||
function _setTheme(t) {
|
||||
document.documentElement.setAttribute('data-theme', t);
|
||||
try { localStorage.setItem('theme', t); } catch(e) {}
|
||||
_syncIcon(t);
|
||||
}
|
||||
// La init: sincronizeaza doar iconita din data-theme curent (setat deja de scriptul anti-FOUC).
|
||||
_syncIcon(document.documentElement.getAttribute('data-theme') || 'dark');
|
||||
btn.addEventListener('click', function() {
|
||||
var cur = document.documentElement.getAttribute('data-theme') || 'dark';
|
||||
_setTheme(cur === 'dark' ? 'light' : 'dark');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user