From 969a690732fcbd07b21e37c92182a014e9b02494 Mon Sep 17 00:00:00 2001 From: Christopher Mayor Date: Wed, 4 Mar 2026 11:31:40 -0800 Subject: [PATCH] feat: add dark mode support v2 --- public/app.js | 78 +++++++++++++++++++++++++++++++++++++++++++---- public/index.html | 1 + public/styles.css | 40 ++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 9 deletions(-) diff --git a/public/app.js b/public/app.js index c942107..8a0dd07 100644 --- a/public/app.js +++ b/public/app.js @@ -1,3 +1,63 @@ +// ============ THEME ============ +const THEME_STORAGE_KEY = 'agentdash-theme'; +const themeToggleBtn = document.getElementById('theme-toggle'); +const systemThemeMedia = window.matchMedia('(prefers-color-scheme: dark)'); + +function getSystemTheme() { + return systemThemeMedia.matches ? 'dark' : 'light'; +} + +function getSavedTheme() { + try { + const saved = localStorage.getItem(THEME_STORAGE_KEY); + return saved === 'light' || saved === 'dark' ? saved : null; + } catch { + return null; + } +} + +function setSavedTheme(theme) { + try { + localStorage.setItem(THEME_STORAGE_KEY, theme); + } catch { + // Ignore localStorage errors (privacy mode, quota, etc.). + } +} + +function updateThemeToggleLabel() { + if (!themeToggleBtn) return; + const isDarkTheme = document.documentElement.getAttribute('data-theme') === 'dark'; + const nextTheme = isDarkTheme ? 'light' : 'dark'; + themeToggleBtn.textContent = isDarkTheme ? 'Light Mode' : 'Dark Mode'; + themeToggleBtn.setAttribute('aria-label', `Switch to ${nextTheme} mode`); +} + +function applyTheme(theme, { persist = false } = {}) { + document.documentElement.setAttribute('data-theme', theme); + if (persist) setSavedTheme(theme); + updateThemeToggleLabel(); + if (usageStats) renderUsageCharts(); +} + +function initTheme() { + const savedTheme = getSavedTheme(); + applyTheme(savedTheme || getSystemTheme()); + + if (themeToggleBtn) { + themeToggleBtn.addEventListener('click', () => { + const currentTheme = document.documentElement.getAttribute('data-theme') || getSystemTheme(); + const nextTheme = currentTheme === 'dark' ? 'light' : 'dark'; + applyTheme(nextTheme, { persist: true }); + }); + } + + systemThemeMedia.addEventListener('change', (event) => { + if (!getSavedTheme()) { + applyTheme(event.matches ? 'dark' : 'light'); + } + }); +} + // ============ NAVIGATION ============ const navLinks = document.querySelectorAll('.nav-link'); const pages = document.querySelectorAll('.page'); @@ -559,6 +619,11 @@ function renderUsageStats() { } function renderUsageCharts() { + const rootStyles = getComputedStyle(document.documentElement); + const themeForeground = rootStyles.getPropertyValue('--fg').trim() || '#e0e0e0'; + const themeBorder = rootStyles.getPropertyValue('--border').trim() || '#444'; + const themePrimary = rootStyles.getPropertyValue('--primary').trim() || '#3498db'; + // Provider chart const providerCtx = document.getElementById('chart-provider').getContext('2d'); @@ -583,7 +648,7 @@ function renderUsageCharts() { plugins: { legend: { position: 'bottom', - labels: { color: '#e0e0e0' } + labels: { color: themeForeground } } } } @@ -604,7 +669,7 @@ function renderUsageCharts() { datasets: [{ label: 'Requests', data: agentData, - backgroundColor: '#3498db' + backgroundColor: themePrimary }] }, options: { @@ -617,12 +682,12 @@ function renderUsageCharts() { scales: { y: { beginAtZero: true, - ticks: { color: '#e0e0e0' }, - grid: { color: '#444' } + ticks: { color: themeForeground }, + grid: { color: themeBorder } }, x: { - ticks: { color: '#e0e0e0' }, - grid: { color: '#444' } + ticks: { color: themeForeground }, + grid: { color: themeBorder } } } } @@ -708,6 +773,7 @@ function escapeHtml(text) { // ============ INITIALIZATION ============ document.addEventListener('DOMContentLoaded', () => { + initTheme(); populateAgentDropdown(); loadTasks(); diff --git a/public/index.html b/public/index.html index cce2854..ffd6f15 100644 --- a/public/index.html +++ b/public/index.html @@ -19,6 +19,7 @@ Wiki Agents Usage + diff --git a/public/styles.css b/public/styles.css index 6fe3a33..f144389 100644 --- a/public/styles.css +++ b/public/styles.css @@ -1,11 +1,22 @@ :root { - --bg: #1a1a1a; - --fg: #e0e0e0; - --border: #444; + --bg: #f5f7fb; + --fg: #1f2937; + --border: #c8d1df; --primary: #3498db; --secondary: #2ecc71; --danger: #e74c3c; --warning: #f39c12; + --dark: #1f2937; + --light: #f8fafc; + --card-bg: #ffffff; + --card-fg: #111827; + --modal-bg: rgba(0, 0, 0, 0.45); +} + +:root[data-theme='dark'] { + --bg: #1a1a1a; + --fg: #e0e0e0; + --border: #444; --dark: #121212; --light: #f0f0f0; --card-bg: #2a2a2a; @@ -13,6 +24,19 @@ --modal-bg: rgba(0, 0, 0, 0.7); } +@media (prefers-color-scheme: dark) { + :root:not([data-theme='light']) { + --bg: #1a1a1a; + --fg: #e0e0e0; + --border: #444; + --dark: #121212; + --light: #f0f0f0; + --card-bg: #2a2a2a; + --card-fg: #e0e0e0; + --modal-bg: rgba(0, 0, 0, 0.7); + } +} + * { margin: 0; padding: 0; @@ -24,6 +48,11 @@ body { background-color: var(--bg); color: var(--fg); line-height: 1.6; + transition: background-color 0.3s ease, color 0.3s ease; +} + +body * { + transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease; } .container { @@ -48,6 +77,7 @@ header h1 { nav { display: flex; gap: 15px; + align-items: center; } .nav-link { @@ -67,6 +97,10 @@ nav { color: white; } +#theme-toggle { + margin-left: auto; +} + /* Buttons */ .btn-primary { background-color: var(--primary);