From 3ff6f87cf44e0e8f571e9ac56aa0a44e5ce5c481 Mon Sep 17 00:00:00 2001 From: "David G. Simmons" Date: Thu, 16 Apr 2026 15:58:37 -0400 Subject: [PATCH] Implement dark mode toggle and persistence logic Adds a new dark mode feature that supports light, dark, and system-default color schemes. The implementation handles theme persistence via local storage, updates the UI icons, and toggles visibility for theme-specific images like logos. --- assets/scripts/features/darkmode/index.js | 75 +++++++++++++++++++++++ assets/scripts/features/index.js | 1 + layouts/partials/comments/bluesky.html | 4 +- layouts/partials/sections/home.html | 4 +- 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 assets/scripts/features/darkmode/index.js diff --git a/assets/scripts/features/darkmode/index.js b/assets/scripts/features/darkmode/index.js new file mode 100644 index 0000000..e33eec1 --- /dev/null +++ b/assets/scripts/features/darkmode/index.js @@ -0,0 +1,75 @@ +const PERSISTENCE_KEY = "darkmode:color-scheme"; + +window.addEventListener("load", async () => { + const menu = document.getElementById("themeMenu"); + const $icon = document.getElementById("navbar-theme-icon-svg"); + if (menu == null || $icon == null) return; + + const btns = menu.getElementsByTagName("a"); + const iconMap = Array.from(btns).reduce((map, btn) => { + const $img = btn.getElementsByTagName("img")[0]; + map[btn.dataset.scheme] = $img.src; + return map; + }, {}); + + function loadScheme() { + console.log("Loading theme: ", localStorage.getItem(PERSISTENCE_KEY)); + return localStorage.getItem(PERSISTENCE_KEY) || "system"; + } + + function saveScheme(scheme) { + console.log("Saving theme: ", scheme); + localStorage.setItem(PERSISTENCE_KEY, scheme); + } + + function getPreferredColorScheme() { + console.log("Getting system theme preference"); + const isDarkMode = window.matchMedia( + "(prefers-color-scheme: dark)", + ).matches; + console.log("System prefers dark mode: ", isDarkMode); + return isDarkMode ? "dark" : "light"; + } + + function setScheme(newScheme) { + let theme = newScheme; + if (newScheme === "system") { + theme = getPreferredColorScheme(); + } + console.log("Setting theme to: ", theme); + // set data-theme attribute on html tag + document.querySelector("html").dataset.theme = theme; + + // update icon + $icon.src = iconMap[newScheme]; + + // save preference to local storage + saveScheme(newScheme); + + setImages(theme); + } + + setScheme(loadScheme()); + + Array.from(menu.getElementsByTagName("a")).forEach((btn) => { + btn.addEventListener("click", () => { + const { scheme } = btn.dataset; + setScheme(scheme); + }); + }); +}); + +function setImages(newScheme) { + const els = Array.from(document.getElementsByClassName("logo-holder")); + for (const el of els) { + const light = el.querySelector(".light-logo"); + const dark = el.querySelector(".dark-logo"); + if (newScheme === "dark" && dark !== null) { + if (light !== null) light.style.display = "none"; + dark.style.display = "inline"; + } else { + if (light !== null) light.style.display = "inline"; + if (dark !== null) dark.style.display = "none"; + } + } +} diff --git a/assets/scripts/features/index.js b/assets/scripts/features/index.js index 809ad33..90474df 100644 --- a/assets/scripts/features/index.js +++ b/assets/scripts/features/index.js @@ -1,3 +1,4 @@ if (process.env.FEATURE_ANALYTICS === "1") { import("./analytics"); } +import "./darkmode"; diff --git a/layouts/partials/comments/bluesky.html b/layouts/partials/comments/bluesky.html index 00c1c2d..734d1f2 100644 --- a/layouts/partials/comments/bluesky.html +++ b/layouts/partials/comments/bluesky.html @@ -1,3 +1,3 @@ -{{ $script := resources.Get "scripts/bsky.js" }} + \ No newline at end of file diff --git a/layouts/partials/sections/home.html b/layouts/partials/sections/home.html index fe20b94..2533b8f 100644 --- a/layouts/partials/sections/home.html +++ b/layouts/partials/sections/home.html @@ -20,10 +20,10 @@ {{ $backgroundImage = site.Params.background }} {{ end }} -{{ $darkBackgroundImage:= $backgroundImage }} + {{ $authorImage:= "/images/default-avatar.png" }} {{ if $author.image }}