add dark/light mode toggle with OS preference detection

Uses Bootstrap 5.3 data-bs-theme attribute for automatic component
theming. Adds CSS custom properties for dark mode, a toggle button
in the header, localStorage persistence, and a flash-prevention
script. Background image overlay adjusts per theme.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mark Eaton
2026-02-21 23:27:53 -05:00
co-authored by Claude Opus 4.6
parent e2e10e9d9b
commit a978df4af9
3 changed files with 142 additions and 10 deletions
+56 -1
View File
@@ -222,6 +222,7 @@ const app1 = createApp({
},
data() {
return {
isDark: false,
hamburger: [
{
link: "https://cuny-kb.primo.exlibrisgroup.com/discovery/search?tab=Everything&vid=01CUNY_KB:CUNY_KB&lang=en",
@@ -454,7 +455,56 @@ const app1 = createApp({
},
};
},
methods: {
toggleTheme() {
this.isDark = !this.isDark;
this.applyTheme(this.isDark);
localStorage.setItem("kbcc-theme", this.isDark ? "dark" : "light");
},
applyTheme(isDark) {
if (isDark) {
document.body.setAttribute("data-bs-theme", "dark");
} else {
document.body.removeAttribute("data-bs-theme");
}
this.updateBackgroundOverlay(isDark);
},
updateBackgroundOverlay(isDark) {
var el = document.querySelector(".kbcc-background-image");
if (el && el.style.backgroundImage) {
// Extract the url() part from the existing background-image
var match = el.style.backgroundImage.match(/url\([^)]+\)/);
if (match) {
var overlay = isDark
? "rgba(0,0,0,0.4)"
: "rgba(255,255,255,0.6)";
el.style.backgroundImage =
"linear-gradient(to bottom, " + overlay + " 0%, " + overlay + " 100%), " + match[0];
}
}
}
},
mounted() {
// Theme initialization
var stored = localStorage.getItem("kbcc-theme");
var prefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
if (stored) {
this.isDark = stored === "dark";
} else {
this.isDark = prefersDark;
}
this.applyTheme(this.isDark);
// Listen for OS preference changes (only applies when no manual override)
var self = this;
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function(e) {
if (!localStorage.getItem("kbcc-theme")) {
self.isDark = e.matches;
self.applyTheme(self.isDark);
}
});
// Dynamic aria-expanded for hamburger menu (WCAG 4.1.2)
const hamburger = document.getElementById('hamburger');
const hamburgerContainer = document.getElementById('hamburger-container');
@@ -490,8 +540,13 @@ app2.mount("#app2");
var picked = images[Math.floor(Math.random() * images.length)];
var el = document.querySelector(".kbcc-background-image");
if (el) {
// Check theme for correct overlay color
var stored = localStorage.getItem("kbcc-theme");
var prefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
var isDark = stored ? stored === "dark" : prefersDark;
var overlay = isDark ? "rgba(0,0,0,0.4)" : "rgba(255,255,255,0.6)";
el.style.backgroundImage =
"linear-gradient(to bottom, rgba(255,255,255,0.6) 0%, rgba(255,255,255,0.6) 100%), url('" + picked + "')";
"linear-gradient(to bottom, " + overlay + " 0%, " + overlay + " 100%), url('" + picked + "')";
}
})();