From e276edb00e0e527c512eae598d1149e1f0e5ffb8 Mon Sep 17 00:00:00 2001 From: Thijs Date: Sun, 21 Jun 2026 15:34:43 +0200 Subject: [PATCH] feat(theme): add light/dark/system theme toggle The dark theme was only driven by @media (prefers-color-scheme: dark). On Chromium-based browsers on Linux (e.g. Edge) the reported color scheme is the browser's own appearance setting, not the OS one, so a machine set to dark at the GNOME level could still render the light theme with no way for the user to override it. Add a header toggle that cycles System -> Light -> Dark, persists the choice in localStorage, and is resolved by an inline script before first paint (no flash of the wrong theme). The script writes a concrete data-theme onto ; CSS now keys forced dark off :root[data-theme="dark"], while the prefers-color-scheme blocks remain as a no-JS fallback scoped to :root:not([data-theme]). --- static/css/base.css | 56 +++++++++++++++++++++++++++++++++++++++++---- templates/base.njk | 43 ++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/static/css/base.css b/static/css/base.css index aa02cb1..dda88f3 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -39,7 +39,7 @@ /* override colors for dark theme */ @media (prefers-color-scheme: dark) { - :root { + :root:not([data-theme]) { --light: #000000; /* inverted from --dark */ --dark: #f8f9fa; /* inverted from --light */ --gray-dark: #9aa0a6; /* inverted from --gray-light */ @@ -50,11 +50,26 @@ --logo: url('/images/logo-dark.svg'); } - .logo { + :root:not([data-theme]) .logo { content: url('/images/logo-dark.svg'); } } +/* Forced dark theme: applied when the toggle is set to Dark, or when the + inline script resolved "System" against a dark OS. That script + always writes a concrete data-theme, so the prefers-color-scheme media + queries above only act as a no-JS fallback. */ +:root[data-theme="dark"] { + --light: #000000; + --dark: #f8f9fa; + --gray-dark: #9aa0a6; + --gray-light: #343a40; + --gray-lightest: #252525; + --bg-green: #46b292; + --bg-red: #a44244; + --logo: url('/images/logo-dark.svg'); +} + /* *************************************** */ /* Fonts and typography */ /* *************************************** */ @@ -116,11 +131,15 @@ a.external::after { } @media (prefers-color-scheme: dark) { - a.external::after { + :root:not([data-theme]) a.external::after { content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKAgMAAADwXCcuAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAxQTFRFAAAA////////////OMA7qAAAAAR0Uk5TAP9gIFOYGRgAAAAqSURBVAjXY2BgDWAIZQpgcOCcwOCgJsDgMIOBwTmBgcHhgAMDDIaGOgAAgeUG8U0ghIsAAAAASUVORK5CYII=); } } +:root[data-theme="dark"] a.external::after { + content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKAgMAAADwXCcuAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAxQTFRFAAAA////////////OMA7qAAAAAR0Uk5TAP9gIFOYGRgAAAAqSURBVAjXY2BgDWAIZQpgcOCcwOCgJsDgMIOBwTmBgcHhgAMDDIaGOgAAgeUG8U0ghIsAAAAASUVORK5CYII=); +} + .logo-light { content: url('/images/logo-light.svg'); } @@ -176,6 +195,24 @@ header #header-nav a:hover { text-decoration: none; } +#theme-toggle-btn { + background: none; + border: none; + color: var(--dark); + cursor: pointer; + font-size: 28px; + line-height: 1; + padding: 0 8px; + margin-left: 8px; + vertical-align: middle; +} + +#theme-toggle-btn:hover, +#theme-toggle-btn:focus { + color: var(--primary); + outline: 0; +} + header #dropdown-toggle-btn { display: none; } @@ -478,11 +515,15 @@ nav a { } @media (prefers-color-scheme: dark) { - .login-logo { + :root:not([data-theme]) .login-logo { content: url('/images/logo-dark.svg'); } } +:root[data-theme="dark"] .login-logo { + content: url('/images/logo-dark.svg'); +} + .login-button { display: block; padding: 12px 64px; @@ -495,12 +536,17 @@ nav a { } @media (prefers-color-scheme: dark) { - .login-button { + :root:not([data-theme]) .login-button { border: solid 1px white; color: white; } } +:root[data-theme="dark"] .login-button { + border: solid 1px white; + color: white; +} + .login-button:hover, .login-button:focus { text-decoration: none; diff --git a/templates/base.njk b/templates/base.njk index 074c22e..1c2ff62 100644 --- a/templates/base.njk +++ b/templates/base.njk @@ -4,6 +4,21 @@ {{ title | e if title }}{{ " > " + subtitle | e if subtitle }}{{ " | " if title or subtitle }}Codamhero v2 + + @@ -26,6 +41,7 @@ {% endif %} {% endif %} CONTRIBUTE + {% if not nodropdown %} @@ -114,6 +130,33 @@ }); }); +
{% block content %}{% endblock %}