Dark Mode Implementation Plan
Dark Mode Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Add a dark mode toggle to the leftofnull.com Jekyll blog, defaulting to the OS preference and persisting the user’s explicit choice across sessions.
Architecture: A CSS-custom-properties palette is defined on :root (light) and overridden under [data-theme="dark"]. A small inline boot script in <head> sets <html data-theme> before paint to avoid FOUC. A navbar toggle button writes the choice to localStorage. The existing screen.css is refactored so all color rules reference variables, and _syntax.scss gains dark Rouge tokens scoped under the dark theme.
Tech Stack: Jekyll, Sass (_syntax.scss), Bootstrap 5.3, vanilla JS, Font Awesome 6.
Testing approach
This is a static Jekyll site with no JS unit test framework. “Tests” in this plan are manual verification steps with bundle exec jekyll serve and a browser (DevTools open). Each task ends with explicit visual / functional checks the engineer must perform before committing.
File map
| File | Action | Responsibility |
|---|---|---|
assets/css/screen.css |
Modify | Add :root light tokens + [data-theme="dark"] overrides at top. Refactor existing color literals to var(--token). Add toggle button styles. |
assets/js/theme.js |
Create | Click handler for the toggle button; reads/writes localStorage; flips data-theme. |
_layouts/default.html |
Modify | Inline boot script in <head>; dual theme-color meta tags; toggle <li> in navbar; <script src="theme.js"> before </body>. |
_sass/_syntax.scss |
Modify | Wrap existing .highlight block in :root[data-theme="light"], :root:not([data-theme="dark"]) (default), add sibling [data-theme="dark"] .highlight { … } block with dark Rouge palette. |
Task 1: Establish CSS custom-property palette
Add the token system without refactoring existing rules. This is reversible and lets us verify the tokens cascade before depending on them.
Files:
-
Modify:
assets/css/screen.css(prepend new content at top) -
Step 1: Add
:rootlight tokens and[data-theme="dark"]overrides
Prepend the following block to the very top of assets/css/screen.css, BEFORE the @media screen and (min-width:1500px) rule at line 6:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
:root {
--bg: #ffffff;
--surface: #ffffff;
--surface-2: #fafafa;
--text: rgba(0, 0, 0, .8);
--text-muted: rgba(0, 0, 0, .44);
--text-faint: rgba(0, 0, 0, .5);
--text-strong: rgba(0, 0, 0, 1);
--border: rgba(0, 0, 0, .125);
--border-faint: rgba(0, 0, 0, .05);
--border-soft: rgba(0, 0, 0, .09);
--link: #2563EB;
--link-hover: #1D4ED8;
--link-active: #1E40AF;
--code-bg: #ffffff;
--code-text: #333333;
--code-border: #E3EDF3;
--tag-bg: rgba(0, 0, 0, .05);
--tag-bg-hover: rgba(0, 0, 0, .07);
--tag-text: rgba(0, 0, 0, .6);
--accent-green: #1C9963;
--accent-green-border: #02B875;
--accent-green-text: #1C9963;
--navbar-bg: rgba(255, 255, 255, .97);
--footer-bg: #ffffff;
--shadow-card: 0 1px 7px rgba(0, 0, 0, .05);
--shadow-nav: 0 2px 2px -2px rgba(0, 0, 0, .15);
--lazy-bg: #f2f2f2;
--sep: #999999;
--bottom-border: #dddddd;
}
[data-theme="dark"] {
--bg: #0f1419;
--surface: #1a2027;
--surface-2: #11161b;
--text: rgba(255, 255, 255, .87);
--text-muted: rgba(255, 255, 255, .55);
--text-faint: rgba(255, 255, 255, .45);
--text-strong: rgba(255, 255, 255, 1);
--border: rgba(255, 255, 255, .12);
--border-faint: rgba(255, 255, 255, .06);
--border-soft: rgba(255, 255, 255, .09);
--link: #60A5FA;
--link-hover: #93C5FD;
--link-active: #BFDBFE;
--code-bg: #11161b;
--code-text: rgba(255, 255, 255, .87);
--code-border: rgba(255, 255, 255, .08);
--tag-bg: rgba(255, 255, 255, .08);
--tag-bg-hover: rgba(255, 255, 255, .12);
--tag-text: rgba(255, 255, 255, .7);
--accent-green: #10b981;
--accent-green-border: #10b981;
--accent-green-text: #34d399;
--navbar-bg: rgba(15, 20, 25, .97);
--footer-bg: #0f1419;
--shadow-card: 0 1px 7px rgba(0, 0, 0, .4);
--shadow-nav: 0 2px 2px -2px rgba(0, 0, 0, .5);
--lazy-bg: #11161b;
--sep: rgba(255, 255, 255, .25);
--bottom-border: rgba(255, 255, 255, .15);
}
html, body {
background-color: var(--bg);
color: var(--text);
}
The final 3 lines (html, body { … }) make the page background follow the token immediately, so we can verify Task 2 visually before doing the big refactor.
- Step 2: Verify Sass / Jekyll compiles cleanly
Run:
1
bundle exec jekyll build
Expected: build succeeds with no errors. No need to start the server yet.
- Step 3: Commit
1
2
3
4
5
6
git add assets/css/screen.css
git commit -m "feat(dark-mode): add CSS custom-property palette
Defines the light/dark token sets on :root and [data-theme=\"dark\"].
No existing color rules use the tokens yet — this is the foundation
for the refactor in a later commit."
Task 2: Add boot script, toggle button, and theme.js
After this task the toggle works end-to-end against the html/body rule we added in Task 1 — the rest of the page is still hard-coded, but the user can prove the wiring with a single working surface.
Files:
- Create:
assets/js/theme.js - Modify:
_layouts/default.html -
Modify:
assets/css/screen.css(add toggle button styles) - Step 1: Create
assets/js/theme.js
Create the file with this exact content:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(function () {
'use strict';
function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
try {
localStorage.setItem('theme', theme);
} catch (e) {
/* localStorage unavailable; in-memory only */
}
}
function currentTheme() {
return document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
}
function init() {
var button = document.getElementById('theme-toggle');
if (!button) return;
button.addEventListener('click', function () {
applyTheme(currentTheme() === 'dark' ? 'light' : 'dark');
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
- Step 2: Add inline boot script to
_layouts/default.html
In _layouts/default.html, find the <title> line (line 14):
1
<title>Dark Mode Implementation Plan | Left of Null</title>
Immediately AFTER it, insert this inline <script> (it must appear before any stylesheet <link>, so it sets the attribute before paint):
1
2
3
4
5
6
7
8
9
10
11
<script>
(function () {
try {
var saved = localStorage.getItem('theme');
var theme = saved || (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
} catch (e) {
document.documentElement.setAttribute('data-theme', 'light');
}
})();
</script>
- Step 3: Add the toggle button to the navbar
In _layouts/default.html, find the search include (around line 94):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<script src="/assets/js/lunr.js"></script>
<style>
.lunrsearchresult .title {
color: #d9230f;
}
.lunrsearchresult .url {
color: silver;
}
.lunrsearchresult a {
display: block;
color: #777;
}
.lunrsearchresult a:hover,
.lunrsearchresult a:focus {
text-decoration: none;
}
.lunrsearchresult a:hover .title {
text-decoration: underline;
}
</style>
<form class="bd-search" onSubmit="return lunr_search(document.getElementById('lunrsearch').value);">
<input type="text" class="form-control text-small launch-modal-search" id="lunrsearch" name="q" maxlength="255"
value="" placeholder="Type and enter..." />
</form>
<div id="lunrsearchresults">
<ul></ul>
</div>
<script src="/assets/js/lunrsearchengine.js"></script>
Immediately AFTER that line and BEFORE the closing </ul> of .navbar-nav, add a new <li>:
1
2
3
4
5
6
<li class="nav-item theme-toggle-item">
<button id="theme-toggle" type="button" class="nav-link theme-toggle" aria-label="Toggle dark mode">
<i class="fas fa-moon theme-icon-moon" aria-hidden="true"></i>
<i class="fas fa-sun theme-icon-sun" aria-hidden="true"></i>
</button>
</li>
- Step 4: Load
theme.jsbefore</body>
In _layouts/default.html, find this line (around line 208):
1
<script src="/assets/js/mediumish.js"></script>
Immediately AFTER it, add:
1
<script src="/assets/js/theme.js"></script>
- Step 5: Add toggle button styles to
screen.css
Append the following to the END of assets/css/screen.css:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* Dark mode toggle button */
.theme-toggle {
background: transparent;
border: 0;
cursor: pointer;
padding: 0.5rem 0.8rem;
color: var(--text-muted);
font-size: 1rem;
line-height: 1;
}
.theme-toggle:hover {
color: var(--text);
}
.theme-toggle .theme-icon-sun {
display: none;
}
.theme-toggle .theme-icon-moon {
display: inline-block;
}
[data-theme="dark"] .theme-toggle .theme-icon-sun {
display: inline-block;
}
[data-theme="dark"] .theme-toggle .theme-icon-moon {
display: none;
}
- Step 6: Start the dev server and verify the toggle
Run:
1
bundle exec jekyll serve
Open http://127.0.0.1:4000 in a browser. Verify all of the following:
- A moon icon appears in the top-right navbar (light mode).
- Click the moon icon — the page background turns dark (the
html/bodyrule from Task 1 is doing the work; most other UI is still light, that’s expected). - The moon icon swaps to a sun icon.
- Refresh the page — the dark choice persists; no white flash on first paint.
- Click the sun icon — page returns to light.
- Open DevTools → Application → Local Storage. Confirm
themekey holds"light"or"dark"matching current state. - Clear
localStorage, set OS to dark mode, hard-reload — site loads dark with no flash. - Disable JavaScript (DevTools → Settings → Debugger → Disable JavaScript), hard-reload — site loads light, toggle button still visible but inert (click does nothing). No console errors that break the page.
If any check fails, fix before committing.
- Step 7: Commit
1
2
3
4
5
6
git add assets/js/theme.js _layouts/default.html assets/css/screen.css
git commit -m "feat(dark-mode): wire toggle button and persistence
Adds the inline boot script that sets data-theme pre-paint, the navbar
toggle button with sun/moon icon swap, and theme.js to handle clicks
and persist the choice in localStorage."
Task 3: Refactor screen.css color rules to use tokens
This is the bulk of the work — replace every hard-coded color with a token reference. Work top-to-bottom through the file. After this task, the entire site (except code blocks) renders correctly in both themes.
Files:
- Modify:
assets/css/screen.css
For each rule listed below, find the existing rule and replace the indicated value. The line numbers below match the file BEFORE Task 1’s additions — after Task 1, all these lines have shifted down by ~67 lines, so search by selector rather than by line number.
- Step 1: Refactor link colors
Find:
1
2
3
4
5
6
7
8
9
10
11
12
13
a {
color: #2563EB;
transition: all 0.2s;
}
a:hover {
color: #1D4ED8;
text-decoration: none;
}
a:active {
color: #1E40AF;
}
Replace the three color values:
1
2
3
4
5
6
7
8
9
10
11
12
13
a {
color: var(--link);
transition: all 0.2s;
}
a:hover {
color: var(--link-hover);
text-decoration: none;
}
a:active {
color: var(--link-active);
}
- Step 2: Refactor
pre(code block) colors
Find the pre { rule and replace:
border: #E3EDF3 1px solid;→border: var(--code-border) 1px solid;background: #fff;→background: var(--code-bg);-
color: #333;→color: var(--code-text); - Step 3: Refactor
.mediumnavigation
Find .mediumnavigation { background: rgba(255, 255, 255, .97); … } and change:
background: rgba(255, 255, 255, .97);→background: var(--navbar-bg);box-shadow: 0 2px 2px -2px rgba(0, 0, 0, .15);→box-shadow: var(--shadow-nav);
Also override Bootstrap’s navbar-light text colors so they read in dark mode. Find the .mediumnavigation rule and AFTER it, add:
1
2
3
4
5
6
7
8
.mediumnavigation .nav-link,
.mediumnavigation .navbar-brand {
color: var(--text);
}
.mediumnavigation .nav-link:hover {
color: var(--link);
}
- Step 4: Refactor
.section-titleborders
Find .section-title h2 and change:
border-bottom: 1px solid rgba(0, 0, 0, .125);→border-bottom: 1px solid var(--border);
Find .section-title span and change:
-
border-bottom: 1px solid rgba(0, 0, 0, .44);→border-bottom: 1px solid var(--text-muted); -
Step 5: Refactor
.listfeaturedtagand card text
Find .listfeaturedtag { border: 1px solid rgba(0, 0, 0, .125); … } and change:
border: 1px solid rgba(0, 0, 0, .125);→border: 1px solid var(--border);
Find .listfeaturedtag h4.card-text, .listrecent h4.card-text and change:
-
color: rgba(0, 0, 0, .44);→color: var(--text-muted); -
Step 6: Refactor
.post-top-metatext
Find .post-top-meta span and change:
color: rgba(0, 0, 0, .44);→color: var(--text-muted);
Find span.post-date, span.post-read and change:
color: rgba(0, 0, 0, .44);→color: var(--text-muted);
Find span.post-read-more a and change:
color: rgba(0, 0, 0, .44);→color: var(--text-muted);
Find span.post-name a, span.post-read-more a:hover and change:
-
color: rgba(0, 0, 0, .8);→color: var(--text); -
Step 7: Refactor search-input border
Find .mediumnavigation .form-control and change:
border: 1px solid rgba(0, 0, 0, 0.09);→border: 1px solid var(--border-soft);
Append after the existing .mediumnavigation .form-control rule:
1
2
3
4
.mediumnavigation .form-control {
background-color: var(--surface);
color: var(--text);
}
Find .mediumnavigation .dropdown-menu and change:
-
border: 1px solid rgba(0, 0, 0, 0.08);→border: 1px solid var(--border-soft); -
Step 8: Refactor footer
Find .footer { … } and change:
border-top: 1px solid rgba(0, 0, 0, .05) !important;→border-top: 1px solid var(--border-faint) !important;color: rgba(0, 0, 0, .44);→color: var(--text-muted);background: #fff;→background: var(--footer-bg);
Find .link-dark and change:
-
color: rgba(0, 0, 0, .8);→color: var(--text); -
Step 9: Refactor
.article-postandblockquote
Find .article-post { … } and change:
color: rgba(0, 0, 0, .8);→color: var(--text);
Find blockquote { … } and change:
border-left: 4px solid #00ab6b;→border-left: 4px solid var(--accent-green);-
color: rgba(0, 0, 0, .5);→color: var(--text-faint); - Step 10: Refactor
.sharesocial link border
Find .share ul li a { … border: 1px solid #ddd; … } and change:
border: 1px solid #ddd;→border: 1px solid var(--bottom-border);
Find .share ul li a:hover and change:
border-color: #999;→border-color: var(--sep);
Find .share, .share a and change:
color: rgba(0, 0, 0, .44);→color: var(--text-muted);-
fill: rgba(0, 0, 0, .44);→fill: var(--text-muted); - Step 11: Refactor
.graybg, related cards, tags
Find .graybg { background-color: #fafafa; … } and change:
background-color: #fafafa;→background-color: var(--surface-2);
Find .listrelated .card { box-shadow: 0 1px 7px rgba(0, 0, 0, .05); … } and change:
box-shadow: 0 1px 7px rgba(0, 0, 0, .05);→box-shadow: var(--shadow-card);
Find ul.tags li a and change:
background: rgba(0, 0, 0, .05);→background: var(--tag-bg);color: rgba(0, 0, 0, .6);→color: var(--tag-text);
Find ul.tags li a:hover and change:
-
background: rgba(0, 0, 0, .07);→background: var(--tag-bg-hover); -
Step 12: Refactor
.sep, follow/subscribe buttons
Find .sep { … } and change:
background: #999;→background: var(--sep);
Find .btn.follow { … } and change:
border-color: #02B875;→border-color: var(--accent-green-border);color: #1C9963;→color: var(--accent-green-text);
Find .btn.subscribe { … } and change:
background-color: #1C9963;→background-color: var(--accent-green);-
border-color: #1C9963;→border-color: var(--accent-green); - Step 13: Refactor alertbar and form colors
Find .alertbar { … } and change:
background-color: #fff;→background-color: var(--surface);
Find .alertbar input[type="email"] { … } and change:
border: 1px solid #ddd;→border: 1px solid var(--bottom-border);
Find .alertbar input[type="submit"] { … } and change:
background-color: #1C9963;→background-color: var(--accent-green);border: 1px solid #1C9963;→border: 1px solid var(--accent-green);
Find each .form-control::-webkit-input-placeholder, .form-control:-moz-placeholder, .form-control::-moz-placeholder, .form-control:-ms-input-placeholder, .form-control::-ms-input-placeholder rule and change each:
color: rgba(0, 0, 0, .5);→color: var(--text-faint);
Find .authorpage .author-description and change:
color: rgba(0, 0, 0, .6);→color: var(--tag-text);
Find .graybg.authorpage and change:
border-top: 1px solid #f0f0f0;→border-top: 1px solid var(--border-faint);
Find .sociallinks a and change:
-
background: #666;→background: var(--text-muted); -
Step 14: Refactor pagination and miscellaneous
Find .bottompagination span.navigation { … } and change:
color: #999;→color: var(--sep);border-top: 1px solid #ddd;→border-top: 1px solid var(--bottom-border);
Find .pointerup i.fa and change:
color: #eaeaea;→color: var(--border);
Find .lazyimg { … } and change:
-
background: #f2f2f2;→background: var(--lazy-bg); -
Step 15: Override Bootstrap card background in dark mode
Bootstrap’s .card has a hard-coded white background. Append to the end of screen.css:
1
2
3
4
5
6
7
8
9
10
11
12
[data-theme="dark"] .card {
background-color: var(--surface);
color: var(--text);
}
[data-theme="dark"] .card-footer {
background-color: var(--surface);
}
[data-theme="dark"] .navbar-toggler-icon {
filter: invert(1) opacity(.75);
}
- Step 16: Visual verification — full site
Run (or keep running) bundle exec jekyll serve. With the dev server up, open http://127.0.0.1:4000 and DevTools, then verify each page in BOTH themes:
| Page | Toggle to dark | Check |
|---|---|---|
Home / |
✓ | Hero card readable, recent post list legible, category badges visible, footer dark |
| A blog post (click any from home) | ✓ | Body text readable, blockquote bar visible, links blue, post meta legible |
/about |
✓ | Author block readable |
A category archive (e.g. /category/coding/) |
✓ | Title visible, post listing legible |
/404.html |
✓ | Text legible |
| Search (click search icon, type a query) | ✓ | Input has dark background, result list legible |
Use DevTools color picker to spot-check contrast: text on background should be ≥ 4.5:1 (WCAG AA for body text). If any element fails, identify the rule and add a token reference.
Code blocks may still look “off” in dark mode — that’s expected; Task 4 fixes it.
- Step 17: Commit
1
2
3
4
5
6
7
git add assets/css/screen.css
git commit -m "feat(dark-mode): refactor screen.css colors to use tokens
Replaces hard-coded color literals throughout screen.css with var(--token)
references so the existing palette responds to the data-theme attribute.
Adds dark-mode overrides for Bootstrap card/footer backgrounds that
Bootstrap defaults to white."
Task 4: Dark-mode syntax highlighting
_syntax.scss defines the light Rouge palette. Scope it to non-dark theme, then add a dark sibling block.
Files:
-
Modify:
_sass/_syntax.scss -
Step 1: Replace the existing
.highlightblock with theme-scoped variants
Open _sass/_syntax.scss. The entire current file (lines 1–326) defines .highlight with light colors and ends with td.rouge-code / pre.lineno rules.
Replace the ENTIRE file content with:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
.highlight {
background: var(--code-bg);
border: 0;
padding: 0;
margin-bottom: 1.7rem;
color: var(--code-text);
.c, .cm, .c1 { color: #999988; font-style: italic; }
.err { color: #a61717; background-color: #e3d2d2; }
.k, .o, .kc, .kd, .kp, .kr, .gs, .ow { font-weight: bold; }
.cp { color: #999999; font-weight: bold; }
.cs { color: #999999; font-weight: bold; font-style: italic; }
.gd { color: #000000; background-color: #ffdddd; }
.gd .x { color: #000000; background-color: #ffaaaa; }
.ge { font-style: italic; }
.gr, .gt { color: #aa0000; }
.gh { color: #999999; }
.gi { color: #000000; background-color: #ddffdd; }
.gi .x { color: #000000; background-color: #aaffaa; }
.go { color: #888888; }
.gp { color: #555555; }
.gu { color: #aaaaaa; }
.kt { color: #445588; font-weight: bold; }
.m, .mf, .mh, .mi, .mo, .il { color: #009999; }
.s, .sb, .sc, .sd, .s2, .se, .sh, .si, .sx, .s1 { color: #d14; }
.na, .no, .nv, .vc, .vg, .vi { color: #008080; }
.nb { color: #0086b3; }
.nc { color: #445588; font-weight: bold; }
.ni { color: #800080; }
.ne, .nf { color: #990000; font-weight: bold; }
.nn { color: #555555; }
.nt { color: #000080; }
.w { color: #bbbbbb; }
.sr { color: #009926; }
.ss { color: #990073; }
.bp { color: #999999; }
}
[data-theme="dark"] .highlight {
.c, .cm, .c1 { color: #75715e; font-style: italic; }
.err { color: #f92672; background-color: transparent; }
.k, .o, .kc, .kd, .kp, .kr, .gs, .ow { color: #f92672; font-weight: bold; }
.cp { color: #75715e; font-weight: bold; }
.cs { color: #75715e; font-weight: bold; font-style: italic; }
.gd { color: #f92672; background-color: #3b1d22; }
.gd .x { color: #f92672; background-color: #5a2229; }
.ge { font-style: italic; }
.gr, .gt { color: #f92672; }
.gh { color: #75715e; }
.gi { color: #a6e22e; background-color: #1f3322; }
.gi .x { color: #a6e22e; background-color: #2a5031; }
.go { color: #888888; }
.gp { color: #aaaaaa; }
.gu { color: #aaaaaa; }
.kt { color: #66d9ef; font-weight: bold; }
.m, .mf, .mh, .mi, .mo, .il { color: #ae81ff; }
.s, .sb, .sc, .sd, .s2, .se, .sh, .si, .sx, .s1 { color: #e6db74; }
.na, .no, .nv, .vc, .vg, .vi { color: #66d9ef; }
.nb { color: #66d9ef; }
.nc { color: #a6e22e; font-weight: bold; }
.ni { color: #ae81ff; }
.ne, .nf { color: #a6e22e; font-weight: bold; }
.nn { color: #f8f8f2; }
.nt { color: #f92672; }
.w { color: #75715e; }
.sr { color: #e6db74; }
.ss { color: #e6db74; }
.bp { color: #75715e; }
}
td.rouge-code {
width: 100%;
}
pre.lineno {
color: var(--text-muted);
}
Notes on the rewrite:
- The light block is consolidated (multiple identical declarations were combined with comma selectors — same colors, less repetition).
backgroundon.highlightnow usesvar(--code-bg)so the block background follows the theme.- The dark palette is a Monokai-inspired set readable on
#11161b. -
pre.linenonow usesvar(--text-muted)instead of the malformed#9999value from the original. - Step 2: Verify Sass compiles
Run:
1
bundle exec jekyll build
Expected: build succeeds with no errors.
- Step 3: Visual verification — a post with code blocks
Restart the dev server if needed (bundle exec jekyll serve). Find a post containing fenced code blocks (e.g., the most recent neovim post — _posts/ has a 2026-05-13 entry mentioned in git log). Open it in the browser.
Verify:
- In LIGHT mode: code block has the existing light appearance (off-white background, dark tokens).
- Toggle to DARK mode: code block background turns dark (
#11161b), tokens render in Monokai-ish colors. Keywords, strings, comments are all distinguishable. - Inline
<code>(Markdown backticks) is readable in both modes. - Line numbers (if any post uses
lineno) are visible but muted.
- Step 4: Commit
1
2
3
4
5
6
git add _sass/_syntax.scss
git commit -m "feat(dark-mode): theme-scoped syntax highlighting
Scopes the existing GitHub-style Rouge palette under the default theme
and adds a Monokai-inspired sibling block under [data-theme=\"dark\"].
Consolidates duplicate declarations with comma selectors."
Task 5: Dual theme-color meta tags
Make the mobile browser chrome bar match the OS theme.
Files:
-
Modify:
_layouts/default.html -
Step 1: Replace the existing
theme-colormeta tag
In _layouts/default.html find line 12:
1
<meta name="theme-color" content="#2563EB">
Replace it with TWO tags:
1
2
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#0f1419" media="(prefers-color-scheme: dark)">
- Step 2: Verify
Run bundle exec jekyll serve and view source on http://127.0.0.1:4000 — confirm both meta tags appear in the rendered HTML.
On a mobile device or in DevTools device emulation, the address bar should be light in light-mode OS and dark in dark-mode OS. (This is OS-driven, not toggle-driven — a known limitation we accepted in the design.)
- Step 3: Commit
1
2
3
4
git add _layouts/default.html
git commit -m "feat(dark-mode): media-scoped theme-color meta tags
Mobile browser chrome now matches the OS preference."
Task 6: End-to-end verification
Final pass with both themes across the whole site. No new code — just confirm everything works together.
- Step 1: Hard reload in both themes
bundle exec jekyll serve, then in a clean browser profile (or with localStorage cleared):
- Set OS to dark → hard reload — site loads dark, no flash.
- Set OS to light → hard reload — site loads light, no flash.
- Click toggle in each → choice persists across reloads regardless of OS pref.
- Step 2: JS-disabled smoke test
Disable JS in DevTools, hard-reload:
- Site renders in light mode.
- Layout is intact.
- Toggle button is visible but doing nothing on click is fine.
-
No console errors that break navigation or content.
- Step 3: Page sweep
Visit each page in both themes; spot-check readability:
/(home)- One post with images
- One post with code blocks
/about- A category archive
/404.html- Search results (click search icon, type a query)
Look for: invisible text, low-contrast borders, light “windows” inside dark pages, broken hover states.
- Step 4: Final commit (only if any cleanup needed)
If Step 3 surfaces any rule still using a hard-coded color, fix it (replace with the appropriate token) and commit:
1
2
git add assets/css/screen.css
git commit -m "fix(dark-mode): adjust contrast / token mapping for <area>"
If nothing needs fixing, no commit. The feature is done.
Out of scope (deliberate, do not implement)
- Per-section theme overrides
- Live-following OS theme after explicit user toggle
- Animating full-page color transitions
- A third “auto” position in the toggle
- Automatic dark-mode treatment of post images (case-by-case
.dark-invertis left for individual posts)