Top nav was at six items and growing. The anti-patterns catalog and the
visual-mode overlay demo were always two views of the same subject (the
rule set and seeing it caught in place), so collapsing them into one page
reduces nav weight and puts the catalog + live demo next to each other.
Four sections in one scroll: See it (iframe demo), Try it live (specimen
gallery), The catalog (full rule list), Run it yourself (invocation
methods). Sidebar TOC nests the catalog sections under "The catalog" for
deep linking. 301s from the old URLs preserve external links.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three docs sidebar improvements.
1. Collapsible mobile menu. The sidebar on narrow viewports used to
dump 21 skill links and 2 tutorial links inline above the content,
forcing a long scroll past the nav. Add a toggle button at the top
of the sidebar that shows the current page label (e.g. "/overdrive"
or "Getting started") plus a chevron, and collapses the menu behind
it on mobile. Click the button to open/close. On desktop (>=920px)
the toggle is hidden and the menu shows unconditionally as before.
Pure aria-expanded state driven by a small delegated click handler
in render-page.js.
2. Active-state breathing room. The left-border accent on the current
sidebar item used to sit 2px from the text, which felt cramped. Pull
the border 14px to the left via margin-left and push the text 12px
to the right via padding-left. The net result: the accent bar sits
in the layout gutter, the text keeps its alignment with the brand
logo in the header, and there's now 12px of comfortable space
between the border and the text.
3. Active state visibility. The same change makes the accent bar more
visible on desktop, since it no longer hugs the text. 'aria-current'
was already being set correctly on /skills/* and /tutorials/* pages;
the bar just looked too subtle at 2px of clearance.
Ported the homepage's padding-margin trick: .split-comparison now has
20px of padding around the visible box, with a matching negative
top/bottom margin so the padding does not affect layout flow. The
pointer event listeners move from .split-container to .split-comparison
so the hover tracking engages inside the buffer and only resets when
the mouse leaves the full padded area. Percentage math still reads
.split-container.getBoundingClientRect() so the divider position stays
aligned with the visible box.
This matches how the landing-page split demo feels: graze the edge
and the divider holds; leave the box entirely and it eases back.
Four issues reported on /skills/overdrive (and every other skill demo):
1. The demo block was centered inside the content column, looking odd
against the otherwise left-aligned page. Drop 'margin: 0 auto' from
both .split-comparison and .split-container, and remove the nested
max-width so the whole demo left-aligns at 500px max-width with no
centering.
2. The BEFORE and AFTER labels were stretching beyond the demo box
because .split-comparison (560px) was wider than .split-container
(500px) and .split-labels was using justify-content: space-between
across the wider parent. Collapse the two max-widths to a single
500px cap so the labels now sit flush with the container edges.
3. The label row was sitting way below the demo (16px margin-top plus
the height-stretched container). Tighten margin-top to 10px.
4. The inline split-compare handler only supported click-and-drag. The
homepage effect also tracks hover on devices with hover:hover, so
the mouse sweeps the divider and leaving the box eases it back to
the default. Port that behavior: matchMedia('(hover: hover)') to
detect, pointerenter/leave to toggle a hovering flag, pointerdown/up
for drag, and a tiny lerp on requestAnimationFrame so the return to
center feels smooth. Eyebrow text now reads 'Drag or hover to
compare' to signal both modes.
Also drop text-align: center on .skill-demo-eyebrow and .skill-demo-caption
for the same left-align consistency.
Two changes bundled:
1. Before/after split demos on every skill detail page.
- loadCommandDemos() in sub-pages-data.js: dynamically imports each
module in public/js/demos/commands (the same files the homepage
uses), returning a { skillId: { id, caption, before, after } } map.
Falls back to a warn-and-continue if a demo file can't be loaded
so one bad demo doesn't break the whole generator.
- buildSubPageData becomes async; caller in build-sub-pages.js
awaits it.
- Each skill object gets a .demo field (may be null for /shape).
- renderSkillDemo() produces the .split-comparison markup matching
the homepage: .split-container with .split-before + .split-after
+ .split-divider, plus Before/After labels and the caption. The
block sits between the detail header and the editorial wrapper
so readers see the visual before reading any prose.
- sub-pages.css ports the core .split-* layout from main.css (the
.slop-* and .impeccable-* helpers are homepage-specific and not
copied). Height is 360px to match the docs column.
- render-page.js grows a lightweight inline split-compare init
script (60 lines of vanilla JS) that handles drag and the skewed
clip-path without depending on the homepage's full lerp/ResizeObserver
module. Runs only on pages that actually have .split-container.
2. Sidebar reorder: Tutorials first, then skills.
Walk-throughs are the on-ramp; they belong at the top of the sidebar
where a new visitor will find them. Add <hr class="skills-sidebar-divider">
between the Tutorials group and the first skill category so the two
sections read as distinct.
Verified: /skills/polish, /skills/bolder, /skills/critique all render
the demo block. /skills/shape correctly has none. Sidebar on any /skills
or /tutorials page shows Tutorials first, then a thin mist divider,
then Create / Evaluate / Refine / Simplify / Harden / System skill
categories.
Two small-to-medium improvements bundled together.
1. Copy buttons on every rendered code block.
- render-markdown.js: wrap each fenced code block in a .code-block-wrap
container and emit a <button class="code-block-copy" data-copy="...">
alongside it. Button text is set via CSS ::before content so the
'Copy' / 'Copied' label is a single toggle class (.is-copied).
- render-page.js: 12-line inline script at the end of the body wires
a delegated click handler that calls navigator.clipboard.writeText
and flips .is-copied for 1.5s.
- sub-pages.css: button styles matching the dark terminal palette,
hidden until you hover the code block, accent-colored success state.
2. Merge Skills and Tutorials under a single Docs nav item.
- Rename the Skills nav link to 'Docs' in every header (partial +
4 hand-authored pages). Drop the separate Tutorials nav item; it
now lives inside Docs. Anti-patterns stays as its own top-level.
- scripts/build-sub-pages.js: replace renderSkillsSidebar and
renderTutorialsSidebar with a unified renderDocsSidebar that shows
every skill category followed by a Tutorials group. Takes a
current descriptor of shape { kind: 'skill'|'tutorial', id|slug }
so both skill detail and tutorial detail pages can mark the active
row. activeNav on every /skills/* and /tutorials/* page is now
'docs'; the shared site header's data-nav matches.
Verified: /skills/polish and /tutorials/getting-started both render
with the unified Docs sidebar (all 21 skills grouped by category +
both tutorials as a final group). The Docs nav item is aria-current
on both. Copy buttons appear on every fenced code block and toggle
to 'Copied' when clicked.
Ships the first new sub-page section. Every user-invocable skill now has
its own page at /skills/{id}, with the canonical SKILL.md body rendered
via marked. The index at /skills lists all 21 skills grouped by category.
Editorial wrappers are opt-in: if content/site/skills/{id}.md exists, it
renders above the canonical body (with a "The skill itself" divider).
All 21 pages currently ship with the auto-rendered body only; hand-written
wrappers land in the next few commits.
- scripts/lib/sub-pages-data.js: builds the data model. Reuses
readSourceFiles() from lib/utils.js for skill content; parses the
ANTIPATTERNS array out of src/detect-antipatterns.mjs; reads optional
editorial wrappers from content/site/skills/*.md; validates that every
user-invocable skill has a category entry (build fails loudly if not).
- scripts/build-sub-pages.js: orchestrator. Writes generated HTML into
public/skills/*.html (gitignored). Called from both scripts/build.js
(before buildStaticSite) and server/index.js (at module load) so dev
and prod share the same generation code path.
- scripts/lib/render-page.js: new assetDepth parameter so generated
pages one level deep under public/ use relative paths (../favicon.svg,
../css/sub-pages.css) that Bun's HTML loader can resolve on disk.
- scripts/build.js: pass generated files into Bun.build entrypoints;
post-process to flatten build/public/* → build/* (Bun preserves the
public/ prefix when entrypoints span multiple depths).
- server/index.js: generateSubPages() runs at module load; new routes
/skills, /skills/:id, /anti-patterns, /tutorials, /tutorials/:slug
serve the pre-generated files via Bun.file().
- public/css/sub-pages.css: adds sub-page layout shell, skills index
grouped-list styling, skill detail header/meta chips/divider, collapsed
<details> reference sections, and a .prose block for rendered markdown
with editorial typography, code blocks, and inline code.
Verified: bun run build produces 26 HTML files (4 hand-authored + 22
generated), all flat under build/. Dev server returns 200 on /skills,
/skills/polish, /skills/impeccable, /skills/critique. Tests pass.
Groundwork for new /skills, /anti-patterns, /tutorials sections.
No user-visible changes yet — this is pure plumbing.
- Split main.css into tokens.css (design tokens + reset, ~100 lines) and
main.css (everything else, imports tokens.css). Lets sub-pages import
only tokens without pulling in the landing-page component CSS.
- Add marked as a dependency.
- Add scripts/lib/render-markdown.js: marked wrapper with a custom link
resolver (skill slugs, reference/*.md anchors, external rel=noopener),
stable heading slugger, and terminal-style code blocks.
- Add scripts/lib/render-page.js: page shell wrapper that injects the
shared site header partial with aria-current marking.
- Add content/site/partials/header.html: shared site header with nav
(Home / Skills / Anti-Patterns / Tutorials / Gallery / GitHub).
- Add public/css/sub-pages.css: shared styles for generated pages, with
.site-header styling (sticky, backdrop blur, accent-underlined active
nav item) and mobile collapse.
Build still produces the same 104 KB landing-page CSS chunk; tests pass.