all repos — legit @ 396548546ed56088cc837080fe44a963199ed7ff

Unnamed repository; edit this file 'description' to name the repository.

feat: add theme toggle with light, dark, and system modes

Adds a theme toggle button in the nav bar that cycles through
system/light/dark. Preference is persisted via a 1-year cookie.
System theme follows prefers-color-scheme by default; explicit
light/dark choices set a data-theme attribute on <html> that
overrides it. Diff colors are converted to CSS variables so they
respond to theme changes automatically.
Maxwell Jensen maxwelljensen@posteo.net
Tue, 12 May 2026 18:42:01 +0200
commit

396548546ed56088cc837080fe44a963199ed7ff

parent

962e84f92cc6d50e624b666bf2aefa21d8656e6f

3 files changed, 111 insertions(+), 15 deletions(-)

jump to
M static/style.cssstatic/style.css

@@ -14,10 +14,28 @@ --sans-font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

--mono-font: "JetBrains Mono", "SF Mono", SFMono-Regular, ui-monospace, "DejaVu Sans Mono", "Roboto Mono", Menlo, Consolas, monospace; --border-radius: 4px; + --diff-add: #2e7d32; + --diff-del: #c62828; +} + +[data-theme="dark"] { + color-scheme: dark; + --red: #e8494f; + --red-dark: #c1272d; + --red-light: #f06a6f; + --white: #0d0d0d; + --light: #1a1a1a; + --light-gray: #2a2a2a; + --medium-gray: #3a3a3a; + --gray: #888; + --dark: #ccc; + --darker: #eee; + --diff-add: #66bb6a; + --diff-del: #ef5350; } @media (prefers-color-scheme: dark) { - :root { + :root:not([data-theme="light"]) { color-scheme: dark light; --red: #e8494f; --red-dark: #c1272d;

@@ -29,6 +47,8 @@ --medium-gray: #3a3a3a;

--gray: #888; --dark: #ccc; --darker: #eee; + --diff-add: #66bb6a; + --diff-del: #ef5350; } }

@@ -596,23 +616,11 @@ font-weight: 400;

} .diff-add { - color: #2e7d32; -} - -@media (prefers-color-scheme: dark) { - .diff-add { - color: #66bb6a; - } + color: var(--diff-add); } .diff-del { - color: #c62828; -} - -@media (prefers-color-scheme: dark) { - .diff-del { - color: #ef5350; - } + color: var(--diff-del); } .diff-noop {

@@ -853,3 +861,45 @@ header h2 {

font-size: 1.1rem; } } + +/* ── Theme Toggle ── */ + +#theme-toggle { + font: inherit; + cursor: pointer; + background: none; + border: 2px solid var(--medium-gray); + border-radius: var(--border-radius); + padding: 0.3rem 0.8rem; + font-size: 0.85rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--darker); + transition: all 0.15s ease; + line-height: inherit; + display: inline-flex; + align-items: center; + gap: 0.35rem; +} + +#theme-toggle:hover { + color: var(--red); + border-color: var(--red); +} + +#theme-toggle.theme-system::before { content: "🖥"; } +#theme-toggle.theme-light::before { content: "☀"; } +#theme-toggle.theme-dark::before { content: "☾"; } + +.theme-label { + margin-left: auto; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--gray); + display: flex; + align-items: center; + padding-right: 0.3rem; +}
M templates/head.htmltemplates/head.html

@@ -28,5 +28,49 @@ {{ if and .servername .gomod }}

<meta name="go-import" content="{{ .servername}}/{{ .name }} git https://{{ .servername }}/{{ .name }}"> {{ end }} <!-- other meta tags here --> + <script> + (function() { + function getCookie(name) { + var m = document.cookie.match('(?:^|;)\\s*' + name + '\\s*=\\s*([^;]*)'); + return m ? m[1] : null; + } + var theme = getCookie('theme') || 'system'; + + // Apply theme to <html> immediately (prevents flash of wrong theme) + if (theme === 'dark') document.documentElement.setAttribute('data-theme', 'dark'); + else if (theme === 'light') document.documentElement.setAttribute('data-theme', 'light'); + else document.documentElement.removeAttribute('data-theme'); + + function updateButton(t) { + var btn = document.getElementById('theme-toggle'); + if (btn) { + btn.className = 'theme-' + t; + btn.textContent = t; + } + } + + // Update button once DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', function() { updateButton(theme); }); + } else { + updateButton(theme); + } + + window.__themeState = theme; + window.__themeApply = function(t) { + if (t === 'dark') document.documentElement.setAttribute('data-theme', 'dark'); + else if (t === 'light') document.documentElement.setAttribute('data-theme', 'light'); + else document.documentElement.removeAttribute('data-theme'); + updateButton(t); + window.__themeState = t; + }; + })(); + function cycleTheme() { + var cur = window.__themeState || 'system'; + var next = cur === 'system' ? 'light' : cur === 'light' ? 'dark' : 'system'; + document.cookie = 'theme=' + next + '; path=/; max-age=31536000; SameSite=Lax'; + window.__themeApply(next); + } + </script> </head> {{ end }}
M templates/nav.htmltemplates/nav.html

@@ -9,6 +9,8 @@ <li><a href="/{{ .name }}/tree/{{ .ref }}/">tree</a></li>

<li><a href="/{{ .name }}/log/{{ .ref }}">log</a></li> {{ end }} {{ end }} + <li class="theme-label">Theme</li> + <li><button id="theme-toggle" onclick="cycleTheme()">system</button></li> </ul> </nav> {{ end }}