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);