feat: implement dark mode toggle across application

- Added dark mode detection and application logic in HTML files (dashboard.html, kioskedit.html, live.html, rulesets.html, sessions.html).
- Introduced a theme toggle button for user interaction.
- Created a new theme-toggle.js script to manage dark mode state and persistence using localStorage.
- Updated CSS styles to support dark mode with appropriate color variables and transitions.
- Enhanced user experience by preventing flash of unstyled content during theme initialization.
This commit is contained in:
Giuseppe Raffa
2026-04-21 22:42:02 +02:00
parent 924c2b5367
commit ee478e52ef
8 changed files with 352 additions and 12 deletions

2
.gitignore vendored
View File

@@ -19,3 +19,5 @@ Thumbs.db
.eslintcache .eslintcache
.venv/ .venv/
.claude/

File diff suppressed because one or more lines are too long

View File

@@ -7,7 +7,18 @@
<script src='https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js'></script> <script src='https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css' rel='stylesheet' /> <link href='https://api.mapbox.com/mapbox-gl-js/v2.14.1/mapbox-gl.css' rel='stylesheet' />
<link rel="stylesheet" href="../static/styles/style.css">
<link rel="stylesheet" href="../static/styles/kiosk.css"> <link rel="stylesheet" href="../static/styles/kiosk.css">
<script>
// Detect and apply dark mode immediately to prevent flash
const THEME_KEY = 'meb-console-theme';
const saved = localStorage.getItem(THEME_KEY);
const isDark = saved ? saved === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches;
if (isDark) {
document.documentElement.classList.add('dark-mode');
document.body.classList.add('dark-mode');
}
</script>
</head> </head>
<body> <body>
@@ -37,6 +48,7 @@
<p id="cardCount">0 cards</p> <p id="cardCount">0 cards</p>
<button id="editBtn">Edit</button> <button id="editBtn">Edit</button>
<button id="theme-toggle-btn" title="Dark Mode" style="padding: 8px 12px; font-size: 18px;">🌙</button>
<button id="addCardBtn" title="Aggiungi Widget">+</button> <button id="addCardBtn" title="Aggiungi Widget">+</button>
<button id="addMapBtn" title="Aggiungi Mappa">Map</button> <button id="addMapBtn" title="Aggiungi Mappa">Map</button>
@@ -555,4 +567,15 @@ ws.onclose = () => {
</script> </script>
<script src="../static/theme-toggle.js"></script>
<script>
// Theme toggle button event listener
document.addEventListener('DOMContentLoaded', () => {
const themeBtn = document.getElementById('theme-toggle-btn');
if (themeBtn) {
themeBtn.addEventListener('click', toggleDarkMode);
}
});
</script>
</html> </html>

View File

@@ -11,6 +11,16 @@
.expanded-chart-container { display: none; } .expanded-chart-container { display: none; }
.comparison-sidebar { display: none; } .comparison-sidebar { display: none; }
</style> </style>
<script>
// Detect and apply dark mode immediately to prevent flash
const THEME_KEY = 'meb-console-theme';
const saved = localStorage.getItem(THEME_KEY);
const isDark = saved ? saved === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches;
if (isDark) {
document.documentElement.classList.add('dark-mode');
document.body.classList.add('dark-mode');
}
</script>
</head> </head>
<body> <body>
@@ -50,6 +60,7 @@
<p class="last-update" id="lastUpdateText">In attesa di dati...</p> <p class="last-update" id="lastUpdateText">In attesa di dati...</p>
</div> </div>
<div class="profile"> <div class="profile">
<button id="theme-toggle-btn" title="Dark Mode" style="padding: 8px 12px; font-size: 18px;">🌙</button>
<p id="sensorName">Sensore</p> <p id="sensorName">Sensore</p>
<button id="changeSessionBtn">Cambia sessione</button> <button id="changeSessionBtn">Cambia sessione</button>
</div> </div>
@@ -777,3 +788,14 @@ document.getElementById('saveSessionLabelBtn').onclick = async () => {
}; };
</script> </script>
<script src="/static/theme-toggle.js"></script>
<script>
// Theme toggle button event listener
document.addEventListener('DOMContentLoaded', () => {
const themeBtn = document.getElementById('theme-toggle-btn');
if (themeBtn) {
themeBtn.addEventListener('click', toggleDarkMode);
}
});
</script>

View File

@@ -4,6 +4,16 @@
<link rel="stylesheet" href="/static/styles/style.css"> <link rel="stylesheet" href="/static/styles/style.css">
<link rel="stylesheet" href="/static/styles/rulesets.css"> <link rel="stylesheet" href="/static/styles/rulesets.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
// Detect and apply dark mode immediately to prevent flash
const THEME_KEY = 'meb-console-theme';
const saved = localStorage.getItem(THEME_KEY);
const isDark = saved ? saved === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches;
if (isDark) {
document.documentElement.classList.add('dark-mode');
document.body.classList.add('dark-mode');
}
</script>
</head> </head>
<body> <body>
@@ -24,6 +34,7 @@
</div> </div>
</div> </div>
<div class="rs-header-right"> <div class="rs-header-right">
<button id="theme-toggle-btn" title="Dark Mode" style="padding: 8px 12px; font-size: 18px;">🌙</button>
<span class="rs-saving" id="savingIndicator">Salvato</span> <span class="rs-saving" id="savingIndicator">Salvato</span>
</div> </div>
</div> </div>
@@ -594,5 +605,16 @@ function flash(text, elId = 'savingIndicator') {
document.addEventListener('DOMContentLoaded', () => loadRules()); document.addEventListener('DOMContentLoaded', () => loadRules());
</script>
<script src="/static/theme-toggle.js"></script>
<script>
// Theme toggle button event listener
document.addEventListener('DOMContentLoaded', () => {
const themeBtn = document.getElementById('theme-toggle-btn');
if (themeBtn) {
themeBtn.addEventListener('click', toggleDarkMode);
}
});
</script> </script>
</body> </body>

View File

@@ -244,6 +244,16 @@
.map-bar .filter button { color: #cbd5e1; } .map-bar .filter button { color: #cbd5e1; }
.map-bar .filter button.active { background: #3b82f6; color: #fff; } .map-bar .filter button.active { background: #3b82f6; color: #fff; }
</style> </style>
<script>
// Detect and apply dark mode immediately to prevent flash
const THEME_KEY = 'meb-console-theme';
const saved = localStorage.getItem(THEME_KEY);
const isDark = saved ? saved === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches;
if (isDark) {
document.documentElement.classList.add('dark-mode');
document.body.classList.add('dark-mode');
}
</script>
</head> </head>
<body> <body>
@@ -279,6 +289,7 @@
<h1>Sessioni</h1> <h1>Sessioni</h1>
</div> </div>
<div class="header-right"> <div class="header-right">
<button id="theme-toggle-btn" title="Dark Mode" style="padding: 8px 12px; font-size: 18px;">🌙</button>
<span id="sessionNameDisplay"></span> <span id="sessionNameDisplay"></span>
<span id="sessionMetaDisplay"></span> <span id="sessionMetaDisplay"></span>
</div> </div>
@@ -918,5 +929,16 @@ document.getElementById('changeSessionBtn').onclick = () => {
// --- Init --- // --- Init ---
document.addEventListener('DOMContentLoaded', loadSessionsList); document.addEventListener('DOMContentLoaded', loadSessionsList);
</script> </script>
<script src="/static/theme-toggle.js"></script>
<script>
// Theme toggle button event listener
document.addEventListener('DOMContentLoaded', () => {
const themeBtn = document.getElementById('theme-toggle-btn');
if (themeBtn) {
themeBtn.addEventListener('click', toggleDarkMode);
}
});
</script>
</body> </body>
</html> </html>

View File

@@ -20,6 +20,53 @@
--radius-lg: 12px; --radius-lg: 12px;
} }
/* DARK MODE */
@media (prefers-color-scheme: dark) {
:root {
--accent-color: #3b82f6;
--accent-hover: #2563eb;
--accent-light: #1e3a8a;
--accent-border: #1e40af;
--text-primary: #f1f5f9;
--text-secondary: #cbd5e1;
--text-tertiary: #94a3b8;
--surface: #0f172a;
--header-bg: rgba(15, 23, 42, 0.85);
--header-border: #334155;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
}
/* Manual dark mode toggle */
body.dark-mode {
--accent-color: #3b82f6;
--accent-hover: #2563eb;
--accent-light: #1e3a8a;
--accent-border: #1e40af;
--text-primary: #f1f5f9;
--text-secondary: #cbd5e1;
--text-tertiary: #94a3b8;
--surface: #0f172a;
--header-bg: rgba(15, 23, 42, 0.85);
--header-border: #334155;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
/* Smooth transition for dark mode */
body {
transition: background-color 0.3s ease, color 0.3s ease;
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -42,6 +89,7 @@
body { body {
font-family: 'Normal', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-family: 'Normal', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
color: var(--text-primary); color: var(--text-primary);
background-color: var(--surface);
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
} }
@@ -49,7 +97,7 @@ button {
padding: 10px 24px; padding: 10px 24px;
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
border: 1px solid var(--header-border); border: 1px solid var(--header-border);
background-color: var(--bg-surface); background-color: var(--surface);
color: var(--text-primary); color: var(--text-primary);
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;

View File

@@ -0,0 +1,85 @@
/**
* Dark Mode Theme Toggle
* Manages light/dark theme with localStorage persistence
*/
const THEME_STORAGE_KEY = 'meb-console-theme';
const DARK_MODE_CLASS = 'dark-mode';
/**
* Initialize theme on page load
*/
function initializeTheme() {
const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
// Use saved preference, or fallback to system preference
const shouldBeDark = savedTheme ? savedTheme === 'dark' : prefersDarkMode;
if (shouldBeDark) {
enableDarkMode();
} else {
disableDarkMode();
}
}
/**
* Enable dark mode
*/
function enableDarkMode() {
document.documentElement.classList.add(DARK_MODE_CLASS);
document.body.classList.add(DARK_MODE_CLASS);
localStorage.setItem(THEME_STORAGE_KEY, 'dark');
updateThemeToggleButton();
}
/**
* Disable dark mode (light mode)
*/
function disableDarkMode() {
document.documentElement.classList.remove(DARK_MODE_CLASS);
document.body.classList.remove(DARK_MODE_CLASS);
localStorage.setItem(THEME_STORAGE_KEY, 'light');
updateThemeToggleButton();
}
/**
* Toggle dark mode
*/
function toggleDarkMode() {
const isDarkMode = document.body.classList.contains(DARK_MODE_CLASS);
if (isDarkMode) {
disableDarkMode();
} else {
enableDarkMode();
}
}
/**
* Update theme toggle button appearance
*/
function updateThemeToggleButton() {
const themeBtn = document.getElementById('theme-toggle-btn');
if (themeBtn) {
const isDarkMode = document.body.classList.contains(DARK_MODE_CLASS);
themeBtn.textContent = isDarkMode ? '☀️' : '🌙';
themeBtn.title = isDarkMode ? 'Light Mode' : 'Dark Mode';
}
}
/**
* Listen for system theme preference changes
*/
function listenToSystemThemeChanges() {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (!localStorage.getItem(THEME_STORAGE_KEY)) {
// If no manual preference set, follow system preference
e.matches ? enableDarkMode() : disableDarkMode();
}
});
}
// Initialize on load
document.addEventListener('DOMContentLoaded', initializeTheme);
listenToSystemThemeChanges();