Add theme switchers: Admin UI (A/B/C) + Display (5 themes)
Admin UI: Dark Minimal / Retro Terminal / Warm Editorial via header buttons Display: Classic White / Dark / Sepia / Nord / Terminal via Settings tab Both switchers persist via localStorage / config.json
This commit is contained in:
+132
-17
@@ -6,24 +6,36 @@
|
||||
<title>epaper-dashboard</title>
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%230a0a0a'/%3E%3Crect x='10' y='10' width='80' height='80' fill='%23fafafa'/%3E%3Crect x='15' y='15' width='35' height='35' fill='%230080ff'/%3E%3Crect x='50' y='15' width='35' height='35' fill='%23ff0080'/%3E%3Crect x='15' y='50' width='35' height='35' fill='%2300cc00'/%3E%3Crect x='50' y='50' width='35' height='35' fill='%23ff8000'/%3E%3C/svg%3E">
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0a0a0f;
|
||||
--surface: #15151c;
|
||||
--surface-2: #1f1f28;
|
||||
--surface-3: #2a2a35;
|
||||
--border: #2e2e3a;
|
||||
--border-light: #3a3a48;
|
||||
--fg: #f0f0f5;
|
||||
--fg-muted: #9090a0;
|
||||
--fg-dim: #6a6a78;
|
||||
--accent: #6d8eff;
|
||||
--accent-glow: rgba(109, 142, 255, 0.15);
|
||||
--ok: #4ade80;
|
||||
--warn: #fbbf24;
|
||||
--alert: #f87171;
|
||||
--info: #60a5fa;
|
||||
--success: #22c55e;
|
||||
/* ================================================================
|
||||
ADMIN UI THEME VARIABLES — switch via data-theme="A|B|C" on <body>
|
||||
================================================================ */
|
||||
:root,
|
||||
:root[data-theme="A"] {
|
||||
/* A: Dark Minimal (original/default) */
|
||||
--bg: #0a0a0f; --surface: #15151c; --surface-2: #1f1f28; --surface-3: #2a2a35;
|
||||
--border: #2e2e3a; --border-light: #3a3a48;
|
||||
--fg: #f0f0f5; --fg-muted: #9090a0; --fg-dim: #6a6a78;
|
||||
--accent: #6d8eff; --accent-glow: rgba(109,142,255,0.15);
|
||||
--ok: #4ade80; --warn: #fbbf24; --alert: #f87171; --info: #60a5fa; --success: #22c55e;
|
||||
}
|
||||
:root[data-theme="B"] {
|
||||
/* B: Retro Terminal — CRT green phosphor */
|
||||
--bg: #0d0d00; --surface: #141400; --surface-2: #1a1a00; --surface-3: #222200;
|
||||
--border: #2a2a00; --border-light: #3a3a00;
|
||||
--fg: #e8c840; --fg-muted: #a09020; --fg-dim: #605800;
|
||||
--accent: #ffcc00; --accent-glow: rgba(255,204,0,0.12);
|
||||
--ok: #88ff44; --warn: #ffaa00; --alert: #ff4444; --info: #88ccff; --success: #44ff88;
|
||||
}
|
||||
:root[data-theme="C"] {
|
||||
/* C: Warm Editorial — newspaper / cream paper */
|
||||
--bg: #f4f1eb; --surface: #faf8f4; --surface-2: #f0ede6; --surface-3: #e8e4dc;
|
||||
--border: #d8d3c8; --border-light: #e6e1d8;
|
||||
--fg: #1a1816; --fg-muted: #6b6560; --fg-dim: #9a9490;
|
||||
--accent: #c0392b; --accent-glow: rgba(192,57,43,0.10);
|
||||
--ok: #27ae60; --warn: #e67e22; --alert: #c0392b; --info: #2980b9; --success: #27ae60;
|
||||
}
|
||||
|
||||
:root[data-theme="B"] body { background-image: repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,0,0,0.06) 2px,rgba(0,0,0,0.06) 4px); }
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
@@ -464,6 +476,29 @@
|
||||
::-webkit-scrollbar-track { background: var(--bg); }
|
||||
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--border-light); }
|
||||
|
||||
/* ============ Admin Theme Switcher ============ */
|
||||
.admin-theme-switcher {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 3px;
|
||||
}
|
||||
.theme-btn {
|
||||
width: 28px; height: 28px;
|
||||
border: none; border-radius: 4px;
|
||||
background: transparent; color: var(--fg-muted);
|
||||
font-size: 0.8em; font-weight: 700; font-family: monospace;
|
||||
cursor: pointer; transition: all 0.15s;
|
||||
}
|
||||
.theme-btn:hover { background: var(--surface-2); color: var(--fg); }
|
||||
.theme-btn.active {
|
||||
background: var(--accent); color: #fff;
|
||||
}
|
||||
[data-theme="B"] .theme-btn.active { background: var(--accent); color: #000; }
|
||||
[data-theme="C"] .theme-btn.active { background: var(--accent); color: #fff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -494,6 +529,11 @@
|
||||
<div class="mode-error" id="netError"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-theme-switcher" title="Admin UI Theme">
|
||||
<button class="theme-btn active" data-admin-theme="A" onclick="setAdminTheme('A')" title="Dark Minimal">A</button>
|
||||
<button class="theme-btn" data-admin-theme="B" onclick="setAdminTheme('B')" title="Retro Terminal">B</button>
|
||||
<button class="theme-btn" data-admin-theme="C" onclick="setAdminTheme('C')" title="Warm Editorial">C</button>
|
||||
</div>
|
||||
<button class="refresh" id="refreshBtn" onclick="triggerRefresh()">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 12a9 9 0 1 1-3-6.7L21 8"/>
|
||||
@@ -586,6 +626,36 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Display Theme</h2>
|
||||
</div>
|
||||
<div style="display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap;">
|
||||
<div style="flex: 1; min-width: 200px;">
|
||||
<label style="display: block; margin-bottom: 8px; font-size: 0.82em; color: var(--fg-muted);">
|
||||
Theme für das e-Paper-Display
|
||||
</label>
|
||||
<select id="displayThemeSelect" onchange="setDisplayTheme(this.value)"
|
||||
style="width: 100%; padding: 8px 12px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 0.9em;">
|
||||
<option value="default" id="themeOpt_default">Classic White</option>
|
||||
<option value="dark" id="themeOpt_dark">Dark Mode</option>
|
||||
<option value="sepia" id="themeOpt_sepia">Sepia</option>
|
||||
<option value="nord" id="themeOpt_nord">Nord</option>
|
||||
<option value="terminal" id="themeOpt_terminal">Terminal (Green)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div id="themePreviewBox" style="display: flex; gap: 10px; align-items: center; padding-top: 22px;">
|
||||
<div style="text-align: center;">
|
||||
<div id="themePreview" style="width: 80px; height: 48px; border-radius: 4px; border: 1px solid var(--border); background: #fff; display: flex; align-items: center; justify-content: center; font-size: 0.65em; color: #000;">Preview</div>
|
||||
<div style="font-size: 0.65em; color: var(--fg-dim); margin-top: 4px;" id="themePreviewLabel">Classic</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta" style="margin-top: 12px;">
|
||||
Theme wird beim nächsten Refresh auf dem Display angezeigt. Nur für Designs mit weißem Hintergrund geeignet (Widget-Farben bleiben gleich).
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>System</h2>
|
||||
@@ -986,11 +1056,56 @@
|
||||
async function netStopAp() { await netFetch('/api/network/ap/stop', { method: 'POST' }); setTimeout(refreshNetStatus, 5000); }
|
||||
async function netForget(name) { if (!confirm(`"${name}" löschen?`)) return; await netFetch('/api/network/forget', { method: 'POST', body: new URLSearchParams({ name }) }); refreshSaved(); }
|
||||
|
||||
// ============ Admin UI Theme Switcher ============
|
||||
function setAdminTheme(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
localStorage.setItem('adminTheme', theme);
|
||||
document.querySelectorAll('.theme-btn').forEach(b => {
|
||||
b.classList.toggle('active', b.dataset.adminTheme === theme);
|
||||
});
|
||||
}
|
||||
// Restore saved admin theme on load
|
||||
const savedTheme = localStorage.getItem('adminTheme');
|
||||
if (savedTheme) setAdminTheme(savedTheme);
|
||||
|
||||
// ============ Display Theme Switcher ============
|
||||
const THEME_PREVIEWS = {
|
||||
default: { bg: '#ffffff', fg: '#000000', label: 'Classic' },
|
||||
dark: { bg: '#111111', fg: '#f0f0f0', label: 'Dark' },
|
||||
sepia: { bg: '#f4ecd8', fg: '#5b4636', label: 'Sepia' },
|
||||
nord: { bg: '#eceff4', fg: '#2e3440', label: 'Nord' },
|
||||
terminal: { bg: '#0d0d00', fg: '#e8c840', label: 'Terminal' },
|
||||
};
|
||||
function updateThemePreview(theme) {
|
||||
const p = THEME_PREVIEWS[theme] || THEME_PREVIEWS.default;
|
||||
const el = document.getElementById('themePreview');
|
||||
const lbl = document.getElementById('themePreviewLabel');
|
||||
if (el) { el.style.background = p.bg; el.style.color = p.fg; }
|
||||
if (lbl) lbl.textContent = p.label;
|
||||
}
|
||||
async function loadDisplayTheme() {
|
||||
const r = await fetch('/api/display_theme');
|
||||
if (!r.ok) return;
|
||||
const d = await r.json();
|
||||
document.getElementById('displayThemeSelect').value = d.current;
|
||||
updateThemePreview(d.current);
|
||||
}
|
||||
async function setDisplayTheme(theme) {
|
||||
updateThemePreview(theme);
|
||||
const r = await fetch('/api/display_theme', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({theme})
|
||||
});
|
||||
if (r.ok) triggerRefresh();
|
||||
}
|
||||
|
||||
// ============ Init ============
|
||||
renderGrid();
|
||||
refreshNetStatus();
|
||||
refreshScan();
|
||||
refreshSaved();
|
||||
loadDisplayTheme();
|
||||
setInterval(refreshNetStatus, 10000);
|
||||
setInterval(refreshScan, 60000);
|
||||
setInterval(refreshPreview, 10000);
|
||||
|
||||
Reference in New Issue
Block a user