From 9890c826199a1343b0e4c9de23120a2ac74c021b Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 08:32:45 +1000 Subject: [PATCH 01/22] style(topics): enlarge topic tag pills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump topic-cloud pill font (0.78→0.85rem) and padding for a larger tap target. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/css/main.css b/assets/css/main.css index 0d6fb8e..18dc629 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -249,13 +249,13 @@ main, gap: 0.4rem 0.5rem; margin-bottom: 2.5rem; font-family: var(--mono); - font-size: 0.78rem; + font-size: 0.85rem; } .topics__item { display: inline-flex; align-items: center; gap: 0.35em; - padding: 0.25em 0.6em; + padding: 0.4em 0.85em; border: 1px solid var(--border); border-radius: 999px; } From eb2787af200fd540a5e97c642c7eddd7c645f728 Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 08:34:12 +1000 Subject: [PATCH 02/22] feat(console): hide console on mobile The keyboard-driven terminal has no place on small touch screens, so hide the launcher and window below 34rem. JS still loads but the UI stays gone; !important beats the hidden-attr removal JS does on enhance. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/css/main.css b/assets/css/main.css index 18dc629..977e6eb 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -391,4 +391,7 @@ main, @media (max-width: 34rem) { body { font-size: 17px; } .post__title { font-size: 1.6rem; } + /* Console is desktop-only; keyboard-driven terminal has no place on mobile. */ + .console-launch, + .console { display: none !important; } } From 9e031a676f50086978bc93438e5f22b5b416dd20 Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 08:35:09 +1000 Subject: [PATCH 03/22] feat(header): pin navbar on scroll Make the site header position:sticky so it stays at the top while scrolling. Opaque bg and z-index so content scrolls cleanly underneath. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/assets/css/main.css b/assets/css/main.css index 977e6eb..e637a57 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -89,6 +89,10 @@ main, /* ---- header / nav ---- */ .site-header { + position: sticky; + top: 0; + z-index: 40; + background: var(--bg); display: flex; align-items: baseline; justify-content: space-between; From 3324c900124b069734f3a9386eaa80d09064f2da Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 08:38:52 +1000 Subject: [PATCH 04/22] feat(ui): accent fill on hover for buttons and pills Topic pills, the console launcher, and the console bar buttons now fill with the accent colour on hover/focus, flipping text to a new --accent-fg var. Adds a 0.15s transition. Inline nav and prose links stay colour-only. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/assets/css/main.css b/assets/css/main.css index e637a57..8be6dd0 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -28,6 +28,7 @@ --muted: #8a8a8a; --border: #e6e6e6; --accent: #b5451e; /* the pop of colour — change this one line to re-tint */ + --accent-fg: #ffffff; /* text/glyph colour on an accent fill */ --measure: 68ch; --pad: 1.5rem; @@ -156,9 +157,14 @@ main, border-radius: 999px; cursor: pointer; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.12); + transition: background-color 0.15s, border-color 0.15s, color 0.15s; } .console-launch:hover, -.console-launch:focus-visible { color: var(--accent); border-color: var(--accent); } +.console-launch:focus-visible { + background: var(--accent); + border-color: var(--accent); + color: var(--accent-fg); +} .console { position: fixed; @@ -204,15 +210,17 @@ main, .console__btn { background: none; border: 0; - padding: 0 0.25em; + padding: 0.1em 0.4em; + border-radius: 4px; color: var(--muted); font: inherit; font-size: 1rem; line-height: 1; cursor: pointer; + transition: background-color 0.15s, color 0.15s; } .console__btn:hover, -.console__btn:focus-visible { color: var(--accent); } +.console__btn:focus-visible { background: var(--accent); color: var(--accent-fg); } .console__title { color: var(--muted); font-size: 0.75rem; } .console__screen { padding: 0.75rem; @@ -262,10 +270,15 @@ main, padding: 0.4em 0.85em; border: 1px solid var(--border); border-radius: 999px; + transition: background-color 0.15s, border-color 0.15s, color 0.15s; +} +.topics__item:hover { + background: var(--accent); + border-color: var(--accent); + color: var(--accent-fg); } -.topics__item:hover { border-color: var(--accent); } .topics__count { color: var(--muted); } -.topics__item:hover .topics__count { color: var(--accent); } +.topics__item:hover .topics__count { color: var(--accent-fg); } /* ---- post list ---- */ .post-list { list-style: none; margin: 0; padding: 0; } From 3e9026efbf36903c13de32c470ff2fb874d36adf Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 08:44:21 +1000 Subject: [PATCH 05/22] feat(post-list): larger titles, full-row accent fill on hover Bump list title to 1.25rem. On hover the whole row fills with the accent colour, text flipping to --accent-fg. Negative inline margin cancels the new padding so text stays aligned at rest. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/assets/css/main.css b/assets/css/main.css index 8be6dd0..cdbc1cb 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -290,10 +290,15 @@ main, justify-content: space-between; gap: 1rem; padding-block: 0.9rem; + padding-inline: 0.75rem; + margin-inline: -0.75rem; + border-radius: 2px; + transition: background-color 0.1s, color 0.1s; } -.post-list__title { font-weight: 450; } +.post-list__link:hover { background: var(--accent); color: var(--accent-fg); } +.post-list__title { font-weight: 450; font-size: 1.25rem; } .post-list__date { font-family: var(--mono); font-size: 0.8rem; color: var(--muted); white-space: nowrap; } -.post-list__link:hover .post-list__date { color: var(--accent); } +.post-list__link:hover .post-list__date { color: var(--accent-fg); } /* ---- single post ---- */ .post__head { margin-bottom: 2.5rem; } From 9f72c7144ad7afa1612dbd518f5afc06f6bb3a34 Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 09:36:24 +1000 Subject: [PATCH 06/22] feat(themes): paper + dracula colour themes with semantic sets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add two rich, multi-hue themes (paper, dracula) pickable from the console (`theme set `), replacing the earlier monochrome set. Introduces: - a pop palette (--c-red..--c-pink) defined on light/dark bases so every theme has vivid source colours, feeding UI, selection, and mermaid - semantic sets (--link, --pill, --row, --term, --focus, each with a fill-fg) so a theme can tint each surface independently — e.g. dracula keeps the selection grey but makes pills green, rows purple, console pink - ::selection highlight and per-theme --selection / --code vars - mermaid diagrams tinted from the active theme's CSS vars (theme: base) The pre-paint script now applies any saved theme, not just light/dark, so colour themes no longer flash on load. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 96 +++++++++++++++++++++++++++++------- assets/js/console.js | 6 ++- layouts/_default/baseof.html | 23 +++++++-- layouts/partials/header.html | 2 +- 4 files changed, 101 insertions(+), 26 deletions(-) diff --git a/assets/css/main.css b/assets/css/main.css index cdbc1cb..dd8b975 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -29,6 +29,21 @@ --border: #e6e6e6; --accent: #b5451e; /* the pop of colour — change this one line to re-tint */ --accent-fg: #ffffff; /* text/glyph colour on an accent fill */ + --selection: #f3d9cc; /* text-selection highlight */ + --code: var(--accent); /* inline `code` colour */ + + /* semantic sets — groups of UI elements coloured together. Each defaults to + --accent so a theme can re-tint one set (e.g. pills) without touching the + others. Fill sets carry a matching *-fg for text on the filled background. */ + --link: var(--accent); /* links, hovered nav, prose underline */ + --pill: var(--accent); --pill-fg: var(--accent-fg); /* topic tag pills */ + --row: var(--accent); --row-fg: var(--accent-fg); /* post-list row hover */ + --term: var(--accent); --term-fg: var(--accent-fg); /* console: fills, banner, prompt, caret */ + --focus: var(--accent); /* focus-visible outline */ + + /* pop palette — themes remap these; also feed syntax + mermaid diagrams */ + --c-red: #c0392b; --c-orange: #cc6a1a; --c-yellow: #b8860b; --c-green: #2e8b57; + --c-cyan: #1f7a8c; --c-blue: #2c5f8a; --c-purple: #7b4bc4; --c-pink: #b83280; --measure: 68ch; --pad: 1.5rem; @@ -46,12 +61,55 @@ --border: #262626; } } +/* dark base shares its pops with the media-query block above */ +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --selection: #33261f; + --c-red: #ff6b6b; --c-orange: #ffb86c; --c-yellow: #f1fa8c; --c-green: #50fa7b; + --c-cyan: #8be9fd; --c-blue: #7aa2f7; --c-purple: #bd93f9; --c-pink: #ff79c6; + } +} /* forced dark */ :root[data-theme="dark"] { --bg: #111111; --fg: #ededed; --muted: #7d7d7d; --border: #262626; + --selection: #33261f; + --c-red: #ff6b6b; --c-orange: #ffb86c; --c-yellow: #f1fa8c; --c-green: #50fa7b; + --c-cyan: #8be9fd; --c-blue: #7aa2f7; --c-purple: #bd93f9; --c-pink: #ff79c6; +} + +/* ---- colour themes (pick via the console: `theme set `) ---- + Full palettes with pop colours across the whole UI. Selectors use :root[...] + so they match the dark media query's specificity and win by source order. */ + +/* Paper — warm light, ink on cream with vermilion + jewel-tone pops */ +:root[data-theme="paper"] { + --bg: #faf4e8; --fg: #2b2b2b; --muted: #8a7f6a; --border: #e4d8c0; + --accent: #c0392b; --accent-fg: #ffffff; --selection: #efe2c6; --code: #b83280; + --c-red: #c0392b; --c-orange: #cc6a1a; --c-yellow: #b8860b; --c-green: #2e8b57; + --c-cyan: #1f7a8c; --c-blue: #2c5f8a; --c-purple: #7b4bc4; --c-pink: #b83280; + /* sets: each surface its own hue */ + --link: var(--c-red); + --pill: var(--c-purple); --pill-fg: #ffffff; + --row: var(--c-blue); --row-fg: #ffffff; + --term: var(--c-green); --term-fg: #ffffff; + --focus: var(--c-orange); +} + +/* Dracula — the canonical dark palette (draculatheme.com spec) */ +:root[data-theme="dracula"] { + --bg: #282a36; --fg: #f8f8f2; --muted: #6272a4; --border: #44475a; + --accent: #bd93f9; --accent-fg: #282a36; --selection: #44475a; --code: #ff79c6; + --c-red: #ff5555; --c-orange: #ffb86c; --c-yellow: #f1fa8c; --c-green: #50fa7b; + --c-cyan: #8be9fd; --c-blue: #8be9fd; --c-purple: #bd93f9; --c-pink: #ff79c6; + /* sets: highlight stays muted grey, each surface a distinct pop */ + --link: var(--c-cyan); + --pill: var(--c-green); --pill-fg: var(--bg); + --row: var(--c-purple); --row-fg: var(--bg); + --term: var(--c-pink); --term-fg: var(--bg); + --focus: var(--c-orange); } * { box-sizing: border-box; } @@ -74,10 +132,12 @@ a { color: inherit; text-decoration: none; } a:hover, a:focus-visible, .theme-toggle:hover, -.theme-toggle:focus-visible { color: var(--accent); } +.theme-toggle:focus-visible { color: var(--link); } .post__body a { text-decoration: underline; text-underline-offset: 2px; text-decoration-color: var(--border); } -.post__body a:hover { text-decoration-color: var(--accent); } -:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +.post__body a:hover { text-decoration-color: var(--link); } +:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; } +::selection { background: var(--selection); } +::-moz-selection { background: var(--selection); } /* ---- layout ---- */ .site-header, @@ -102,7 +162,7 @@ main, } .site-title { font-family: var(--pixel); font-weight: 400; font-size: 1.75rem; letter-spacing: 0.02em; } .site-nav { display: flex; align-items: baseline; gap: 1rem; font-family: var(--mono); font-size: 0.85rem; } -.site-nav .current { color: var(--accent); } +.site-nav .current { color: var(--link); } .theme-toggle { background: none; @@ -161,9 +221,9 @@ main, } .console-launch:hover, .console-launch:focus-visible { - background: var(--accent); - border-color: var(--accent); - color: var(--accent-fg); + background: var(--term); + border-color: var(--term); + color: var(--term-fg); } .console { @@ -220,7 +280,7 @@ main, transition: background-color 0.15s, color 0.15s; } .console__btn:hover, -.console__btn:focus-visible { background: var(--accent); color: var(--accent-fg); } +.console__btn:focus-visible { background: var(--term); color: var(--term-fg); } .console__title { color: var(--muted); font-size: 0.75rem; } .console__screen { padding: 0.75rem; @@ -237,11 +297,11 @@ main, margin: 0 0 0.5rem; white-space: pre; line-height: 1.02; - color: var(--accent); + color: var(--term); overflow-x: auto; } .console__form { display: flex; align-items: baseline; gap: 0.5em; margin-top: 0.25rem; } -.console__prompt { color: var(--accent); } +.console__prompt { color: var(--term); } .console__input { flex: 1; min-width: 0; @@ -250,7 +310,7 @@ main, padding: 0; color: inherit; font: inherit; - caret-color: var(--accent); + caret-color: var(--term); } .console__input:focus { outline: none; } @@ -273,12 +333,12 @@ main, transition: background-color 0.15s, border-color 0.15s, color 0.15s; } .topics__item:hover { - background: var(--accent); - border-color: var(--accent); - color: var(--accent-fg); + background: var(--pill); + border-color: var(--pill); + color: var(--pill-fg); } .topics__count { color: var(--muted); } -.topics__item:hover .topics__count { color: var(--accent-fg); } +.topics__item:hover .topics__count { color: var(--pill-fg); } /* ---- post list ---- */ .post-list { list-style: none; margin: 0; padding: 0; } @@ -295,10 +355,10 @@ main, border-radius: 2px; transition: background-color 0.1s, color 0.1s; } -.post-list__link:hover { background: var(--accent); color: var(--accent-fg); } +.post-list__link:hover { background: var(--row); color: var(--row-fg); } .post-list__title { font-weight: 450; font-size: 1.25rem; } .post-list__date { font-family: var(--mono); font-size: 0.8rem; color: var(--muted); white-space: nowrap; } -.post-list__link:hover .post-list__date { color: var(--accent-fg); } +.post-list__link:hover .post-list__date { color: var(--row-fg); } /* ---- single post ---- */ .post__head { margin-bottom: 2.5rem; } @@ -346,7 +406,7 @@ main, color: var(--muted); } .post__topics a::before { content: "#"; color: var(--border); } -.post__topics a:hover::before { color: var(--accent); } +.post__topics a:hover::before { color: var(--link); } .post__body table { width: 100%; border-collapse: collapse; diff --git a/assets/js/console.js b/assets/js/console.js index bd89e6e..f153cfe 100644 --- a/assets/js/console.js +++ b/assets/js/console.js @@ -69,13 +69,14 @@ case "theme": { var sub = (cmd.args[0] || "").toLowerCase(); - var modes = ["light", "dark", "auto"]; + // base modes first, then colour palettes (see main.css) + var modes = ["light", "dark", "auto", "paper", "dracula"]; if (sub === "list") { return { lines: modes.map(function (m) { return { text: " " + m }; }) }; } if (sub === "set") { var mode = (cmd.args[1] || "").toLowerCase(); - if (modes.indexOf(mode) === -1) return { lines: [{ text: "usage: theme set " }] }; + if (modes.indexOf(mode) === -1) return { lines: [{ text: "usage: theme set <" + modes.join(" | ") + ">" }] }; return { lines: [{ text: "theme set to " + mode + "." }], theme: mode }; } return { lines: [ @@ -300,6 +301,7 @@ assert.strictEqual(run("", {}).lines.length, 0); assert.ok(/theme/.test(run("help", {}).lines[1].text)); assert.strictEqual(run("theme set dark", {}).theme, "dark"); + assert.strictEqual(run("theme set dracula", {}).theme, "dracula"); assert.ok(/usage: theme set/.test(run("theme set purple", {}).lines[0].text)); assert.ok(/dark/.test(run("theme list", {}).lines[1].text)); assert.ok(/Subcommands/.test(run("theme", {}).lines[4].text)); diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 685b4c8..6e90d7b 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -10,7 +10,7 @@ @@ -28,10 +28,23 @@ {{/* ponytail: mermaid from CDN (Hugo's documented approach); vendor it if you need offline builds. */}} {{ end }} diff --git a/layouts/partials/header.html b/layouts/partials/header.html index 0c96d8c..cba8948 100644 --- a/layouts/partials/header.html +++ b/layouts/partials/header.html @@ -15,7 +15,7 @@ var glyph = { auto: "◐", light: "○", dark: "●" }; function current() { return localStorage.getItem("theme") || "auto"; } function render(m) { - label.textContent = glyph[m]; + label.textContent = glyph[m] || "◆"; // colour themes (set via console) get a generic glyph if (m === "auto") delete document.documentElement.dataset.theme; else document.documentElement.dataset.theme = m; } From d9a59795e337f3565b050823fdc8613ac58a1cd9 Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 09:36:39 +1000 Subject: [PATCH 07/22] feat(highlight): theme-aware code syntax highlighting Switch Chroma to class-based output (markup.highlight.noClasses = false) and map the token classes to the pop palette, so fenced code blocks colour themselves per theme instead of using Chroma's baked-in hex. Also tint inline `code` with the theme's --code var. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 23 ++++++++++++++++++++++- exampleSite/hugo.toml | 4 ++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/assets/css/main.css b/assets/css/main.css index dd8b975..5fddcb7 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -440,7 +440,28 @@ main, padding: 0.1em 0.35em; border: 1px solid var(--border); border-radius: 3px; -} + color: var(--code); +} + +/* syntax highlighting — Chroma token classes mapped to the pop palette so + every theme colours code (config: markup.highlight.noClasses = false) */ +.chroma { background: transparent; } +.chroma .c, .chroma .ch, .chroma .cm, .chroma .c1, .chroma .cs, +.chroma .cp, .chroma .cpf { color: var(--muted); font-style: italic; } /* comments */ +.chroma .k, .chroma .kc, .chroma .kd, .chroma .kn, .chroma .kp, +.chroma .kr, .chroma .kt { color: var(--c-pink); } /* keywords */ +.chroma .o, .chroma .ow { color: var(--c-pink); } /* operators */ +.chroma .s, .chroma .sa, .chroma .sb, .chroma .sc, .chroma .dl, .chroma .sd, +.chroma .s2, .chroma .se, .chroma .sh, .chroma .si, .chroma .sx, .chroma .sr, +.chroma .s1, .chroma .ss { color: var(--c-yellow); } /* strings */ +.chroma .m, .chroma .mb, .chroma .mf, .chroma .mh, .chroma .mi, +.chroma .il, .chroma .mo { color: var(--c-purple); } /* numbers */ +.chroma .nf, .chroma .fm { color: var(--c-green); } /* function names */ +.chroma .nb, .chroma .bp, .chroma .nc, .chroma .nn, +.chroma .ne, .chroma .nt { color: var(--c-cyan); } /* builtins, classes, tags */ +.chroma .nd, .chroma .nl { color: var(--c-orange); } /* decorators, labels */ +.chroma .na, .chroma .nv, .chroma .vc, .chroma .vg, +.chroma .vi { color: var(--c-blue); } /* attributes, variables */ /* ---- post nav ---- */ .post-nav { diff --git a/exampleSite/hugo.toml b/exampleSite/hugo.toml index 81da19d..4c35b99 100644 --- a/exampleSite/hugo.toml +++ b/exampleSite/hugo.toml @@ -27,6 +27,10 @@ themesDir = "../.." [markup.goldmark.renderer] unsafe = true +# Syntax highlighting via CSS classes so the theme (main.css) colours tokens. +[markup.highlight] + noClasses = false + # LaTeX math: $...$ / \(...\) inline, $$...$$ / \[...\] block [markup.goldmark.extensions.passthrough] enable = true From f0319c3dd2302fdba61905f5704f8482670b0bba Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 09:36:46 +1000 Subject: [PATCH 08/22] fix(console): hide on touch devices, not narrow windows The mobile hide keyed on max-width: 34rem, which also fired on a narrow desktop window and made the console vanish. Key on pointer type instead (hover: none and pointer: coarse) so only real touch devices lose it. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/assets/css/main.css b/assets/css/main.css index 5fddcb7..7aa0670 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -494,7 +494,10 @@ main, @media (max-width: 34rem) { body { font-size: 17px; } .post__title { font-size: 1.6rem; } - /* Console is desktop-only; keyboard-driven terminal has no place on mobile. */ +} +/* Console is desktop-only: a keyboard-driven terminal has no place on a touch + device. Key on pointer type, not width, so a narrow desktop window keeps it. */ +@media (hover: none) and (pointer: coarse) { .console-launch, .console { display: none !important; } } From 8f1bb832d78495d09acd8682749ebdfb80dc9383 Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 09:36:55 +1000 Subject: [PATCH 09/22] docs: document colourable surfaces, sets, and adding a theme Add a Themes section to CLAUDE.md covering every colourable surface (core, selection, inline code, syntax, mermaid, pop palette), the semantic sets table, and a recipe for adding a new theme. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- CLAUDE.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 33ba735..5a19d90 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,3 +52,59 @@ Subcommands: - Add every new command to the `help` list (aligned `name : description`). - Add an `assert` to the self-check block for each new branch, then run `node assets/js/console.js`. + +## Themes + +Themes are pure CSS: one `:root[data-theme=""]` block in +`assets/css/main.css` sets the palette. Picked from the console (`theme set +`, `theme list`) — the name list lives in `console.js`'s `theme` command +`modes` array, so add there too. `light`/`dark`/`auto` are the base modes; +everything else is a colour theme. Persistence + no-flash apply happen in +`baseof.html` (pre-paint script) and `header.html` (toggle glyph). + +### Colourable surfaces + +Every colour flows from CSS vars, so a theme controls all of it: + +- **Core**: `--bg`, `--fg`, `--muted`, `--border`. +- **Selection highlight**: `--selection` (the `::selection` background). +- **Inline code**: `--code`. +- **Syntax highlighting**: the pop palette below drives Chroma token classes + (`.chroma .k` keywords, `.s` strings, `.m` numbers, `.nf` functions, etc). + Requires `markup.highlight.noClasses = false` in `hugo.toml`. +- **Mermaid diagrams**: `baseof.html` reads the pop palette + core vars at load + and feeds them to `mermaid.initialize({ theme: "base", themeVariables })`. + Diagrams are tinted at page load — reload after switching themes. +- **Pop palette**: `--c-red --c-orange --c-yellow --c-green --c-cyan --c-blue + --c-purple --c-pink`. Themes remap these; they feed syntax + mermaid and are + handy source colours for the sets below. + +### Sets + +UI chrome is grouped into **sets** — named vars so a theme can tint each surface +independently (e.g. Dracula keeps the selection grey but makes pills green, rows +purple, the console pink). Every set defaults to `--accent` (fill sets also to +`--accent-fg`), so a theme that sets only `--accent` colours everything at once; +override a set to break it out. + +| set | var(s) | elements | +|-----|--------|----------| +| link | `--link` | link hover, hovered nav `.current`, prose underline, `#topic` hover | +| pill | `--pill` / `--pill-fg` | topic tag pills (`.topics__item`) hover fill | +| row | `--row` / `--row-fg` | post-list row (`.post-list__link`) hover fill | +| terminal | `--term` / `--term-fg` | console launcher + button fills, banner, prompt, caret | +| focus | `--focus` | `:focus-visible` outline | + +`*-fg` is the text/glyph colour on a filled (solid-background) set — pick one +with contrast against the fill. + +### Adding a theme + +1. Add a `:root[data-theme=""] { … }` block. Use `:root[...]` (not bare + `[data-theme]`) so it matches the dark media query's specificity and wins by + source order. Set core vars + `--selection` + `--code` + the pop palette; + override any sets you want distinct from `--accent`. +2. Add `` to the `modes` array in `console.js`'s `theme` command, plus an + `assert` for it in the self-check, then run `node assets/js/console.js`. +3. `pnpm build` and eyeball it (`theme set `), including a post with code + and a mermaid diagram. From 053640a8848238df74883b0504c7cf2e7d33a4b4 Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 09:42:15 +1000 Subject: [PATCH 10/22] feat(header): drop theme toggle; system default, console to switch Remove the toggle button next to the site title and its inline script. Theme now follows the OS by default (no data-theme), and is changed via the console (`theme set `); persistence/no-flash stays in the baseof pre-paint script. Deletes the now-dead .theme-toggle CSS. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 28 +--------------------------- layouts/partials/header.html | 24 ------------------------ 2 files changed, 1 insertion(+), 51 deletions(-) diff --git a/assets/css/main.css b/assets/css/main.css index 7aa0670..6871f67 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -130,9 +130,7 @@ a { color: inherit; text-decoration: none; } /* ---- hover pops of colour: every clickable thing lights up ---- */ a:hover, -a:focus-visible, -.theme-toggle:hover, -.theme-toggle:focus-visible { color: var(--link); } +a:focus-visible { color: var(--link); } .post__body a { text-decoration: underline; text-underline-offset: 2px; text-decoration-color: var(--border); } .post__body a:hover { text-decoration-color: var(--link); } :focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; } @@ -164,30 +162,6 @@ main, .site-nav { display: flex; align-items: baseline; gap: 1rem; font-family: var(--mono); font-size: 0.85rem; } .site-nav .current { color: var(--link); } -.theme-toggle { - background: none; - border: 0; - padding: 0; - cursor: pointer; - color: inherit; - font: inherit; - font-size: 1rem; - line-height: 1; - position: relative; - top: 0px; /* nudge glyph down to sit on the nav baseline */ -} -.theme-toggle__label { - display: inline-flex; - align-items: center; - justify-content: center; - width: 1em; - height: 1em; - line-height: 1; - transform-origin: 50% 50%; - transition: transform 0.3s ease; -} -.theme-toggle:hover .theme-toggle__label { transform: rotate(180deg); } - /* ---- back link (term pages) ---- */ .back-link { display: inline-block; diff --git a/layouts/partials/header.html b/layouts/partials/header.html index cba8948..5871aab 100644 --- a/layouts/partials/header.html +++ b/layouts/partials/header.html @@ -2,29 +2,5 @@ {{ site.Title }} - From 39ff1f63b898124f50b629cd931f3ec4277e5651 Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 09:42:20 +1000 Subject: [PATCH 11/22] feat(console): render on every page, not just home Move the console partial from index.html into baseof.html so the launcher and window appear on all pages (posts, lists, taxonomies), not only the home page. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- layouts/_default/baseof.html | 1 + layouts/index.html | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 6e90d7b..d66bbd9 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -24,6 +24,7 @@ {{ block "main" . }}{{ end }} {{ partial "footer.html" . }} + {{ partial "console.html" . }} {{ if .Store.Get "hasMermaid" }} {{/* ponytail: mermaid from CDN (Hugo's documented approach); vendor it if you need offline builds. */}} - {{ $css := resources.Get "css/main.css" | resources.Minify | resources.Fingerprint }} + {{/* main.css + every theme file, concatenated (themes must come after). */}} + {{ $main := resources.Get "css/main.css" }} + {{ $themes := resources.Match "css/themes/*.css" }} + {{ $css := slice $main | append $themes | resources.Concat "css/bundle.css" | resources.Minify | resources.Fingerprint }} {{ block "head" . }}{{ end }} From 68a80d1d03a9821833e4fd0a647b7075faa27341 Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 09:56:22 +1000 Subject: [PATCH 15/22] feat(themes): add valentine theme Blush ground, burgundy ink, all pinks + rose/crimson pops (no purple). Adds valentine.css and registers it in the console theme modes + self-check. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/themes/valentine.css | 14 ++++++++++++++ assets/js/console.js | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 assets/css/themes/valentine.css diff --git a/assets/css/themes/valentine.css b/assets/css/themes/valentine.css new file mode 100644 index 0000000..010dc79 --- /dev/null +++ b/assets/css/themes/valentine.css @@ -0,0 +1,14 @@ +/* Valentine — blush ground, burgundy ink, all pinks + rose/crimson pops + (no purple). Concatenated after main.css (see baseof.html). */ +:root[data-theme="valentine"] { + --bg: #fff0f5; --fg: #4a0d20; --muted: #a86b7e; --border: #f3cdd9; + --accent: #c71f5d; --accent-fg: #ffffff; --selection: #ffd6e5; --code: #b0185a; + --c-red: #a4133c; --c-orange: #d81159; --c-yellow: #b23a48; --c-green: #7b2d43; + --c-cyan: #c9184a; --c-blue: #8c1c3f; --c-purple: #e05780; --c-pink: #ff5c8a; + /* sets: pinks + crimson, each surface distinct */ + --link: var(--c-red); + --pill: var(--c-pink); --pill-fg: #ffffff; + --row: var(--c-cyan); --row-fg: #ffffff; + --term: var(--c-orange); --term-fg: #ffffff; + --focus: var(--c-pink); +} diff --git a/assets/js/console.js b/assets/js/console.js index f153cfe..be60701 100644 --- a/assets/js/console.js +++ b/assets/js/console.js @@ -70,7 +70,7 @@ case "theme": { var sub = (cmd.args[0] || "").toLowerCase(); // base modes first, then colour palettes (see main.css) - var modes = ["light", "dark", "auto", "paper", "dracula"]; + var modes = ["light", "dark", "auto", "paper", "dracula", "valentine"]; if (sub === "list") { return { lines: modes.map(function (m) { return { text: " " + m }; }) }; } @@ -302,6 +302,7 @@ assert.ok(/theme/.test(run("help", {}).lines[1].text)); assert.strictEqual(run("theme set dark", {}).theme, "dark"); assert.strictEqual(run("theme set dracula", {}).theme, "dracula"); + assert.strictEqual(run("theme set valentine", {}).theme, "valentine"); assert.ok(/usage: theme set/.test(run("theme set purple", {}).lines[0].text)); assert.ok(/dark/.test(run("theme list", {}).lines[1].text)); assert.ok(/Subcommands/.test(run("theme", {}).lines[4].text)); From 161e4ff4bbc5d4524a85ab23b354d06dbe4d519b Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 10:00:39 +1000 Subject: [PATCH 16/22] style(footer): fix to bottom-left corner, drop the divider bar Pull the footer out of the centred content column and pin it to the viewport's bottom-left (mirroring the console launcher on the right). Remove the border-top divider. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/assets/css/main.css b/assets/css/main.css index 06ecd46..bda0e0a 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -112,8 +112,7 @@ a:focus-visible { color: var(--link); } /* ---- layout ---- */ .site-header, -main, -.site-footer { +main { max-width: var(--measure); margin-inline: auto; padding-inline: var(--pad); @@ -423,14 +422,15 @@ main, } .post-nav__next { margin-left: auto; text-align: right; } -/* ---- footer ---- */ +/* ---- footer: fixed to the bottom-left corner, no divider bar ---- */ .site-footer { + position: fixed; + left: 1rem; + bottom: 1rem; + z-index: 30; display: flex; - justify-content: space-between; - gap: 1rem; - margin-top: 5rem; - padding-block: 2rem; - border-top: 1px solid var(--border); + flex-direction: column; + gap: 0.2rem; font-family: var(--mono); font-size: 0.75rem; color: var(--muted); From 088349d8f0dcd0f29cfd9d1b6d543670a02ee0b9 Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 10:00:47 +1000 Subject: [PATCH 17/22] feat(post-list): paginate the home list Paginate posts on the home page via .Paginate with pagerSize = 12, plus a minimal mono pager (newer / n / total / older) that only renders past one page. Keeps long archives from turning into an endless scroll. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 14 ++++++++++++++ exampleSite/hugo.toml | 3 +++ layouts/index.html | 10 +++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/assets/css/main.css b/assets/css/main.css index bda0e0a..3f0f6e0 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -306,6 +306,20 @@ main { .post-list__date { font-family: var(--mono); font-size: 0.8rem; color: var(--muted); white-space: nowrap; } .post-list__link:hover .post-list__date { color: var(--row-fg); } +/* ---- pager (paginated post list) ---- */ +.pager { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 1rem; + margin-top: 2rem; + font-family: var(--mono); + font-size: 0.8rem; +} +.pager__link:hover { color: var(--link); } +.pager__link--off { color: var(--border); } +.pager__count { color: var(--muted); } + /* ---- single post ---- */ .post__head { margin-bottom: 2.5rem; } .post__title { margin: 0 0 0.5rem; font-size: 2rem; line-height: 1.2; letter-spacing: -0.02em; } diff --git a/exampleSite/hugo.toml b/exampleSite/hugo.toml index 4c35b99..7db74ff 100644 --- a/exampleSite/hugo.toml +++ b/exampleSite/hugo.toml @@ -4,6 +4,9 @@ title = "basic" theme = "basic" themesDir = "../.." +[pagination] + pagerSize = 12 + [params] description = "A minimal black-and-white theme for text-first blogs." footerNote = "Built with the basic theme." diff --git a/layouts/index.html b/layouts/index.html index df23419..1093cb9 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -3,5 +3,13 @@ {{ $intro := or .Content site.Params.description }} {{ with $intro }}
{{ . }}
{{ end }} {{ partial "topics.html" . }} - {{ partial "post-list.html" (where site.RegularPages "Type" "posts") }} + {{ $paginator := .Paginate (where site.RegularPages "Type" "posts") }} + {{ partial "post-list.html" $paginator.Pages }} + {{ if gt $paginator.TotalPages 1 }} + + {{ end }} {{ end }} From b5acdd10cf4f0fbc9ccd7e70ac87feb8caf71a5c Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 10:10:54 +1000 Subject: [PATCH 18/22] =?UTF-8?q?feat(console):=20=E2=86=91/=E2=86=93=20to?= =?UTF-8?q?=20walk=20previous=20commands?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shell-style recall: ArrowUp steps back through the session's command history, ArrowDown forward, and down past the newest clears the line. Submitting resets the cursor. Caret jumps to end on recall. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/js/console.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/assets/js/console.js b/assets/js/console.js index be60701..8f4f4bf 100644 --- a/assets/js/console.js +++ b/assets/js/console.js @@ -143,6 +143,7 @@ var expandBtn = root.querySelector(".console__expand"); var closeBtn = root.querySelector(".console__close"); var cmdHistory = []; // local — do not shadow window.history + var histIdx = 0; // cursor for ↑/↓ recall; == length means "current line" var write = function (line, kind) { var el = document.createElement("div"); @@ -249,10 +250,22 @@ if (res.music === "off") { chiptune.off(); write({ text: "music off" }); } if (res.volume != null) chiptune.setVolume(res.volume); input.value = ""; + histIdx = cmdHistory.length; // reset recall to the (empty) current line log.scrollTop = log.scrollHeight; if (res.close) close(); }); + // ↑/↓ walk previous commands (shell-style). Down past the newest clears the line. + input.addEventListener("keydown", function (e) { + if (e.key !== "ArrowUp" && e.key !== "ArrowDown" || !cmdHistory.length) return; + e.preventDefault(); + if (e.key === "ArrowUp") { if (histIdx > 0) histIdx--; } + else if (histIdx < cmdHistory.length) histIdx++; + input.value = histIdx >= cmdHistory.length ? "" : cmdHistory[histIdx]; + var end = input.value.length; + input.setSelectionRange(end, end); + }); + root.querySelector(".console__screen").addEventListener("click", function () { input.focus(); }); // Drag by the title bar (skipped while docked). From a7a09756c2809047b0da18181720870460af4f16 Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 10:11:10 +1000 Subject: [PATCH 19/22] fix(post): raise post-nav and pad the page bottom The fixed footer overlapped the tail of long pages, and post-nav sat too far down. Add 6rem bottom padding to main so content scrolls clear of the footer, and drop post-nav margin-top 4rem -> 2rem. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/css/main.css b/assets/css/main.css index 3f0f6e0..704debe 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -117,6 +117,8 @@ main { margin-inline: auto; padding-inline: var(--pad); } +/* clear the fixed footer + give the last block breathing room at page bottom */ +main { padding-bottom: 6rem; } /* ---- header / nav ---- */ .site-header { @@ -428,7 +430,7 @@ main { display: flex; justify-content: space-between; gap: 1rem; - margin-top: 4rem; + margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid var(--border); font-family: var(--mono); From 4809e7015ae32c560966ecf87a0d7b29d12039ad Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 10:11:10 +1000 Subject: [PATCH 20/22] feat(home): rework landing into a front door MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The home page duplicated /posts/: both showed the topic cloud and the full post list. Make the landing distinct — a greeting, the latest post featured (large title, date, topics, summary excerpt, read link), a short "more writing" list of recents, topics, then a link to the full archive. Move the paginator to /posts/ (list.html) so the firehose and pager live in one place. Shorten the hero description copy. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- assets/css/main.css | 57 ++++++++++++++++++++++++++++++++++++++ exampleSite/hugo.toml | 2 +- layouts/_default/list.html | 10 ++++++- layouts/index.html | 39 +++++++++++++++++++------- 4 files changed, 96 insertions(+), 12 deletions(-) diff --git a/assets/css/main.css b/assets/css/main.css index 704debe..32b270f 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -150,6 +150,63 @@ main { padding-bottom: 6rem; } .page-head h1 { margin: 0 0 0.5rem; font-size: 1.6rem; letter-spacing: -0.01em; } .page-intro { color: var(--muted); max-width: 46ch; } +/* ---- landing (home) ---- */ +/* the front door: a greeting, a featured latest post, then a short recent list. + The full archive lives at /posts/, so this page reads differently on purpose. */ +.intro { + max-width: 30ch; + margin: 0.5rem 0 3.5rem; + font-size: clamp(1.5rem, 5vw, 2.1rem); + line-height: 1.25; + letter-spacing: -0.015em; +} + +.eyebrow { + margin: 0 0 1rem; + font-family: var(--mono); + font-size: 0.72rem; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--muted); +} + +.lead { margin-bottom: 3.5rem; } +.lead__title { + margin: 0; + font-size: clamp(1.8rem, 4.5vw, 2.5rem); + line-height: 1.1; + letter-spacing: -0.025em; + font-weight: 500; +} +.lead__meta { + display: flex; + flex-wrap: wrap; + gap: 0.4rem 0.9rem; + margin: 0.75rem 0 0; + font-family: var(--mono); + font-size: 0.8rem; + color: var(--muted); +} +.lead__topics a:not(:hover) { color: var(--muted); } +.lead__summary { max-width: var(--measure); margin: 1.1rem 0 0; color: var(--fg); } +.lead__more { + display: inline-block; + margin-top: 1.1rem; + font-family: var(--mono); + font-size: 0.85rem; + color: var(--link); +} + +.recent { margin-bottom: 2.5rem; } + +.all-link { + display: inline-block; + font-family: var(--mono); + font-size: 0.85rem; + color: var(--muted); +} +.all-link:hover { color: var(--link); } + /* ---- console (floating terminal) ---- */ .console-launch { position: fixed; diff --git a/exampleSite/hugo.toml b/exampleSite/hugo.toml index 7db74ff..fd3074e 100644 --- a/exampleSite/hugo.toml +++ b/exampleSite/hugo.toml @@ -8,7 +8,7 @@ themesDir = "../.." pagerSize = 12 [params] - description = "A minimal black-and-white theme for text-first blogs." + description = "A minimal theme for text-first blogs." footerNote = "Built with the basic theme." [[menu.main]] diff --git a/layouts/_default/list.html b/layouts/_default/list.html index 8d5e0d7..45f183d 100644 --- a/layouts/_default/list.html +++ b/layouts/_default/list.html @@ -4,5 +4,13 @@

{{ .Title }}

{{ with .Content }}
{{ . }}
{{ end }} {{ partial "topics.html" . }} - {{ partial "post-list.html" .Pages }} + {{ $paginator := .Paginate .Pages }} + {{ partial "post-list.html" $paginator.Pages }} + {{ if gt $paginator.TotalPages 1 }} + + {{ end }} {{ end }} diff --git a/layouts/index.html b/layouts/index.html index 1093cb9..acd5fd4 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -1,15 +1,34 @@ {{ define "main" }} - {{/* No h1 — the site title in the header already carries it. Keep the intro only. */}} + {{/* Landing = a front door, not the archive: a greeting, the latest post given + real weight, a few recents, topics, then a link to the full list at /posts/. */}} + {{ $posts := where site.RegularPages "Type" "posts" }} {{ $intro := or .Content site.Params.description }} - {{ with $intro }}
{{ . }}
{{ end }} + + {{ with $intro }}
{{ . }}
{{ end }} + + {{ with index $posts 0 }} +
+

latest

+

{{ .Title }}

+

+ + {{ with .GetTerms "topics" }}{{ range . }}#{{ .LinkTitle }} {{ end }}{{ end }} +

+ {{ with .Summary }}

{{ . | plainify | truncate 220 }}

{{ end }} + read → +
+ {{ end }} + + {{ with first 4 (after 1 $posts) }} +
+

more writing

+ {{ partial "post-list.html" . }} +
+ {{ end }} + {{ partial "topics.html" . }} - {{ $paginator := .Paginate (where site.RegularPages "Type" "posts") }} - {{ partial "post-list.html" $paginator.Pages }} - {{ if gt $paginator.TotalPages 1 }} - + + {{ with site.GetPage "/posts" }} + {{ if gt (len $posts) 5 }}all posts →{{ end }} {{ end }} {{ end }} From f03bf22c318d275ce585701fce5233a7abd8a9dc Mon Sep 17 00:00:00 2001 From: James Jiang Date: Tue, 28 Jul 2026 10:16:01 +1000 Subject: [PATCH 21/22] fix(header): site-title link respects the baseURL subpath `relURL "/"` returns a bare "/", so on a project-subpath deploy (e.g. GitHub Pages at /basic/) the title linked to the domain root and broke the site. Use site.Home.RelPermalink, which prepends the subpath like the nav links already do. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Bgxxw4baXATznPYP4ARHzW --- layouts/partials/header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layouts/partials/header.html b/layouts/partials/header.html index 5871aab..58cbe75 100644 --- a/layouts/partials/header.html +++ b/layouts/partials/header.html @@ -1,5 +1,5 @@