Skip to content

Commit 26cba1a

Browse files
authored
dark mode scaffolding (#271)
1 parent d6fc806 commit 26cba1a

3 files changed

Lines changed: 237 additions & 0 deletions

File tree

_includes/head-custom.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,18 @@
66
<!-- You can set your favicon here -->
77
<!-- link rel="shortcut icon" type="image/x-icon" href="{{ '/favicon.ico' | relative_url }}" -->
88

9+
<!-- Dark mode (light is the default): apply a stored preference before paint
10+
to prevent a flash. -->
11+
<script>
12+
(function () {
13+
try {
14+
if (localStorage.getItem("paara-theme") === "dark") {
15+
document.documentElement.setAttribute("data-theme", "dark");
16+
}
17+
} catch (e) {}
18+
})();
19+
</script>
20+
<link rel="stylesheet" href="{{ '/assets/css/dark.css' | relative_url }}" type="text/css">
21+
<script src="{{ '/assets/js/theme-toggle.js' | relative_url }}" defer></script>
22+
923
<!-- end custom head snippets -->

assets/css/dark.css

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/* Dark mode overrides. Light is the default; these rules apply only when
2+
data-theme="dark" is set on <html> (see theme-toggle.js). */
3+
4+
/* TOGGLE BUTTON */
5+
6+
.theme-toggle {
7+
position: absolute;
8+
top: 14px;
9+
right: 14px;
10+
z-index: 1000;
11+
display: flex;
12+
align-items: center;
13+
justify-content: center;
14+
width: 40px;
15+
height: 40px;
16+
padding: 0;
17+
color: #fff;
18+
cursor: pointer;
19+
background: rgba(13, 27, 42, 0.35);
20+
border: solid 1px rgba(255, 255, 255, 0.55);
21+
border-radius: 50%;
22+
appearance: none;
23+
-webkit-appearance: none;
24+
transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
25+
}
26+
.theme-toggle:hover {
27+
background: rgba(13, 27, 42, 0.6);
28+
transform: scale(1.06);
29+
}
30+
.theme-toggle:focus-visible {
31+
outline: solid 2px #9ddcff;
32+
outline-offset: 2px;
33+
}
34+
.theme-toggle svg {
35+
display: block;
36+
width: 22px;
37+
height: 22px;
38+
fill: currentColor;
39+
}
40+
41+
/* Moon shows in light mode; sun shows in dark mode. */
42+
.theme-toggle .icon-sun {
43+
display: none;
44+
}
45+
.theme-toggle .icon-moon {
46+
display: block;
47+
}
48+
html[data-theme="dark"] .theme-toggle .icon-sun {
49+
display: block;
50+
}
51+
html[data-theme="dark"] .theme-toggle .icon-moon {
52+
display: none;
53+
}
54+
html[data-theme="dark"] .theme-toggle {
55+
color: #ffd45e;
56+
background: rgba(230, 233, 238, 0.12);
57+
border-color: rgba(230, 233, 238, 0.5);
58+
}
59+
html[data-theme="dark"] .theme-toggle:hover {
60+
background: rgba(230, 233, 238, 0.22);
61+
}
62+
63+
/* LAYOUT STYLES */
64+
65+
html[data-theme="dark"] body {
66+
color: #c7cbd1;
67+
background: #14171c;
68+
background-image: none;
69+
}
70+
71+
html[data-theme="dark"] a {
72+
color: #6cb0f0;
73+
}
74+
html[data-theme="dark"] a:hover {
75+
color: #8cc4f7;
76+
}
77+
78+
html[data-theme="dark"] header {
79+
background: #0d1b2a;
80+
background-image: none;
81+
border-bottom-color: #1d2b3a;
82+
}
83+
84+
html[data-theme="dark"] #content-wrapper {
85+
border-top-color: #2d333b;
86+
}
87+
88+
html[data-theme="dark"] aside#sidebar {
89+
background-image: none;
90+
}
91+
92+
/* The base theme forces the current nav item to black; restore it for dark. */
93+
html[data-theme="dark"] .sidebar.current {
94+
color: #fff;
95+
text-decoration-color: #fff;
96+
}
97+
98+
html[data-theme="dark"] code,
99+
html[data-theme="dark"] pre {
100+
color: #c7cbd1;
101+
}
102+
html[data-theme="dark"] code {
103+
background-color: #1c2128;
104+
border-color: #2d333b;
105+
}
106+
html[data-theme="dark"] pre {
107+
background: #1c2128;
108+
border-color: #2d333b;
109+
}
110+
html[data-theme="dark"] pre code {
111+
color: #c7cbd1;
112+
background-color: transparent;
113+
}
114+
115+
/* COMMON STYLES */
116+
117+
html[data-theme="dark"] hr {
118+
border-top-color: #2d333b;
119+
}
120+
121+
html[data-theme="dark"] table,
122+
html[data-theme="dark"] td {
123+
border-color: #2d333b;
124+
}
125+
126+
html[data-theme="dark"] form {
127+
background: #1c2128;
128+
}
129+
130+
/* GENERAL ELEMENT TYPE STYLES */
131+
132+
html[data-theme="dark"] #main-content h1,
133+
html[data-theme="dark"] #main-content h2,
134+
html[data-theme="dark"] #main-content h3,
135+
html[data-theme="dark"] #main-content h4,
136+
html[data-theme="dark"] #main-content h5,
137+
html[data-theme="dark"] #main-content h6 {
138+
color: #e6e9ee;
139+
}
140+
141+
html[data-theme="dark"] blockquote {
142+
border-left-color: #3a4250;
143+
}
144+
145+
html[data-theme="dark"] footer {
146+
color: #8b919a;
147+
}
148+
html[data-theme="dark"] footer a {
149+
color: #aab1bb;
150+
}
151+
html[data-theme="dark"] footer a:hover {
152+
color: #cfd4db;
153+
}

assets/js/theme-toggle.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Sun/moon dark-mode toggle. Light is the default; a stored preference is
3+
* applied before paint by an inline script in head-custom.html to prevent a
4+
* flash. This file builds the toggle button and remembers the choice.
5+
*/
6+
(function () {
7+
"use strict";
8+
9+
var STORAGE_KEY = "paara-theme";
10+
11+
function currentTheme() {
12+
return document.documentElement.getAttribute("data-theme") === "dark"
13+
? "dark"
14+
: "light";
15+
}
16+
17+
function applyTheme(theme) {
18+
if (theme === "dark") {
19+
document.documentElement.setAttribute("data-theme", "dark");
20+
} else {
21+
document.documentElement.removeAttribute("data-theme");
22+
}
23+
try {
24+
localStorage.setItem(STORAGE_KEY, theme);
25+
} catch (e) {
26+
/* localStorage may be unavailable */
27+
}
28+
var btn = document.querySelector(".theme-toggle");
29+
if (btn) {
30+
btn.setAttribute("aria-pressed", theme === "dark" ? "true" : "false");
31+
}
32+
}
33+
34+
function buildButton() {
35+
var btn = document.createElement("button");
36+
btn.className = "theme-toggle";
37+
btn.type = "button";
38+
btn.setAttribute("aria-label", "Toggle dark mode");
39+
btn.setAttribute("aria-pressed", currentTheme() === "dark" ? "true" : "false");
40+
btn.title = "Toggle light / dark mode";
41+
42+
// Moon shows in light mode (click to go dark); sun shows in dark mode.
43+
var moon =
44+
'<svg class="icon-moon" viewBox="0 0 24 24" aria-hidden="true">' +
45+
'<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>';
46+
47+
var sun =
48+
'<svg class="icon-sun" viewBox="0 0 24 24" aria-hidden="true">' +
49+
'<path d="M12 17a5 5 0 1 1 0-10 5 5 0 0 1 0 10zm0-13a1 1 0 0 1 1 1v1.5a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1zm0 13.5a1 1 0 0 1 1 1V20a1 1 0 1 1-2 0v-1.5a1 1 0 0 1 1-1zM4 12a1 1 0 0 1 1-1h1.5a1 1 0 1 1 0 2H5a1 1 0 0 1-1-1zm13.5 0a1 1 0 0 1 1-1H20a1 1 0 1 1 0 2h-1.5a1 1 0 0 1-1-1zM6.3 6.3a1 1 0 0 1 1.4 0l1.1 1.1a1 1 0 1 1-1.4 1.4L6.3 7.7a1 1 0 0 1 0-1.4zm9 9a1 1 0 0 1 1.4 0l1.1 1.1a1 1 0 0 1-1.4 1.4l-1.1-1.1a1 1 0 0 1 0-1.4zm2.5-9a1 1 0 0 1 0 1.4l-1.1 1.1a1 1 0 1 1-1.4-1.4l1.1-1.1a1 1 0 0 1 1.4 0zm-9 9a1 1 0 0 1 0 1.4l-1.1 1.1a1 1 0 0 1-1.4-1.4l1.1-1.1a1 1 0 0 1 1.4 0z"/></svg>';
50+
51+
btn.innerHTML = moon + sun;
52+
btn.addEventListener("click", function () {
53+
applyTheme(currentTheme() === "dark" ? "light" : "dark");
54+
});
55+
return btn;
56+
}
57+
58+
function init() {
59+
if (document.querySelector(".theme-toggle")) {
60+
return;
61+
}
62+
document.body.appendChild(buildButton());
63+
}
64+
65+
if (document.readyState === "loading") {
66+
document.addEventListener("DOMContentLoaded", init);
67+
} else {
68+
init();
69+
}
70+
})();

0 commit comments

Comments
 (0)