theme: New progressive theme toggle

This works without JavaScript, and starts out with the user preference
(according to the browser) by default. If JavaScript is available, the
toggle writes to LocalStorage.
This commit is contained in:
Lorenz Leutgeb
2026-04-25 02:02:08 +02:00
parent 72b9c1fa73
commit 3d352f283f
7 changed files with 176 additions and 228 deletions
+11 -1
View File
@@ -1,4 +1,14 @@
<a href="/">
<img id="logo" src="/assets/images/radicle.svg" width="32" height="32" />
</a>
<button class="toggle" id="toggle-theme"><!-- Set by script --></button>
<fieldset id="theme" aria-label="Theme">
<label for="light" id="light-label">
<input type="radio" id="light" name="theme" value="light" onchange="toggleTheme('light')">
<img src="/assets/images/moon.svg" width="32" height="32" />
</label>
<label for="dark" id="dark-label">
<input type="radio" id="dark" name="theme" value="dark" onchange="toggleTheme('dark')">
<img src="/assets/images/sun.svg" width="32" height="32" />
</label>
</fieldset>
-1
View File
@@ -15,7 +15,6 @@
</div>
<nav>
</nav>
<button class="toggle" id="toggle-theme"><!-- Set by script --></button>
</header>
<main>
<h1>{{ page.title }}</h1>
-1
View File
@@ -14,7 +14,6 @@
</div>
<nav>
</nav>
<button class="toggle" id="toggle-theme"><!-- Set by script --></button>
</header>
<main>
<h1>{{ page.title }}</h1>
+73 -41
View File
@@ -1,46 +1,24 @@
:root {
--color-pink: #ff55ff;
--color-pink: light-dark(#ff55ff, #f999f9);
--color-yellow: #ffff99;
--color-secondary: #6666ff;
--color-secondary: light-dark(#6666ff, #9999ff);
--color-fg-highlight: var(--color-yellow);
--color-fg-highlight-tertiary: #1f3f8a;
--color-fg-highlight-secondary: #229944;
--color-bg-highlight: #efefff;
--color-bg-default: #f5f5ff;
--color-fg-contrast: #232563;
--color-fg-low-contrast: #434583; /* TODO: tweak this */
--color-fg-dim: #5a5a7a;
--color-fg-emphasized: #7070FF;
--color-fg-match-background: #f5f5ff;
--color-fill-secondary: #7070FF;
--color-fill-separator: #dbdbff;
--color-fill-ghost: #EBEBFF;
--color-border-hint: #DBDBFF;
--color-border-focus: #8585FF;
--color-border-default: var(--color-fg-dim);
--color-selection: var(--color-fg-contrast);
}
:root[data-theme="dark"] {
--color-pink: #f999f9;
--color-yellow: #ffff99;
--color-secondary: #9999ff;
--color-fg-highlight: var(--color-yellow);
--color-fg-highlight-tertiary: #bbeeff;
--color-fg-highlight-secondary: #77dd77;
--color-bg-highlight: #14151a;
--color-bg-default: #0a0d10;
--color-fg-contrast: #f9f9fb;
--color-fg-low-contrast: #d9d9db; /* TODO: tweak this */
--color-fg-dim: #9b9bb1;
--color-fg-highlight-tertiary: light-dark(#1f3f8a,#bbeeff);
--color-fg-highlight-secondary: light-dark(#229944,#77dd77);
--color-bg-highlight: light-dark(#efefff,#14151a);
--color-bg-default: light-dark(#f5f5ff,#0a0d10);
--color-fg-contrast: light-dark(#232563,#f9f9fb);
--color-fg-low-contrast: light-dark(#434583,#d9d9db); /* TODO: tweak this */
--color-fg-dim: light-dark(#5a5a7a,#9b9bb1);
--color-fg-emphasized: #7070ff;
--color-fg-match-background: #0a0d10;
--color-fg-match-background: light-dark(#f5f5ff,#0a0d10);
--color-fill-secondary: #7070ff;
--color-fill-separator: #24252d;
--color-fill-ghost: #24252d;
--color-border-hint: #24252d;
--color-fill-separator: light-dark(#dbdbff,#24252d);
--color-fill-ghost: light-dark(#EBEBFF,#24252d);
--color-border-hint: light-dark(#DBDBFF,#24252d);
--color-border-focus: #8585ff;
--color-selection: var(--color-bg-default);
--color-border-default: var(--color-fg-dim);
--color-selection: light-dark(var(--color-fg-contrast),var(--color-bg-default));
}
::selection {
@@ -83,9 +61,63 @@ body, html {
margin-top: 0.25rem;
margin-bottom: 0.25rem;
}
:root[data-theme=light] #toggle-theme {
background-image: url(/assets/images/sun.svg);
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
}
#dark-label {
display: none;
}
}
:root[data-theme=dark] #toggle-theme {
background-image: url(/assets/images/moon.svg);
@media (prefers-color-scheme: light) {
:root {
color-scheme: light;
}
#light-label {
display: none;
}
}
:root:has(#light:checked) {
color-scheme: light;
}
:root:has(#dark:checked) {
color-scheme: dark;
}
:root:has(#dark:checked) #light-label,
:root:has(#light:checked) #dark-label {
display: block;
margin-left: auto;
}
:root:has(#dark:checked) #dark-label,
:root:has(#light:checked) #light-label {
display: none;
}
#theme {
gap: 0;
margin: 0;
padding: 0;
border: 1px solid var(--color-fg-default);
text-align: right;
}
#theme label:first-child {
margin-left: auto;
}
#theme label {
width: auto;
border: 0;
margin: 0;
padding: 0;
}
#theme input {
display: none;
}
+60 -151
View File
@@ -1,156 +1,65 @@
:root[data-theme="dark"] .highlight .nl { color: var(--color-yellow) }
:root[data-theme="dark"] .highlight .s2 { color: var(--color-fg-highlight-secondary) }
.highlight .nl { color: var(--color-fg-contrast) }
.highlight .s2 { color: var(--color-fg-highlight-secondary) }
/* .highlight pre { background-color: #272822; } */
.highlight .hll { background-color: #272822; }
.highlight .c { color: #75715e } /* Comment */
.highlight .err { color: #960050; background-color: #1e0010 } /* Error */
.highlight .k { color: #66d9ef } /* Keyword */
.highlight .l { color: #ae81ff } /* Literal */
.highlight .n { color: #f8f8f2 } /* Name */
.highlight .o { color: #f92672 } /* Operator */
.highlight .p { color: #f8f8f2 } /* Punctuation */
.highlight .cm { color: #75715e } /* Comment.Multiline */
.highlight .cp { color: #75715e } /* Comment.Preproc */
.highlight .c1 { color: #75715e } /* Comment.Single */
.highlight .cs { color: #75715e } /* Comment.Special */
.highlight .c { color: light-dark(#6a737d, #75715e) } /* Comment */
.highlight .err { color: light-dark(#cb2431, #960050); background-color: light-dark(#ffeaea, #1e0010) } /* Error */
.highlight .k { color: light-dark(#d73a49, #66d9ef) } /* Keyword */
.highlight .l { color: light-dark(#005cc5, #ae81ff) } /* Literal */
.highlight .n { color: light-dark(#24292e, #f8f8f2) } /* Name */
.highlight .o { color: light-dark(#d73a49, #f92672) } /* Operator */
.highlight .p { color: light-dark(#24292e, #f8f8f2) } /* Punctuation */
.highlight .cm { color: light-dark(#6a737d, #75715e) } /* Comment.Multiline */
.highlight .cp { color: light-dark(#d73a49, #75715e) } /* Comment.Preproc */
.highlight .c1 { color: light-dark(#6a737d, #75715e) } /* Comment.Single */
.highlight .cs { color: light-dark(#6a737d, #75715e); font-weight: light-dark(bold, inherit) } /* Comment.Special */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .kc { color: #66d9ef } /* Keyword.Constant */
.highlight .kd { color: #66d9ef } /* Keyword.Declaration */
.highlight .kn { color: #f92672 } /* Keyword.Namespace */
.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */
.highlight .kr { color: #66d9ef } /* Keyword.Reserved */
.highlight .kt { color: #66d9ef } /* Keyword.Type */
.highlight .ld { color: #e6db74 } /* Literal.Date */
.highlight .m { color: #ae81ff } /* Literal.Number */
.highlight .s { color: #e6db74 } /* Literal.String */
.highlight .na { color: #a6e22e } /* Name.Attribute */
.highlight .nb { color: #f8f8f2 } /* Name.Builtin */
.highlight .nc { color: #a6e22e } /* Name.Class */
.highlight .no { color: #66d9ef } /* Name.Constant */
.highlight .nd { color: #a6e22e } /* Name.Decorator */
.highlight .ni { color: #f8f8f2 } /* Name.Entity */
.highlight .ne { color: #a6e22e } /* Name.Exception */
.highlight .nf { color: #a6e22e } /* Name.Function */
.highlight .nl { color: #f8f8f2 } /* Name.Label */
.highlight .nn { color: #f8f8f2 } /* Name.Namespace */
.highlight .nx { color: #a6e22e } /* Name.Other */
.highlight .py { color: #f8f8f2 } /* Name.Property */
.highlight .nt { color: #f92672 } /* Name.Tag */
.highlight .nv { color: #f8f8f2 } /* Name.Variable */
.highlight .ow { color: #f92672 } /* Operator.Word */
.highlight .w { color: #f8f8f2 } /* Text.Whitespace */
.highlight .mf { color: #ae81ff } /* Literal.Number.Float */
.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */
.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */
.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */
.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */
.highlight .sc { color: #e6db74 } /* Literal.String.Char */
.highlight .sd { color: #e6db74 } /* Literal.String.Doc */
.highlight .s2 { color: #e6db74 } /* Literal.String.Double */
.highlight .se { color: #ae81ff } /* Literal.String.Escape */
.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */
.highlight .si { color: #e6db74 } /* Literal.String.Interpol */
.highlight .sx { color: #e6db74 } /* Literal.String.Other */
.highlight .sr { color: #e6db74 } /* Literal.String.Regex */
.highlight .s1 { color: #e6db74 } /* Literal.String.Single */
.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */
.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */
.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */
.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */
.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */
.highlight .kc { color: light-dark(#005cc5, #66d9ef) } /* Keyword.Constant */
.highlight .kd { color: light-dark(#d73a49, #66d9ef) } /* Keyword.Declaration */
.highlight .kn { color: light-dark(#d73a49, #f92672) } /* Keyword.Namespace */
.highlight .kp { color: light-dark(#005cc5, #66d9ef) } /* Keyword.Pseudo */
.highlight .kr { color: light-dark(#d73a49, #66d9ef) } /* Keyword.Reserved */
.highlight .kt { color: light-dark(#d73a49, #66d9ef) } /* Keyword.Type */
.highlight .ld { color: light-dark(#032f62, #e6db74) } /* Literal.Date */
.highlight .m { color: light-dark(#005cc5, #ae81ff) } /* Literal.Number */
.highlight .s { color: light-dark(#032f62, #e6db74) } /* Literal.String */
.highlight .na { color: light-dark(#e36209, #a6e22e) } /* Name.Attribute */
.highlight .nb { color: light-dark(#005cc5, #f8f8f2) } /* Name.Builtin */
.highlight .nc { color: light-dark(#6f42c1, #a6e22e) } /* Name.Class */
.highlight .no { color: light-dark(#005cc5, #66d9ef) } /* Name.Constant */
.highlight .nd { color: light-dark(#6f42c1, #a6e22e) } /* Name.Decorator */
.highlight .ni { color: light-dark(#24292e, #f8f8f2) } /* Name.Entity */
.highlight .ne { color: light-dark(#6f42c1, #a6e22e) } /* Name.Exception */
.highlight .nf { color: light-dark(#6f42c1, #a6e22e) } /* Name.Function */
.highlight .nl { color: light-dark(#24292e, #f8f8f2) } /* Name.Label */
.highlight .nn { color: light-dark(#24292e, #f8f8f2) } /* Name.Namespace */
.highlight .nx { color: light-dark(#6f42c1, #a6e22e) } /* Name.Other */
.highlight .py { color: light-dark(#24292e, #f8f8f2) } /* Name.Property */
.highlight .nt { color: light-dark(#22863a, #f92672) } /* Name.Tag */
.highlight .nv { color: light-dark(#e36209, #f8f8f2) } /* Name.Variable */
.highlight .ow { color: light-dark(#d73a49, #f92672) } /* Operator.Word */
.highlight .w { color: light-dark(#24292e, #f8f8f2) } /* Text.Whitespace */
.highlight .mf { color: light-dark(#005cc5, #ae81ff) } /* Literal.Number.Float */
.highlight .mh { color: light-dark(#005cc5, #ae81ff) } /* Literal.Number.Hex */
.highlight .mi { color: light-dark(#005cc5, #ae81ff) } /* Literal.Number.Integer */
.highlight .mo { color: light-dark(#005cc5, #ae81ff) } /* Literal.Number.Oct */
.highlight .sb { color: light-dark(#032f62, #e6db74) } /* Literal.String.Backtick */
.highlight .sc { color: light-dark(#032f62, #e6db74) } /* Literal.String.Char */
.highlight .sd { color: light-dark(#6a737d, #e6db74) } /* Literal.String.Doc */
.highlight .s2 { color: light-dark(#032f62, #e6db74) } /* Literal.String.Double */
.highlight .se { color: light-dark(#e36209, #ae81ff) } /* Literal.String.Escape */
.highlight .sh { color: light-dark(#032f62, #e6db74) } /* Literal.String.Heredoc */
.highlight .si { color: light-dark(#e36209, #e6db74) } /* Literal.String.Interpol */
.highlight .sx { color: light-dark(#032f62, #e6db74) } /* Literal.String.Other */
.highlight .sr { color: light-dark(#22863a, #e6db74) } /* Literal.String.Regex */
.highlight .s1 { color: light-dark(#032f62, #e6db74) } /* Literal.String.Single */
.highlight .ss { color: light-dark(#005cc5, #e6db74) } /* Literal.String.Symbol */
.highlight .bp { color: light-dark(#005cc5, #f8f8f2) } /* Name.Builtin.Pseudo */
.highlight .vc { color: light-dark(#e36209, #f8f8f2) } /* Name.Variable.Class */
.highlight .vg { color: light-dark(#e36209, #f8f8f2) } /* Name.Variable.Global */
.highlight .vi { color: light-dark(#e36209, #f8f8f2) } /* Name.Variable.Instance */
.highlight .il { color: light-dark(#005cc5, #ae81ff) } /* Literal.Number.Integer.Long */
.highlight .gh { } /* Generic Heading & Diff Header */
.highlight .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */
.highlight .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */
.highlight .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */
/* Light Mode */
/* Light mode syntax highlighting improvements */
:root:not([data-theme="dark"]) .highlight {
/* Base text and background */
.c { color: #6a737d; } /* Comment - GitHub style gray */
.err { color: #cb2431; background-color: #ffeaea; } /* Error */
.k { color: #d73a49; } /* Keyword - GitHub red */
.l { color: #005cc5; } /* Literal - GitHub blue */
.n { color: #24292e; } /* Name - dark gray */
.o { color: #d73a49; } /* Operator - GitHub red */
.p { color: #24292e; } /* Punctuation - dark gray */
/* Comments */
.cm { color: #6a737d; } /* Comment.Multiline */
.cp { color: #d73a49; } /* Comment.Preproc - red for preprocessor */
.c1 { color: #6a737d; } /* Comment.Single */
.cs { color: #6a737d; font-weight: bold; } /* Comment.Special */
/* Keywords */
.kc { color: #005cc5; } /* Keyword.Constant - blue */
.kd { color: #d73a49; } /* Keyword.Declaration - red */
.kn { color: #d73a49; } /* Keyword.Namespace - red */
.kp { color: #005cc5; } /* Keyword.Pseudo - blue */
.kr { color: #d73a49; } /* Keyword.Reserved - red */
.kt { color: #d73a49; } /* Keyword.Type - red */
/* Literals */
.ld { color: #032f62; } /* Literal.Date - dark blue */
.m { color: #005cc5; } /* Literal.Number - blue */
.s { color: #032f62; } /* Literal.String - dark blue */
/* Names */
.na { color: #e36209; } /* Name.Attribute - orange */
.nb { color: #005cc5; } /* Name.Builtin - blue */
.nc { color: #6f42c1; } /* Name.Class - purple */
.no { color: #005cc5; } /* Name.Constant - blue */
.nd { color: #6f42c1; } /* Name.Decorator - purple */
.ni { color: #24292e; } /* Name.Entity - dark gray */
.ne { color: #6f42c1; } /* Name.Exception - purple */
.nf { color: #6f42c1; } /* Name.Function - purple */
.nl { color: #24292e; } /* Name.Label - dark gray */
.nn { color: #24292e; } /* Name.Namespace - dark gray */
.nx { color: #6f42c1; } /* Name.Other - purple */
.py { color: #24292e; } /* Name.Property - dark gray */
.nt { color: #22863a; } /* Name.Tag - green */
.nv { color: #e36209; } /* Name.Variable - orange */
/* Operators */
.ow { color: #d73a49; } /* Operator.Word - red */
.w { color: #24292e; } /* Text.Whitespace - dark gray */
/* Numbers */
.mf { color: #005cc5; } /* Literal.Number.Float - blue */
.mh { color: #005cc5; } /* Literal.Number.Hex - blue */
.mi { color: #005cc5; } /* Literal.Number.Integer - blue */
.mo { color: #005cc5; } /* Literal.Number.Oct - blue */
/* Strings */
.sb { color: #032f62; } /* Literal.String.Backtick - dark blue */
.sc { color: #032f62; } /* Literal.String.Char - dark blue */
.sd { color: #6a737d; } /* Literal.String.Doc - gray */
.s2 { color: #032f62; } /* Literal.String.Double - dark blue */
.se { color: #e36209; } /* Literal.String.Escape - orange */
.sh { color: #032f62; } /* Literal.String.Heredoc - dark blue */
.si { color: #e36209; } /* Literal.String.Interpol - orange */
.sx { color: #032f62; } /* Literal.String.Other - dark blue */
.sr { color: #22863a; } /* Literal.String.Regex - green */
.s1 { color: #032f62; } /* Literal.String.Single - dark blue */
.ss { color: #005cc5; } /* Literal.String.Symbol - blue */
/* Variables */
.bp { color: #005cc5; } /* Name.Builtin.Pseudo - blue */
.vc { color: #e36209; } /* Name.Variable.Class - orange */
.vg { color: #e36209; } /* Name.Variable.Global - orange */
.vi { color: #e36209; } /* Name.Variable.Instance - orange */
.il { color: #005cc5; } /* Literal.Number.Integer.Long - blue */
/* Diff colors */
.gh { color: #24292e; font-weight: bold; } /* Generic Heading */
.gu { color: #6a737d; } /* Generic.Subheading */
.gd { color: #cb2431; background-color: #ffeaea; } /* Generic.Deleted */
.gi { color: #22863a; background-color: #f0fff4; } /* Generic.Inserted */
}
.highlight .gh { color: light-dark(#24292e, inherit) } /* Generic Heading & Diff Header */
.highlight .gu { color: light-dark(#6a737d, #75715e); } /* Generic.Subheading & Diff Unified/Comment? */
.highlight .gd { color: light-dark(#cb2431, #f92672); background-color: light-dark(#ffeaea, transparent) } /* Generic.Deleted & Diff Deleted */
.highlight .gi { color: light-dark(#22863a, #a6e22e); background-color: light-dark(#f0fff4, transparent) } /* Generic.Inserted & Diff Inserted */
+23 -12
View File
@@ -182,29 +182,29 @@ a:hover {
}
.button {
display: inline-block;
color: var(--color-pink);
color: light-dark(var(--color-bg-default), var(--color-pink));
background: none;
background-color: light-dark(var(--color-pink), transparent);
border: 1px dashed var(--color-pink);
margin: 0.5rem 0;
padding: 0.5rem 0.75rem;
cursor: pointer;
}
.button:hover, .button.filled,
:root[data-theme="light"] .button {
.button:hover {
border-color: var(--color-pink);
background-color: var(--color-pink);
background-color: light-dark(var(--color-fg-low-contrast), var(--color-pink));
color: var(--color-bg-default);
}
.button.secondary {
color: var(--color-secondary);
color: light-dark(var(--color-bg-default), var(--color-secondary));
border: 1px dashed var(--color-secondary);
background-color: light-dark(var(--color-secondary), transparent);
}
.button.secondary:hover, .button.secondary.filled,
:root[data-theme="light"] .button.secondary {
.button.secondary:hover {
border-color: var(--color-secondary);
background-color: var(--color-secondary);
background-color: light-dark(var(--color-fg-low-contrast), var(--color-secondary));
color: var(--color-bg-default);
}
@@ -213,10 +213,19 @@ a:hover {
margin: 0;
}
.button.filled, :root[data-theme="light"] .button {
.button.filled {
border-style: none;
border-color: var(--color-pink);
background-color: var(--color-pink);
color: var(--color-bg-default);
}
.button.filled:hover, :root[data-theme="light"] .button:hover {
.button.secondary.filled {
border-color: var(--color-secondary);
background-color: var(--color-secondary);
}
.button.filled:hover {
background-color: var(--color-fg-low-contrast) !important;
color: var(--color-bg-default) !important;
}
@@ -278,8 +287,6 @@ body > footer {
white-space: nowrap;
width: 100%;
height: 2rem;
min-height: 1.5rem;
line-height: 1.5rem;
}
footer {
@@ -292,8 +299,12 @@ body > footer > * {
flex: 1;
display: flex;
justify-content: center;
}
body > footer > span {
align-items: center;
}
body > footer :first-child > * {
margin-right: auto;
}
+9 -21
View File
@@ -1,24 +1,12 @@
const rootElement = document.documentElement;
const themeToggleBtn = document.getElementById("toggle-theme");
const storedTheme = localStorage.getItem("theme");
const defaultTheme = window.matchMedia
&& window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
setTheme(storedTheme || defaultTheme);
if (themeToggleBtn) {
themeToggleBtn.addEventListener("click", () => {
const currentTheme = rootElement.dataset.theme;
if (currentTheme === "dark") {
setTheme("light");
} else {
setTheme("dark");
}
});
function loadTheme() {
const theme = localStorage.getItem("theme") || (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
document.documentElement.style.colorScheme = theme;
document.getElementById(theme).checked = true;
}
function setTheme(theme) {
rootElement.dataset.theme = theme;
loadTheme();
function toggleTheme(theme) {
document.documentElement.style.colorScheme = theme;
localStorage.setItem("theme", theme);
}
}