feat: add dark mode support v2 #4
@@ -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 ============
|
// ============ NAVIGATION ============
|
||||||
const navLinks = document.querySelectorAll('.nav-link');
|
const navLinks = document.querySelectorAll('.nav-link');
|
||||||
const pages = document.querySelectorAll('.page');
|
const pages = document.querySelectorAll('.page');
|
||||||
@@ -559,6 +619,11 @@ function renderUsageStats() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderUsageCharts() {
|
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
|
// Provider chart
|
||||||
const providerCtx = document.getElementById('chart-provider').getContext('2d');
|
const providerCtx = document.getElementById('chart-provider').getContext('2d');
|
||||||
|
|
||||||
@@ -583,7 +648,7 @@ function renderUsageCharts() {
|
|||||||
plugins: {
|
plugins: {
|
||||||
legend: {
|
legend: {
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
labels: { color: '#e0e0e0' }
|
labels: { color: themeForeground }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -604,7 +669,7 @@ function renderUsageCharts() {
|
|||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Requests',
|
label: 'Requests',
|
||||||
data: agentData,
|
data: agentData,
|
||||||
backgroundColor: '#3498db'
|
backgroundColor: themePrimary
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
@@ -617,12 +682,12 @@ function renderUsageCharts() {
|
|||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
ticks: { color: '#e0e0e0' },
|
ticks: { color: themeForeground },
|
||||||
grid: { color: '#444' }
|
grid: { color: themeBorder }
|
||||||
},
|
},
|
||||||
x: {
|
x: {
|
||||||
ticks: { color: '#e0e0e0' },
|
ticks: { color: themeForeground },
|
||||||
grid: { color: '#444' }
|
grid: { color: themeBorder }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -708,6 +773,7 @@ function escapeHtml(text) {
|
|||||||
|
|
||||||
// ============ INITIALIZATION ============
|
// ============ INITIALIZATION ============
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initTheme();
|
||||||
populateAgentDropdown();
|
populateAgentDropdown();
|
||||||
loadTasks();
|
loadTasks();
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
<a href="#wiki" class="nav-link" data-page="wiki">Wiki</a>
|
<a href="#wiki" class="nav-link" data-page="wiki">Wiki</a>
|
||||||
<a href="#agents" class="nav-link" data-page="agents">Agents</a>
|
<a href="#agents" class="nav-link" data-page="agents">Agents</a>
|
||||||
<a href="#usage" class="nav-link" data-page="usage">Usage</a>
|
<a href="#usage" class="nav-link" data-page="usage">Usage</a>
|
||||||
|
<button id="theme-toggle" class="btn-secondary" type="button" aria-label="Toggle theme">Dark Mode</button>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg: #1a1a1a;
|
--bg: #f5f7fb;
|
||||||
--fg: #e0e0e0;
|
--fg: #1f2937;
|
||||||
--border: #444;
|
--border: #c8d1df;
|
||||||
--primary: #3498db;
|
--primary: #3498db;
|
||||||
--secondary: #2ecc71;
|
--secondary: #2ecc71;
|
||||||
--danger: #e74c3c;
|
--danger: #e74c3c;
|
||||||
--warning: #f39c12;
|
--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;
|
--dark: #121212;
|
||||||
--light: #f0f0f0;
|
--light: #f0f0f0;
|
||||||
--card-bg: #2a2a2a;
|
--card-bg: #2a2a2a;
|
||||||
@@ -13,6 +24,19 @@
|
|||||||
--modal-bg: rgba(0, 0, 0, 0.7);
|
--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;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -24,6 +48,11 @@ body {
|
|||||||
background-color: var(--bg);
|
background-color: var(--bg);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
line-height: 1.6;
|
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 {
|
.container {
|
||||||
@@ -48,6 +77,7 @@ header h1 {
|
|||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-link {
|
.nav-link {
|
||||||
@@ -67,6 +97,10 @@ nav {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#theme-toggle {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
|
|||||||
Reference in New Issue
Block a user