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 :root light 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.js before </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:

  1. A moon icon appears in the top-right navbar (light mode).
  2. Click the moon icon — the page background turns dark (the html/body rule from Task 1 is doing the work; most other UI is still light, that’s expected).
  3. The moon icon swaps to a sun icon.
  4. Refresh the page — the dark choice persists; no white flash on first paint.
  5. Click the sun icon — page returns to light.
  6. Open DevTools → Application → Local Storage. Confirm theme key holds "light" or "dark" matching current state.
  7. Clear localStorage, set OS to dark mode, hard-reload — site loads dark with no flash.
  8. 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-title borders

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 .listfeaturedtag and 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-meta text

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-post and blockquote

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 .share social 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 .highlight block 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).
  • background on .highlight now uses var(--code-bg) so the block background follows the theme.
  • The dark palette is a Monokai-inspired set readable on #11161b.
  • pre.lineno now uses var(--text-muted) instead of the malformed #9999 value 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:

  1. In LIGHT mode: code block has the existing light appearance (off-white background, dark tokens).
  2. Toggle to DARK mode: code block background turns dark (#11161b), tokens render in Monokai-ish colors. Keywords, strings, comments are all distinguishable.
  3. Inline <code> (Markdown backticks) is readable in both modes.
  4. 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-color meta 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):

  1. Set OS to dark → hard reload — site loads dark, no flash.
  2. Set OS to light → hard reload — site loads light, no flash.
  3. 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-invert is left for individual posts)