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 Leica black-paint theme across the site, default it from system preference, and let visitors switch between chrome and black from a quiet header control or the homepage finish module.
Architecture: Keep one theme engine for the whole static site. Put semantic color and surface tokens in the shared stylesheet, store theme state on the root element with data-theme, use a small shared JavaScript file for persistence and control syncing, and patch page-level inline styles so both finishes inherit the same material system.
Tech Stack: Jekyll, static HTML, shared CSS, small vanilla JavaScript, local Jekyll preview, GitHub Actions HTML validation
assets/js/theme.js
Responsibility: store theme state, read system preference, persist manual override, sync all theme controls, expose a small init hook for pagesassets/css/style.css
Responsibility: define semantic theme tokens, dark-theme overrides, shared toggle styles, homepage finish-module styles, and shared component statesindex.html
Responsibility: add root bootstrap script, load assets/js/theme.js, add homepage finish module and homepage theme control, replace hard-coded inline colors with semantic variablesabout/index.html
Responsibility: add root bootstrap script, load theme JS, add header control markup, replace inline hard-coded colors with semantic variablesreviews/index.html
Responsibility: add root bootstrap script, load theme JS, add header control markup, replace inline hard-coded colors with semantic variables_layouts/post.html
Responsibility: add root bootstrap script, load theme JS, add header control markup for posts and archive pages, replace inline hard-coded colors with semantic variablesreviews/camera/fujifilm/x100vi/index.html
Responsibility: add root bootstrap script, load theme JS, add header control markup, patch local hard-coded colors if the preview shows dark-theme driftreviews/camera/leica/m11-p/index.html
Responsibility: add root bootstrap script, load theme JS, add header control markup, patch local hard-coded colors if the preview shows dark-theme drift.github/workflows/validate_html.yml
Responsibility: keep existing HTML validation expectations in mind, do not modify unless the implementation exposes a workflow gapscripts/check_markdown_in_html.py
Responsibility: existing validation script, use as-is through the workflow or direct command if neededFiles:
assets/js/theme.jsindex.html, about/index.html, reviews/index.html, _layouts/post.html, reviews/camera/fujifilm/x100vi/index.html, reviews/camera/leica/m11-p/index.htmlTest: manual browser verification on the homepage and one interior page
Write down the exact shared API before coding:
window.GuruTheme = {
init(),
applyTheme(theme),
getPreferredTheme(),
setStoredTheme(theme),
syncControls(theme)
};
Expected:
theme values exposed to UI are chrome and blackgetPreferredTheme() may internally resolve a system fallback
index.htmlInsert a small inline script in <head> before the stylesheet:
<script>
(function () {
try {
var stored = localStorage.getItem("site-theme");
var prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
var theme = stored || (prefersDark ? "black" : "chrome");
document.documentElement.setAttribute("data-theme", theme);
} catch (error) {
document.documentElement.setAttribute("data-theme", "chrome");
}
})();
</script>
Expected:
The root has data-theme before the stylesheet loads
Step 3: Repeat the same bootstrap snippet in each interior template/page head
Apply the same snippet to:
about/index.htmlreviews/index.html_layouts/post.htmlreviews/camera/fujifilm/x100vi/index.htmlreviews/camera/leica/m11-p/index.htmlExpected:
Every major page shell resolves the theme before paint
Step 4: Create assets/js/theme.js with minimal runtime logic
Start with the smallest working implementation:
(function () {
var STORAGE_KEY = "site-theme";
var THEMES = ["chrome", "black"];
function getStoredTheme() {
try {
return localStorage.getItem(STORAGE_KEY);
} catch (error) {
return null;
}
}
function getPreferredTheme() {
var stored = getStoredTheme();
if (THEMES.indexOf(stored) !== -1) return stored;
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "black" : "chrome";
}
function applyTheme(theme) {
document.documentElement.setAttribute("data-theme", theme);
}
window.GuruTheme = { getPreferredTheme: getPreferredTheme, applyTheme: applyTheme };
})();
Expected:
The shared file exists and loads without syntax errors
Step 5: Add theme.js to the homepage and one interior page
Add:
<script src="/assets/js/theme.js" defer></script>
Use relative path form where needed:
assets/js/theme.js from index.html/assets/js/theme.js from nested pagesExpected:
window.GuruTheme exists in the browser console on both pages
Step 6: Extend theme.js to persist overrides and sync controls
Implement:
function setStoredTheme(theme) {
try {
localStorage.setItem(STORAGE_KEY, theme);
} catch (error) {}
}
function syncControls(theme) {
document.querySelectorAll("[data-theme-control]").forEach(function (control) {
var value = control.getAttribute("data-theme-value");
var active = value === theme;
control.setAttribute("aria-pressed", active ? "true" : "false");
control.setAttribute("data-active", active ? "true" : "false");
});
}
Expected:
A manual theme selection can update root state and visible control state together
Step 7: Add DOM init logic in theme.js
Implement init() to:
DOMContentLoaded[data-theme-control]prefers-color-scheme changes only when no stored theme existsExpected:
The runtime is ready for both the homepage module and interior header controls
Step 8: Run a manual smoke check
Run:
bundle exec jekyll serve
Check:
No console syntax errors from theme.js
git add assets/js/theme.js index.html about/index.html reviews/index.html _layouts/post.html reviews/camera/fujifilm/x100vi/index.html reviews/camera/leica/m11-p/index.html
git commit -m "Add shared site theme runtime"
Files:
assets/css/style.cssTest: shared visual states on homepage and interior page
assets/css/style.cssAdd theme-neutral tokens near :root:
:root {
--accent-red: #e30613;
--accent-brass: #b08a52;
--page-bg: #f5f5f7;
--surface-bg: rgba(255, 255, 255, 0.72);
--surface-elevated: #ffffff;
--text-primary: #1c1c1e;
--text-muted: #6a6b70;
--border-subtle: rgba(0, 0, 0, 0.08);
--header-glass: rgba(245, 245, 247, 0.9);
--grid-dot: rgba(108, 108, 112, 0.42);
}
Expected:
Shared components can switch themes by token instead of literal colors
Step 2: Add [data-theme="black"] overrides
Define dark-theme values:
html[data-theme="black"] {
--page-bg: #111214;
--surface-bg: rgba(17, 18, 20, 0.82);
--surface-elevated: #17181b;
--text-primary: #f1ede4;
--text-muted: #b4aa99;
--border-subtle: rgba(187, 149, 92, 0.18);
--header-glass: rgba(15, 16, 18, 0.88);
--grid-dot: rgba(196, 177, 147, 0.22);
}
Expected:
Switching root theme changes the shared palette immediately
Step 3: Replace shared body and header literals with semantic tokens
Update rules for:
body.dot-grid.header-minimal.logo-link.nav-link.printed-card.printed-card .image-captionExpected:
Shared shell responds to both themes before page-level inline patches
Step 4: Add shared toggle component styles
Create shared classes such as:
.theme-toggle
.theme-toggle-button
.theme-toggle-button[data-active="true"]
.theme-finish-module
.theme-finish-copy
Expected:
Both the header control and homepage module can reuse the same component language
Step 5: Add dark-theme interaction states
Define hover, focus, and active states for:
Expected:
Brass remains restrained and only shows up in selected interaction states
Step 6: Verify token coverage manually
Run:
bundle exec jekyll serve
Check:
The shared header, body background, and dot grid all change when data-theme switches in DevTools
Step 7: Commit the shared CSS layer
git add assets/css/style.css
git commit -m "Add Leica theme tokens and shared controls"
Files:
index.htmlTest: homepage in both themes at desktop and mobile widths
Update styles such as:
.home-intro.feed-title.feed-item.feed-item-date.social-icon.copyright.link-itemExample:
.home-intro { color: var(--text-muted); }
.feed-item { color: var(--text-primary); border-bottom: 1px solid var(--border-subtle); }
Expected:
Homepage text and separators remain readable in both themes
Step 2: Add the homepage finish module markup near the intro/nav cluster
Insert a compact module with:
Example:
<section class="theme-finish-module" aria-label="Choose finish">
<p class="theme-finish-copy">Silver-chrome or black paint, same restraint.</p>
<div class="theme-toggle" role="group" aria-label="Theme finish">
<button type="button" class="theme-toggle-button" data-theme-control data-theme-value="chrome">Chrome</button>
<button type="button" class="theme-toggle-button" data-theme-control data-theme-value="black">Black</button>
</div>
</section>
Expected:
The homepage offers the theme switch without adding a sticky header
Step 3: Adjust homepage spacing so the finish module feels native
Tune margin and gap values around:
Expected:
The module reads as part of the composition, not a utility block
Step 4: Verify homepage interaction
Run:
bundle exec jekyll serve
Check:
Choice persists after reload
git add index.html
git commit -m "Add homepage finish selector"
Files:
about/index.html, reviews/index.html, _layouts/post.html, reviews/camera/fujifilm/x100vi/index.html, reviews/camera/leica/m11-p/index.htmlTest: about, reviews, archive/post, Fujifilm review, Leica review
about/index.htmlUse the same button contract as the homepage, but visually quieter:
<div class="theme-toggle theme-toggle-header" role="group" aria-label="Theme finish">
<button type="button" class="theme-toggle-button" data-theme-control data-theme-value="chrome">Chrome</button>
<button type="button" class="theme-toggle-button" data-theme-control data-theme-value="black">Black</button>
</div>
Expected:
About page header gains the finish switch without changing nav structure more than needed
Step 2: Add the same header control to reviews/index.html and _layouts/post.html
Expected:
Reviews pages, blog archive, and blog posts share one consistent control pattern
Step 3: Add the same header control to the Fujifilm and Leica review pages
Expected:
The duplicated header markup across camera reviews stays aligned with the rest of the site
Step 4: Replace inline hard-coded colors in the modified interior files
Patch values like:
#333#444#555#f9f9f9#fff#eeergba(0, 0, 0, 0.05)Map them to semantic variables such as:
var(--text-primary)var(--text-muted)var(--surface-elevated)var(--border-subtle)Expected:
Interior pages stay readable in the dark theme without file-by-file palette drift
Step 5: Add post-layout theme token support for code and quote surfaces
Update _layouts/post.html inline CSS to use semantic values for:
--code-bgExpected:
Posts remain legible and premium in both finishes
Step 6: Verify all major page shells
Run:
bundle exec jekyll serve
Check:
/about/index.html/reviews/index.html/blog//reviews/camera/fujifilm/x100vi//reviews/camera/leica/m11-p/
git add about/index.html reviews/index.html _layouts/post.html reviews/camera/fujifilm/x100vi/index.html reviews/camera/leica/m11-p/index.html
git commit -m "Apply theme controls across site pages"
Files:
Test: local preview plus existing HTML validation
Run:
bundle exec jekyll serve
Expected:
Site builds successfully at http://localhost:4000
Step 2: Verify theme persistence and system default behavior
Manual checks:
localStorage and confirm system preference decides the first loadchrome, reload, and confirm persistenceblack, reload, and confirm persistenceExpected:
Fresh state follows system preference
Manual checks:
Expected:
Homepage module does not crowd the nav or feed
Run:
python3 scripts/check_markdown_in_html.py
Expected:
No raw Markdown validation failures
Step 5: Review git diff for scope creep
Run:
git status --short
git diff --stat
Expected:
Only theme-related files changed
Step 6: Commit verification fixes if needed
git add assets/css/style.css assets/js/theme.js index.html about/index.html reviews/index.html _layouts/post.html reviews/camera/fujifilm/x100vi/index.html reviews/camera/leica/m11-p/index.html
git commit -m "Refine Leica black theme behavior"
git push -u origin feature/leica-black-theme
gh pr create --fill
Expected:
Status: Approved
Notes: