Skip to content

Examples Page Overdrive 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: Bring landing/examples/index.html into the same execution-pointer visual language as the redesigned landing page so the //examples/ transition feels like one site, not two.

Architecture: The examples page is statically generated by scripts/build_examples_gallery.py. All redesign work edits that Python generator's f-string templates and Python constants — never the generated HTML directly. After each edit, regenerate the HTML and visually verify in a browser. The redesign duplicates the landing page's execution-pointer atoms (.exec-dot, .exec-caret, .exec-scan, tokens like --exec-color, --exec-pulse-dur, @keyframes exec-pulse / exec-blink / exec-stamp) into the examples page's inline <style> block to match the landing page's "single self-contained HTML" architecture. The redesign covers six sections: nav dot, terminal-session header, category rail, search row, ls -la card rows, and $ cat card expansion.

Tech Stack: Python 3.9+ (the generator), vanilla HTML/CSS/JS (the output), no new dependencies, no new build steps. Reference spec: docs/superpowers/specs/2026-04-08-examples-page-overdrive-design.md.


Pre-flight: file inventory and conventions

Before starting any task, the implementer should know these absolute facts:

Generator source (where ALL edits land): - scripts/build_examples_gallery.py — 327 lines, single file. The docstring at the top says > site/examples/index.html but the real output path is landing/examples/index.html. The docstring is stale; ignore it.

Generator regeneration command:

python scripts/build_examples_gallery.py > landing/examples/index.html

Run this after every task that changes the generator. Verify the file size is ~600KB (it bundles all 88 example sources inline), and verify line count is ~180.

Key locations inside scripts/build_examples_gallery.py (current line numbers — they will shift as edits land, so re-grep if uncertain):

What Generator lines Generated HTML lines
:root CSS vars 247 (the long :root{{...}} line) 13
nav markup 288-291 54-57
.ph page header 292 58
.ct filter container 293-297 59-80
.cb chip row template 295 (consumes cat_btns from 178-183) 61-78
.el card list 298-300 (consumes cards from 189-233) 81+
Inline <script> 301-309 171-180
extract_metadata() 65-154 n/a
build_gallery() 172-311 n/a
main() 314-322 n/a

Reference spec: docs/superpowers/specs/2026-04-08-examples-page-overdrive-design.md. Re-read its §1–§6 before starting the matching task. The spec is the source of truth for visuals; this plan is the source of truth for sequence and code.

Landing page atom source (for copy-paste reference): - Tokens: landing/index.html:298-311 - .exec-dot / .exec-caret / .exec-scan: landing/index.html:2553-2616 - @keyframes exec-pulse / exec-blink / exec-scan-sweep / exec-stamp: landing/index.html:2568-2624

Security note about existing code. The generator's existing toggle() function uses a .innerHTML assignment to render syntax-highlighted source into a <pre> element. The highlighter function hl() escapes &, <, and > before any replacement. The SRC data source is the repository's own examples/*.py files read at build time — not user input. This pre-existing pattern is not modified by this plan; the only change to toggle() is an aria-expanded attribute sync (Step 6.3), which is a surgical single-line addition, not a rewrite of the rendering path.

Test loop convention for every task: 1. Edit the generator. 2. Regenerate: python scripts/build_examples_gallery.py > landing/examples/index.html 3. Open the regenerated file in a browser (locally: open landing/examples/index.html on macOS or use a static server). 4. Open the browser DevTools console — confirm zero errors. 5. Smoke-test the affected behavior (specified per task). 6. Commit.

Commit message convention (matches recent project history): - feat(examples): <what> for new features - style(examples): <what> for CSS-only changes - refactor(examples): <what> for restructuring with no visual change


Task 1: Duplicate execution-pointer atoms into the generator

Goal: Add the shared design tokens, .exec-dot, .exec-caret, .exec-scan, .sr-only, and the @keyframes from the landing page into the examples generator's inline <style> block. No visual change yet — this just makes the atoms available for later tasks. Verifies that adding ~80 lines of CSS to the generator does not break regeneration.

Files: - Modify: scripts/build_examples_gallery.py:247 (the long :root{{...}} line and the surrounding <style> block)

  • Step 1.1: Read the current :root line and the lines immediately following

Read scripts/build_examples_gallery.py:245-285. This is the inline <style> block. The :root CSS variables are on a single very long line. The atoms must be added directly after the existing :root{{...}} line, before the html{{...}} rule.

  • Step 1.2: Add execution-pointer tokens to :root

In scripts/build_examples_gallery.py, locate the long :root{{...}} line at line 247. It currently ends with ...opacity='0.018'/%3E%3C/svg%3E\")}}. Add the new tokens inside the :root braces, just before the closing }}. Note that this is a Python f-string so single braces become {{ and }}.

The new content to insert (verbatim, before the closing }} of :root):

--exec-color:#22d3ee;--exec-glow:rgba(34,211,238,0.55);--exec-glow-soft:rgba(34,211,238,0.18);--exec-pulse-dur:1.6s;--exec-step-dur:0.55s;--exec-ease-step:cubic-bezier(0.4,0,0.2,1);--exec-ease-soft:cubic-bezier(0.16,1,0.3,1);--exec-blink-dur:1.05s

After the edit, the line should still be one single line and still end with }}.

  • Step 1.3: Add execution-pointer atoms after the existing CSS rules

In scripts/build_examples_gallery.py, locate line 284 (the @media(max-width:640px){{...}} rule — the last CSS rule before the closing </style>). Insert these new lines AFTER line 284 and BEFORE line 285 (</style>).

The new lines (each one is one CSS rule on its own line — match the existing minified style):

.sr-only{{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}}
.exec-dot{{display:inline-block;width:8px;height:8px;border-radius:999px;background:var(--exec-color);box-shadow:0 0 0 0 var(--exec-glow);animation:exec-pulse var(--exec-pulse-dur) var(--exec-ease-soft) infinite;vertical-align:middle}}
.exec-dot--lg{{width:10px;height:10px}}
.exec-dot--sm{{width:6px;height:6px}}
@keyframes exec-pulse{{0%{{box-shadow:0 0 0 0 var(--exec-glow)}}60%{{box-shadow:0 0 0 8px rgba(34,211,238,0)}}100%{{box-shadow:0 0 0 0 rgba(34,211,238,0)}}}}
.exec-caret{{display:inline-block;width:0.55em;height:1.1em;vertical-align:text-bottom;background:var(--exec-color);box-shadow:0 0 6px var(--exec-glow);animation:exec-blink var(--exec-blink-dur) steps(2,jump-none) infinite;margin-left:2px}}
.exec-caret--thin{{width:2px;box-shadow:0 0 4px var(--exec-glow-soft)}}
@keyframes exec-blink{{0%,49%{{opacity:1}}50%,100%{{opacity:0}}}}
.exec-scan{{position:relative;overflow:hidden}}
.exec-scan.in-view::after{{content:"";position:absolute;top:0;left:-25%;width:25%;height:100%;background:linear-gradient(90deg,rgba(34,211,238,0) 0%,rgba(34,211,238,0.18) 40%,rgba(34,211,238,0.55) 50%,rgba(34,211,238,0.18) 60%,rgba(34,211,238,0) 100%);pointer-events:none;animation:exec-scan-sweep 1.4s var(--exec-ease-step) 0.2s 1 forwards}}
@keyframes exec-scan-sweep{{0%{{transform:translateX(0)}}100%{{transform:translateX(520%)}}}}
@keyframes exec-stamp{{0%{{transform:scale(0.92);box-shadow:0 0 0 0 var(--exec-glow)}}40%{{transform:scale(1.02);box-shadow:0 0 0 6px var(--exec-glow-soft)}}100%{{transform:scale(1);box-shadow:0 0 0 1px rgba(34,211,238,0.18)}}}}
@media(prefers-reduced-motion:reduce){{.exec-dot{{animation:none;box-shadow:0 0 6px var(--exec-glow)}}.exec-caret{{animation:none;opacity:1}}.exec-scan.in-view::after{{animation:none;display:none}}}}

These rules duplicate the landing page atoms verbatim, with single braces escaped as double braces for the Python f-string.

  • Step 1.4: Regenerate the HTML and verify it parses

Run:

python scripts/build_examples_gallery.py > landing/examples/index.html

Expected: command exits with code 0, no Python tracebacks. The output file is approximately 600KB.

  • Step 1.5: Verify in browser

Open landing/examples/index.html in a browser. Open DevTools console.

Expected: - Page renders identically to before (no visual change yet — the new classes are not used by any element). - DevTools console shows zero errors and zero warnings. - DevTools Elements panel: searching for --exec-color in the inline <style> finds the new token.

  • Step 1.6: Commit
git add scripts/build_examples_gallery.py landing/examples/index.html
git commit -m "$(cat <<'EOF'
refactor(examples): import execution-pointer atoms from landing

Duplicate the landing page's design tokens, .exec-dot, .exec-caret,
.exec-scan, .sr-only, @keyframes exec-pulse/exec-blink/exec-scan-sweep/
exec-stamp, and prefers-reduced-motion fallbacks into the examples page
generator's inline <style>. No visual change yet — these atoms become
the foundation for the §1–§6 redesign in subsequent commits.

Spec: docs/superpowers/specs/2026-04-08-examples-page-overdrive-design.md
EOF
)"

Task 2: Add .exec-dot to the nav brand (§6)

Goal: Add a permanent pulsing cyan dot to the left of the selectools wordmark in the nav. This is the smallest visible diff and is the single most important cross-page coherence signal.

Files: - Modify: scripts/build_examples_gallery.py:289 (the <a class="nl"> element)

  • Step 2.1: Locate the nav brand line in the generator

Read scripts/build_examples_gallery.py:288-291. The relevant line is:

  <a href="../" class="nl">selectools <span>examples</span></a>
  • Step 2.2: Insert the .exec-dot span before the wordmark

Edit line 289 to:

  <a href="../" class="nl"><span class="exec-dot"></span>&nbsp;selectools <span>examples</span></a>

The &nbsp; provides spacing between the dot and the wordmark. The dot will pulse continuously via the @keyframes exec-pulse from Task 1.

  • Step 2.3: Regenerate the HTML

Run:

python scripts/build_examples_gallery.py > landing/examples/index.html

Expected: exits 0, file ~600KB.

  • Step 2.4: Verify in browser

Open landing/examples/index.html in a browser at desktop width.

Expected: - A small cyan dot appears immediately to the left of "selectools" in the top-left nav. - The dot visibly pulses on a ~1.6s loop with a glow effect. - The wordmark text is unchanged. - DevTools console: zero errors.

In a second browser window or tab, open landing/index.html (the main landing page). Confirm the same dot appears in the same position. The two pages should now feel like the same site at first glance.

  • Step 2.5: Verify reduced-motion fallback

In DevTools, open Rendering tab → Emulate CSS media feature prefers-reduced-motion: reduce. Reload.

Expected: the dot is still visible and glowing but does NOT pulse (no animation).

  • Step 2.6: Commit
git add scripts/build_examples_gallery.py landing/examples/index.html
git commit -m "$(cat <<'EOF'
feat(examples): add pulsing exec-dot to nav brand (§6)

Adds a permanent cyan execution-pointer dot to the left of the
selectools wordmark in the examples page nav. Matches the landing
page's wordmark variant 1 — a user clicking between / and /examples/
now sees the same pulse in the same place.

Respects prefers-reduced-motion (becomes a static glow).

Spec §6: docs/superpowers/specs/2026-04-08-examples-page-overdrive-design.md
EOF
)"

Task 3: Replace .ph with the terminal-session header (§1)

Goal: Replace the current <h1>88 Example Scripts</h1> block with a terminal-window panel that types ls examples/ on page load and live-mirrors the search and category state into the prompt suffix.

Files: - Modify: scripts/build_examples_gallery.py:254-255 (the .ph CSS rules) - Modify: scripts/build_examples_gallery.py:292 (the .ph markup template) - Modify: scripts/build_examples_gallery.py:301-309 (the inline <script> — add typeLine() and syncPrompt()) - Modify: scripts/build_examples_gallery.py:304 (the flt() body — update counter format)

  • Step 3.1: Replace the .ph CSS with .ex-term CSS

In scripts/build_examples_gallery.py, locate lines 254-255:

.ph{{max-width:960px;margin:0 auto;padding:48px 20px 24px}}
.ph h1{{font-size:28px;letter-spacing:-0.03em;margin-bottom:8px;font-weight:800}}.ph p{{color:var(--dm);font-size:15px;max-width:600px;line-height:1.6}}

Replace those two lines with:

.ex-term{{max-width:960px;margin:32px auto 24px;background:#0b1220;border:1px solid var(--bd);border-radius:14px;box-shadow:0 20px 60px -28px rgba(0,0,0,0.55),0 0 0 1px rgba(34,211,238,0.05);overflow:hidden}}
.ex-term__bar{{display:flex;align-items:center;gap:8px;padding:12px 16px;border-bottom:1px solid var(--bd);background:rgba(15,23,42,0.7)}}
.ex-term__dot{{width:11px;height:11px;border-radius:999px}}
.ex-term__dot--r{{background:rgba(239,68,68,0.85)}}
.ex-term__dot--y{{background:rgba(250,204,21,0.85)}}
.ex-term__dot--g{{background:rgba(34,197,94,0.85)}}
.ex-term__name{{margin-left:8px;font-family:var(--mono);font-size:12px;color:var(--ft)}}
.ex-term__shell{{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--ft);letter-spacing:0.08em}}
.ex-term__body{{padding:22px 22px 24px;font-family:var(--mono)}}
.ex-prompt{{font-family:var(--mono);font-size:13px;line-height:1.75;white-space:pre;overflow-x:auto}}
.ex-prompt__user{{color:var(--cy)}}
.ex-prompt__at{{color:var(--ft)}}
.ex-prompt__host{{color:var(--cy)}}
.ex-prompt__colon{{color:var(--ft)}}
.ex-prompt__path{{color:#fbbf24}}
.ex-prompt__glyph{{color:var(--gn);margin:0 6px}}
.ex-prompt__cmd{{color:var(--tx)}}
.ex-prompt__flags{{color:#fbbf24}}
.ex-prompt__grep{{color:var(--ft)}}
.ex-subtitle{{margin-top:14px;font-family:var(--font);font-size:14px;color:var(--dm);max-width:600px;line-height:1.6}}
@media(max-width:640px){{.ex-prompt__user,.ex-prompt__at,.ex-prompt__host,.ex-prompt__colon,.ex-prompt__path{{display:none}}.ex-prompt__glyph{{margin-left:0}}}}
@media(prefers-reduced-motion:reduce){{.ex-prompt .exec-caret{{animation:none;opacity:1}}}}
  • Step 3.2: Replace the .ph markup template

In scripts/build_examples_gallery.py, locate line 292:

<div class="ph"><h1>{total} Example Scripts</h1><p>Runnable Python examples covering agents, RAG, multi-agent graphs, evals, streaming, guardrails, and more. {no_key} run without an API key.</p></div>

Replace it with this multi-line template (mind the f-string braces):

<header class="ex-term">
  <div class="ex-term__bar">
    <span class="ex-term__dot ex-term__dot--r"></span>
    <span class="ex-term__dot ex-term__dot--y"></span>
    <span class="ex-term__dot ex-term__dot--g"></span>
    <span class="ex-term__name">~/selectools/examples</span>
    <span class="ex-term__shell">zsh</span>
  </div>
  <div class="ex-term__body">
    <div class="ex-prompt" aria-hidden="true"><span class="ex-prompt__user">selectools</span><span class="ex-prompt__at">@</span><span class="ex-prompt__host">examples.dev</span><span class="ex-prompt__colon">:</span><span class="ex-prompt__path">~/selectools/examples</span><span class="ex-prompt__glyph">$</span><span class="ex-prompt__cmd" id="ex-cmd"></span><span class="ex-prompt__flags" id="ex-flags"></span><span class="ex-prompt__grep" id="ex-grep"></span><span class="exec-caret"></span></div>
    <h1 class="sr-only">Selectools examples  {total} runnable Python scripts</h1>
    <p class="ex-subtitle">{total} runnable scripts covering agents, RAG, multi-agent graphs, evals, streaming, and guardrails. {no_key} run without an API key.</p>
  </div>
</header>

Notice: the ex-prompt__cmd span is now empty — id="ex-cmd" will be filled by the type-on routine on page load. The flags and grep spans are also empty and will be filled by syncPrompt().

  • Step 3.3: Update the flt() body to the new counter format

In scripts/build_examples_gallery.py, locate the flt() function inside the inline <script> at line 304. Its final statement currently ends with:

document.getElementById('rc').textContent=c+' example'+(c!==1?'s':'')

Replace that single statement with:

document.getElementById('rc').textContent='# '+c+' files match'

Do not modify any other part of flt(). Only the counter text format changes.

  • Step 3.4: Add typeLine(), syncPrompt(), and bootPrompt() to the inline <script>

In scripts/build_examples_gallery.py, locate the script block around lines 301-309. After the existing function cpSrc(...) line (around line 308) and BEFORE the closing </script> tag, add this block on three new lines:

function syncPrompt(){{const q=document.getElementById('si').value;document.getElementById('ex-grep').textContent=q?' | grep -i '+q:'';document.getElementById('ex-flags').textContent=ac==='all'?'':' --tags '+ac}}
function typeLine(target,text,perChar,done){{let i=0;const tick=()=>{{if(i<=text.length){{target.textContent=text.slice(0,i);i++;setTimeout(tick,perChar)}}else if(done){{done()}}}};tick()}}
(function bootPrompt(){{const cmd=document.getElementById('ex-cmd');if(!cmd)return;const reduced=window.matchMedia('(prefers-reduced-motion: reduce)').matches;if(reduced){{cmd.textContent='ls examples/';syncPrompt();return}}typeLine(cmd,'ls examples/',35,syncPrompt)}})();

Note that syncPrompt() writes to .textContent only — it does not touch any HTML rendering path. The typeLine() helper also writes only to .textContent. Both are safe text-only DOM updates.

  • Step 3.5: Wire syncPrompt() to the search input's oninput

In scripts/build_examples_gallery.py, locate line 294:

  <input class="si" type="text" placeholder="Search examples\u2026" oninput="flt()" id="si" />

Replace oninput="flt()" with oninput="flt();syncPrompt()". The full line becomes:

  <input class="si" type="text" placeholder="Search examples\u2026" oninput="flt();syncPrompt()" id="si" />
  • Step 3.6: Regenerate the HTML

Run:

python scripts/build_examples_gallery.py > landing/examples/index.html

Expected: exits 0, file ~600KB.

  • Step 3.7: Verify in browser — type-on, live-mirror, mobile, reduced-motion

Open landing/examples/index.html in a browser at desktop width.

Expected on page load: - A terminal panel appears below the nav. It has a colored window-control bar (red/yellow/green dots) with ~/selectools/examples and zsh labels. - Inside the terminal body, the prompt selectools@examples.dev:~/selectools/examples $ appears immediately, fully styled. - After ~10ms, the cursor types ls examples/ character-by-character at ~35ms per char (~420ms total). - After typing finishes, a blinking cyan caret remains at the end of the line. - Below the prompt, a paragraph reads "88 runnable scripts covering agents, RAG, multi-agent graphs, evals, streaming, and guardrails. 34 run without an API key."

Expected on interaction: - Type rag into the search input. The terminal prompt updates live to show | grep -i rag appended after ls examples/. - Clear the search. The grep suffix disappears (no dangling pipe). - The counter below the rail updates to # 12 files match while filtered, # 88 files match when cleared.

Mobile fallback test: - In DevTools, switch to a 375×812 viewport (iPhone). Reload. - Expected: the selectools@examples.dev:~/selectools/examples prefix is hidden. Only $ ls examples/ and the caret are visible. Typing in search still updates the grep suffix.

Reduced-motion test: - DevTools → Rendering → Emulate prefers-reduced-motion: reduce. Reload. - Expected: the ls examples/ text appears fully typed instantly (no character-by-character animation). The caret is visible but does not blink.

  • Step 3.8: Commit
git add scripts/build_examples_gallery.py landing/examples/index.html
git commit -m "$(cat <<'EOF'
feat(examples): replace page header with terminal-session panel (§1)

Replaces the bare <h1> + paragraph with a full terminal-window panel
that types out 'ls examples/' on page load and live-mirrors the search
state into the prompt suffix as ' | grep -i <query>'.

Counter format changes from 'N examples' to '# N files match' to
match the monospace comment aesthetic.

The category --tags suffix wiring lands in Task 4 once the rail exists.

Adds typeLine() and syncPrompt() helpers and a bootPrompt() IIFE that
respects prefers-reduced-motion. Mobile collapses to '$ ls examples/'.
Both helpers write only to .textContent — no HTML rendering paths.

Spec §1: docs/superpowers/specs/2026-04-08-examples-page-overdrive-design.md
EOF
)"

Task 4: Replace the chip row with the proportional-width category rail (§2)

Goal: Remove the 18 pill-shaped category buttons (.cb) and replace them with a single horizontal bar of segments whose widths are proportional to category counts. On viewport entry, the rail "stamps" each segment left-to-right in sequence.

Files: - Modify: scripts/build_examples_gallery.py:259-261 (the .cr and .cb CSS rules) - Modify: scripts/build_examples_gallery.py:178-183 (the cat_btns Python loop that builds the chip markup) - Modify: scripts/build_examples_gallery.py:295 (the f-string slot that emits cat_btns) - Modify: scripts/build_examples_gallery.py:305 (the inline JS chip click handler)

  • Step 4.1: Replace .cr and .cb CSS with .ex-rail CSS

In scripts/build_examples_gallery.py, locate lines 259-261:

.cr{{display:flex;flex-wrap:wrap;gap:6px}}
.cb{{font-family:var(--font);font-size:12px;font-weight:500;padding:6px 14px;border-radius:100px;border:1px solid rgba(51,65,85,0.6);background:rgba(30,41,59,0.7);color:var(--dm);cursor:pointer;transition:all .15s;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}}
.cb:hover{{background:rgba(51,65,85,0.5);border-color:var(--dm);color:var(--tx)}}.cb.on{{background:rgba(34,211,238,0.12);border-color:rgba(34,211,238,0.35);color:var(--cy);box-shadow:0 0 12px rgba(34,211,238,0.08)}}

Replace those three lines with:

.ex-rail{{display:flex;gap:2px;height:40px;border-radius:8px;overflow:hidden;border:1px solid var(--bd);background:rgba(30,41,59,0.4)}}
.ex-rail__seg{{flex:var(--seg-weight,1) 1 0;min-width:56px;height:100%;display:flex;align-items:center;justify-content:center;gap:6px;font-family:var(--mono);font-size:12px;color:var(--dm);background:transparent;border:none;cursor:pointer;transition:background .15s,color .15s;position:relative;padding:0 8px;white-space:nowrap}}
.ex-rail__seg--all{{flex:0 0 72px}}
.ex-rail__seg:hover{{background:rgba(34,211,238,0.08);color:var(--tx)}}
.ex-rail__seg.on{{background:rgba(34,211,238,0.12);color:var(--cy);box-shadow:inset 0 -2px 0 var(--exec-color)}}
.ex-rail__name{{font-size:12px}}
.ex-rail__count{{font-size:11px;color:var(--cy);opacity:0.75}}
.ex-rail.in-view .ex-rail__seg{{animation:exec-stamp 0.6s var(--exec-ease-soft) both;animation-delay:calc(var(--seg-index,0) * 80ms)}}
@media(max-width:640px){{.ex-rail{{overflow-x:auto;-webkit-overflow-scrolling:touch;scroll-snap-type:x mandatory;height:44px}}.ex-rail__seg{{flex:0 0 auto;min-width:80px;scroll-snap-align:start}}}}
@media(prefers-reduced-motion:reduce){{.ex-rail.in-view .ex-rail__seg{{animation:none}}}}
  • Step 4.2: Rewrite the cat_btns builder loop in Python

In scripts/build_examples_gallery.py, locate the existing cat_btns builder at lines 178-183:

    cat_btns = [f'<button class="cb on" data-cat="all">All ({total})</button>']
    for c in all_cats:
        n = sum(1 for e in examples if c in e["categories"])
        icon = CAT_ICONS.get(c, "")
        label = c.replace("-", " ").title()
        cat_btns.append(f'<button class="cb" data-cat="{c}">{icon} {label} ({n})</button>')

Replace those six lines with:

    rail_segs = [
        f'<button class="ex-rail__seg ex-rail__seg--all on" data-cat="all" '
        f'role="tab" aria-selected="true" style="--seg-index:0">'
        f'<span class="ex-rail__name">all</span>'
        f'<span class="ex-rail__count">{total}</span>'
        f'</button>'
    ]
    for idx, c in enumerate(all_cats, start=1):
        n = sum(1 for e in examples if c in e["categories"])
        rail_segs.append(
            f'<button class="ex-rail__seg" data-cat="{c}" role="tab" '
            f'aria-selected="false" style="--seg-weight:{n};--seg-index:{idx}">'
            f'<span class="ex-rail__name">{c}</span>'
            f'<span class="ex-rail__count">{n}</span>'
            f'</button>'
        )

Note: the variable is renamed from cat_btns to rail_segs to reflect what it now produces. The CAT_ICONS dictionary is no longer referenced — leave the dictionary in place (other code may still use it; do not delete it in this task).

  • Step 4.3: Update the f-string slot in the markup template

In scripts/build_examples_gallery.py, locate line 295:

  <div class="cr">{chr(10).join(cat_btns)}</div>

Replace it with:

  <div class="ex-rail" id="ex-rail" role="tablist" aria-label="Filter examples by category">{chr(10).join(rail_segs)}</div>
  • Step 4.4: Replace the chip click handler in the inline <script>

In scripts/build_examples_gallery.py, locate line 305:

document.querySelectorAll('.cb').forEach(b=>{{b.addEventListener('click',()=>{{document.querySelectorAll('.cb').forEach(x=>x.classList.remove('on'));b.classList.add('on');ac=b.dataset.cat;flt()}});}});

Replace it with:

document.querySelectorAll('.ex-rail__seg').forEach(b=>{{b.addEventListener('click',()=>{{document.querySelectorAll('.ex-rail__seg').forEach(x=>{{x.classList.remove('on');x.setAttribute('aria-selected','false')}});b.classList.add('on');b.setAttribute('aria-selected','true');ac=b.dataset.cat;b.style.animation='none';requestAnimationFrame(()=>{{b.style.animation='exec-stamp 0.6s var(--exec-ease-soft)'}});flt();syncPrompt()}});}});
(function(){{const r=document.getElementById('ex-rail');if(!r)return;const io=new IntersectionObserver((ents)=>{{ents.forEach(e=>{{if(e.isIntersecting){{r.classList.add('in-view');io.disconnect()}}}})}},{{rootMargin:'0px 0px -20% 0px'}});io.observe(r)}})();

The replacement does five things: 1. Selects .ex-rail__seg instead of .cb. 2. Maintains aria-selected correctly on click. 3. Re-runs exec-stamp on the clicked segment via animation reset trick (set animation to none, then on next frame set it back to exec-stamp 0.6s var(--exec-ease-soft)). 4. Calls syncPrompt() so the prompt's --tags flag updates. 5. Adds an IntersectionObserver (one-shot, disconnects after first trigger) that adds .in-view to the rail when it scrolls into view, triggering the staggered stamp sweep.

  • Step 4.5: Regenerate the HTML

Run:

python scripts/build_examples_gallery.py > landing/examples/index.html

Expected: exits 0, file ~600KB.

  • Step 4.6: Verify in browser — sweep, click, prompt sync, mobile

Open landing/examples/index.html in a browser at desktop width.

Expected on first view: - The category rail appears below the search input as a single horizontal bar. - The leftmost segment is a fixed-width "all 88" segment (active, cyan-tinted). - 17 more segments to its right, sized proportionally to their category counts. agent (21) is the widest non-"all" segment; audit (1) is the narrowest (but still at least 56px wide). - Within ~1.5s of the rail becoming visible, each segment "stamps" left-to-right with a brief scale + cyan glow (the exec-stamp keyframe), staggered by 80ms.

Expected on interaction: - Click the rag segment. It becomes active (cyan tint + cyan inset bottom border), the previously-active segment loses its on state, and the segment briefly stamps again. - The card list filters to RAG examples only. - The terminal prompt above (from Task 3) now shows $ ls examples/ --tags rag.

Click all. The --tags suffix disappears from the prompt. The full list returns.

Type rag into the search box. The prompt shows $ ls examples/ --tags rag | grep -i rag. Clear the search; the grep suffix goes away but the --tags rag stays until you click all.

Mobile fallback: - Switch to a 375×812 viewport. Reload. - Expected: the rail is now horizontally scrollable. Each segment is at least 80px wide. Scroll-snapping engages on swipe.

Reduced-motion: - Enable prefers-reduced-motion. Reload. - Expected: the rail renders in its rest state immediately, no stamp sweep. Click handlers still work; clicked segments do NOT stamp on click.

  • Step 4.7: Commit
git add scripts/build_examples_gallery.py landing/examples/index.html
git commit -m "$(cat <<'EOF'
feat(examples): replace chip row with proportional category rail (§2)

Removes the 18-pill .cb chip row and replaces it with a single bar
of .ex-rail__seg segments sized proportionally to each category's
count. Visually shows the shape of the catalog at a glance.

On viewport entry an IntersectionObserver triggers a left-to-right
stamp sweep (80ms stagger). Clicking a segment filters the list,
re-stamps the segment, and rewrites the terminal prompt's --tags suffix.

Mobile becomes a horizontal scroll-snap strip. Respects
prefers-reduced-motion (no sweep, no on-click stamp).

Spec §2: docs/superpowers/specs/2026-04-08-examples-page-overdrive-design.md
EOF
)"

Task 5: Search input glyph, kbd hint, and counter format (§3)

Goal: Wrap the search input with a leading glyph and a trailing kbd shortcut hint (/), wire the / key to focus the search, and confirm the result counter renders as # N files match (the flt() body was already updated in Task 3).

Files: - Modify: scripts/build_examples_gallery.py:256-258 (the .ct and .si CSS) - Modify: scripts/build_examples_gallery.py:262 (the .rc CSS) - Modify: scripts/build_examples_gallery.py:293-297 (the .ct markup template) - Modify: scripts/build_examples_gallery.py:301-309 (add / keydown handler to the <script>)

  • Step 5.1: Add .ex-search CSS rules

In scripts/build_examples_gallery.py, locate lines 256-258:

.ct{{max-width:960px;margin:0 auto;padding:0 20px 16px;display:flex;flex-direction:column;gap:10px;position:sticky;top:52px;z-index:40;background:var(--bg);padding-top:10px}}
.si{{flex:1;background:var(--sf);border:1px solid var(--bd);border-radius:8px;padding:10px 14px;color:var(--tx);font-family:var(--font);font-size:14px;outline:none}}
.si:focus{{border-color:var(--cy);box-shadow:0 0 0 2px rgba(34,211,238,0.12)}}.si::placeholder{{color:var(--ft)}}

Add these new lines AFTER line 258 (do not delete the existing three lines — .si and .ct are still used):

.ex-search{{position:relative;display:flex;align-items:center}}
.ex-search__glyph{{position:absolute;left:14px;color:var(--ft);font-size:14px;pointer-events:none}}
.ex-search__kbd{{position:absolute;right:12px;font-family:var(--mono);font-size:11px;color:var(--ft);background:rgba(100,116,139,0.15);padding:2px 6px;border-radius:4px;pointer-events:none}}
.ex-search .si{{padding-left:36px;padding-right:36px}}
  • Step 5.2: Update the .rc counter style to use the mono comment color

The existing .rc rule at line 262 already uses font-family: var(--mono); font-size: 11px; color: var(--ft); — no change needed. The counter text was already updated by the flt() body change in Task 3.

  • Step 5.3: Update the .ct markup template

In scripts/build_examples_gallery.py, locate lines 293-297:

<div class="ct">
  <input class="si" type="text" placeholder="Search examples\u2026" oninput="flt();syncPrompt()" id="si" />
  <div class="ex-rail" id="ex-rail" role="tablist" aria-label="Filter examples by category">{chr(10).join(rail_segs)}</div>
  <div class="rc" id="rc">{total} examples</div>
</div>

(Note: line 295 was already updated by Task 4 to emit the .ex-rail. Line 296 still emits the old text format {total} examples.)

Replace those five lines with:

<div class="ct">
  <div class="ex-search">
    <span class="ex-search__glyph"></span>
    <input class="si" type="text" placeholder="search by name or keyword\u2026" oninput="flt();syncPrompt()" id="si" autocomplete="off" />
    <kbd class="ex-search__kbd">/</kbd>
  </div>
  <div class="ex-rail" id="ex-rail" role="tablist" aria-label="Filter examples by category">{chr(10).join(rail_segs)}</div>
  <div class="rc" id="rc"># {total} files match</div>
</div>

Three changes: search input wrapped in .ex-search, placeholder text changed to lowercase search by name or keyword…, counter initial text format changed to # {total} files match.

  • Step 5.4: Add the / keydown handler to the inline <script>

In scripts/build_examples_gallery.py, locate the script block. Just before the closing </script> tag (after the bootPrompt IIFE you added in Task 3), add:

document.addEventListener('keydown',(e)=>{{if(e.key!=='/')return;const t=e.target;if(t&&(t.tagName==='INPUT'||t.tagName==='TEXTAREA'||t.isContentEditable))return;e.preventDefault();const si=document.getElementById('si');if(si)si.focus()}});

The handler ignores / when typed inside any input/textarea/contenteditable element so it doesn't break normal typing.

  • Step 5.5: Regenerate the HTML

Run:

python scripts/build_examples_gallery.py > landing/examples/index.html

Expected: exits 0, file ~600KB.

  • Step 5.6: Verify in browser — glyph, kbd, shortcut, counter format

Open landing/examples/index.html in a browser at desktop width.

Expected: - The search input now has a glyph at the left and a small / keyboard hint pill at the right. - The placeholder text is search by name or keyword…. - Below the rail, the counter reads # 88 files match. - Press / while focused outside any input. The search box gains focus immediately. - Press / while typing inside the search box itself. The character / appears in the input (the shortcut does NOT fire). - Type rag. The counter updates to # 12 files match (or however many RAG examples exist). - Clear the search. Counter returns to # 88 files match.

Reduced-motion: no animations in this section, nothing extra to verify.

  • Step 5.7: Commit
git add scripts/build_examples_gallery.py landing/examples/index.html
git commit -m "$(cat <<'EOF'
feat(examples): search glyph, kbd hint, and # files match counter (§3)

Wraps the search input with a leading ⌕ glyph and a trailing /
keyboard shortcut hint. Adds a global keydown listener that focuses
the search when / is pressed outside any input.

Counter format finalized as '# N files match' to match the monospace
comment aesthetic of the terminal header.

Spec §3: docs/superpowers/specs/2026-04-08-examples-page-overdrive-design.md
EOF
)"

Task 6: Card rows as ls -la columns (§4)

Goal: Replace each card's flex-row header (.eh) with a 7-column CSS Grid that mimics ls -la output. Add a subtle 14ms-staggered enter animation for the first 30 rows. Add a mobile media query that collapses the grid to 2 visual lines.

Files: - Modify: scripts/build_examples_gallery.py:264-274 (the .ec and .eh CSS rules) - Modify: scripts/build_examples_gallery.py:189-233 (the Python loop that builds each card) - Modify: the toggle() function inside the inline <script> (single-line addition for aria-expanded sync) - Modify: the inline <script> (append a keydown handler for .ex-row elements)

  • Step 6.1: Replace .eh and related CSS with .ex-row grid CSS

In scripts/build_examples_gallery.py, locate lines 264-274. Currently:

.ec{{border:1px solid var(--bd);border-radius:8px;overflow:hidden;background:var(--sf);background-image:var(--gr);transition:border-color .15s}}
.ec:hover{{border-color:rgba(34,211,238,0.2)}}.ec.op{{border-color:rgba(34,211,238,0.3)}}
.eh{{display:flex;align-items:center;gap:14px;padding:14px 18px;cursor:pointer;user-select:none}}
.en{{font-family:var(--mono);font-size:12px;font-weight:500;color:var(--cy);min-width:24px;flex-shrink:0}}
.ei{{flex:1;min-width:0}}.et{{font-weight:600;font-size:13px;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}
.ed{{font-size:12px;color:var(--dm);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}
.em{{display:flex;gap:8px;align-items:center;flex-shrink:0}}
.ek{{font-family:var(--mono);font-size:10px;color:var(--ft);background:rgba(100,116,139,0.15);padding:2px 8px;border-radius:100px}}
.enk{{font-family:var(--mono);font-size:10px;color:var(--gn);background:rgba(34,197,94,0.1);padding:2px 8px;border-radius:100px}}
.eln{{font-family:var(--mono);font-size:10px;color:var(--ft)}}
.ev{{font-size:10px;color:var(--ft);transition:transform .2s}}.ec.op .ev{{transform:rotate(180deg)}}

Replace lines 266-274 (everything from .eh{{...}} through .ev{{...}}.ec.op .ev{{...}}) with:

.ex-row{{display:grid;grid-template-columns:32px 112px 54px 72px minmax(180px,1.5fr) minmax(0,3fr) 20px;align-items:center;gap:16px;padding:12px 18px;font-family:var(--mono);font-size:12px;cursor:pointer;user-select:none;transition:background-color .15s,border-left-color .15s;border-left:2px solid transparent}}
.ex-row:hover{{background:rgba(34,211,238,0.04);border-left-color:var(--cy)}}
.ec.op .ex-row{{background:rgba(34,211,238,0.06)}}
.ex-row__num{{color:var(--cy);font-weight:500}}
.ex-row__perm{{color:var(--ft)}}
.ex-row__size{{color:var(--ft);text-align:right}}
.ex-row__key{{font-size:10px;padding:2px 8px;border-radius:100px;text-align:center}}
.ex-row__key--free{{color:var(--gn);background:rgba(34,197,94,0.1)}}
.ex-row__key--paid{{color:var(--ft);background:rgba(100,116,139,0.15)}}
.ex-row__file{{color:var(--cy);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}
.ex-row__desc{{color:var(--tx);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}
.ex-row__chev{{font-size:10px;color:var(--ft);transition:transform 0.22s var(--exec-ease-soft);text-align:center}}
.ec.op .ex-row__chev{{transform:rotate(180deg)}}
.ex-row--enter{{animation:ex-row-in 0.35s var(--exec-ease-soft) both;animation-delay:calc(var(--row-index,0) * 14ms)}}
@keyframes ex-row-in{{from{{opacity:0;transform:translateY(4px)}}to{{opacity:1;transform:none}}}}
@media(max-width:640px){{.ex-row{{grid-template-columns:32px 1fr 20px;gap:8px 12px}}.ex-row__num{{grid-column:1;grid-row:1 / 3;align-self:start}}.ex-row__perm{{display:none}}.ex-row__file{{grid-column:2;grid-row:1}}.ex-row__chev{{grid-column:3;grid-row:1 / 3;align-self:start}}.ex-row__size{{grid-column:2;grid-row:2;display:inline;margin-right:8px;color:var(--ft)}}.ex-row__key{{grid-column:2;grid-row:2;display:inline;margin-right:8px}}.ex-row__desc{{grid-column:2;grid-row:2;display:inline;color:var(--dm)}}}}
@media(prefers-reduced-motion:reduce){{.ex-row--enter{{animation:none}}.ex-row__chev{{transition-duration:0.01s}}}}

The legacy .eh, .en, .ei, .et, .ed, .em, .ek, .enk, .eln, .ev rules can be deleted since their elements are no longer rendered (the new card markup uses .ex-row__* exclusively). However, the old .ec and .ec:hover/.ec.op rules at line 264-265 must be kept — they style the card container.

  • Step 6.2: Rewrite the per-card markup builder

In scripts/build_examples_gallery.py, locate the cards builder loop at lines 189-233. The current loop produces a <div class="ec"> with <div class="eh"> containing the old flex-row header. Replace lines 189-233 (the entire loop) with this new version:

    cards = []
    for ex in examples:
        cats_str = " ".join(ex["categories"])
        cat_parts = []
        for c in ex["categories"]:
            label = c.replace("-", " ").title()
            if c in CAT_DOCS:
                cat_parts.append(f'<a href="../{CAT_DOCS[c]}" class="ec1">{label}</a>')
            else:
                cat_parts.append(f'<span class="ec1">{label}</span>')
        cats_html = "".join(cat_parts)
        key_class = "ex-row__key--paid" if ex["needs_key"] else "ex-row__key--free"
        key_label = "api-key" if ex["needs_key"] else "no-key"
        graph_btn = ""
        if ex["has_graph"]:
            graph_btn = f'<a href="../{BUILDER_URL}" class="eab ebu">Open in Builder</a>'
        doc_btn = ""
        for c in ex["categories"]:
            if c in CAT_DOCS:
                doc_btn = f'<a href="../{CAT_DOCS[c]}" class="eab">Docs</a>'
                break

        # Strip the leading "NN_" from the displayed filename (column 1 already shows the number)
        display_file = re.sub(r"^\d+_", "", ex["file"])

        # Per-row enter animation: only the first 30 rows get the .ex-row--enter class
        row_index = ex["num"] - 1
        enter_class = " ex-row--enter" if row_index < 30 else ""
        enter_style = f' style="--row-index:{row_index}"' if row_index < 30 else ""

        cards.append(
            f'<div class="ec" data-cats="{cats_str}" '
            f'data-title="{html.escape(ex["title"].lower())}" data-file="{ex["file"]}">'
            f'<div class="ex-row eh{enter_class}" onclick="toggle(this)" '
            f'role="button" tabindex="0" aria-expanded="false"{enter_style}>'
            f'<span class="ex-row__num">{ex["num"]:02d}</span>'
            f'<span class="ex-row__perm">-rw-r--r--</span>'
            f'<span class="ex-row__size">{ex["lines"]}L</span>'
            f'<span class="ex-row__key {key_class}">{key_label}</span>'
            f'<span class="ex-row__file">{html.escape(display_file)}</span>'
            f'<span class="ex-row__desc">{html.escape(ex["desc"] or ex["title"])}</span>'
            f'<span class="ex-row__chev ev">▾</span>'
            f'</div>'
            f'<div class="eb" style="display:none">'
            f'<div class="eg">{cats_html}</div>'
            f'<div class="ea">'
            f'<button class="eab" onclick="cpSrc(this)">Copy</button>'
            f'<a href="{REPO_URL}/blob/main/examples/{ex["file"]}" class="eab" '
            f'target="_blank">GitHub</a>{doc_btn}{graph_btn}</div>'
            f'<pre class="ep"></pre>'
            f"</div></div>"
        )

Notes on the changes: - The outer .ec div is unchanged. - The inner header div now has class="ex-row eh ex-row--enter" (or just .ex-row eh for rows past index 29). The eh class is preserved so the existing toggle() handler still works via closest('.ec'). Rows past index 29 omit the --enter class entirely. - Each row is role="button" tabindex="0" aria-expanded="false" for keyboard accessibility (Step 6.3 wires the keydown handler). - Filename column strips the NN_ prefix via re.sub(r"^\d+_", "", ex["file"]). The full filename is still used in the GitHub link inside .eb. - The chevron is (down-pointing small triangle) and gets the existing .ev class so the existing .ec.op .ev rotation rule still applies — but the new .ex-row__chev rule overrides the rotation with var(--exec-ease-soft) easing. - Description fallback: ex["desc"] or ex["title"] ensures empty descriptions don't render an empty cell.

  • Step 6.3: Surgical single-line addition to toggle() for aria-expanded sync

In scripts/build_examples_gallery.py, locate the toggle() function inside the inline <script> around line 307. Its current body includes these statements (in order):

  1. const c=h.closest('.ec'),b=c.querySelector('.eb'),p=c.querySelector('.ep');
  2. c.classList.toggle('op');
  3. const open=c.classList.contains('op');
  4. b.style.display=open?'':'none';
  5. ...source loading logic which this plan does not touch...

Immediately after statement 4 (b.style.display=open?'':'none';) and before the source loading statement, insert this single new statement:

h.setAttribute('aria-expanded',open?'true':'false');

Do not modify any other statement inside toggle(). The source loading path stays untouched.

  • Step 6.4: Add a keydown handler for .ex-row elements

In scripts/build_examples_gallery.py, locate the script block. Just before the closing </script> tag (after the / keydown handler from Task 5), add this keydown listener:

document.querySelectorAll('.ex-row').forEach(r=>{{r.addEventListener('keydown',(e)=>{{if(e.key==='Enter'||e.key===' '){{e.preventDefault();toggle(r)}}}})}});

This makes every row keyboard-activatable. The existing toggle() function (now updated in Step 6.3 to sync aria-expanded) handles the rest.

  • Step 6.5: Regenerate the HTML

Run:

python scripts/build_examples_gallery.py > landing/examples/index.html

Expected: exits 0, file ~600KB.

  • Step 6.6: Verify in browser — desktop columns, mobile collapse, enter animation, keyboard

Open landing/examples/index.html in a browser at desktop width (1440×900).

Expected first paint: - Cards render as monospace rows in ls -la style. Each row reads (left to right): two-digit number (cyan), -rw-r--r-- (dim), line count 46L (dim, right-aligned), key badge no-key (green) or api-key (dim), filename without the NN_ prefix (cyan), description (bright), chevron (dim). - The first ~30 rows visibly fade-up in a 14ms-staggered cascade on first paint. The full cascade completes in about 450ms. - Rows past index 29 render in their final state immediately (no animation). - Hovering a row tints its background and grows a 2px cyan border on the left.

Click row 01: - The row expands. The chevron rotates 180° smoothly (with the new ease curve). - The .eb body becomes visible with the existing tag links + Copy/GitHub/Docs buttons + source <pre>. - The row's aria-expanded attribute becomes "true" (inspect in DevTools).

Tab into the first row, then press Enter: - The row expands. Press Enter again to collapse. - aria-expanded toggles between "true" and "false" on each activation.

Mobile fallback: - Switch to a 375×812 viewport. Reload. - Expected: each card collapses to 2 visual lines. Line 1 has the number on the left, the filename, and the chevron on the right. Line 2 has the line count, key badge, and description (truncated). The -rw-r--r-- permissions column is hidden on mobile.

Reduced-motion: - Enable prefers-reduced-motion. Reload. - Expected: rows appear immediately without the cascade animation. Chevron rotation is instant (0.01s). Card expansion still works.

Smoke test all 88 cards filter correctly: - Type rag in search. Only RAG cards remain visible. Counter says # 12 files match. - Click the agent segment in the rail. Only Agent cards visible. - Click all. All 88 visible. Clear search.

  • Step 6.7: Commit
git add scripts/build_examples_gallery.py landing/examples/index.html
git commit -m "$(cat <<'EOF'
feat(examples): card rows as ls -la grid columns (§4)

Replaces the flex-row .eh card header with a 7-column CSS Grid that
mimics 'ls -la' output: number, permissions, size, key badge,
filename (with NN_ prefix stripped), description, chevron.

The first 30 rows get a 14ms-staggered fade-up enter animation via
the .ex-row--enter class. Rows past index 29 render immediately to
avoid a cascade-of-88 effect.

Mobile collapses to 2-line layout with permissions column hidden.
Rows are keyboard-accessible (Enter/Space) with aria-expanded synced
via a surgical single-line addition to the existing toggle() function.

Spec §4: docs/superpowers/specs/2026-04-08-examples-page-overdrive-design.md
EOF
)"

Task 7: $ cat prefix on card expansion (§5)

Goal: Add a one-line $ cat examples/NN_name.py terminal prefix above each expanded card's body content. The prefix uses the same monospace comment styling as the rest of the page.

Files: - Modify: scripts/build_examples_gallery.py:275 (the .eb CSS — add .ex-cat-prefix) - Modify: scripts/build_examples_gallery.py:189-233 (the per-card builder — insert prefix into .eb)

  • Step 7.1: Add .ex-cat-prefix CSS

In scripts/build_examples_gallery.py, locate line 275:

.eb{{padding:0 18px 18px}}.eg{{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px}}

Add these new lines AFTER line 275 (do not delete the existing line):

.ex-cat-prefix{{font-family:var(--mono);font-size:11px;color:var(--ft);padding:0 0 10px;user-select:text}}
.ex-cat-prefix__glyph{{color:var(--gn);margin-right:6px}}
  • Step 7.2: Insert the prefix into the .eb body

In scripts/build_examples_gallery.py, locate the per-card builder loop. Find this part of the f-string in the cards.append(...) call:

            f'<div class="eb" style="display:none">'
            f'<div class="eg">{cats_html}</div>'

Replace those two lines with:

            f'<div class="eb" style="display:none">'
            f'<div class="ex-cat-prefix"><span class="ex-cat-prefix__glyph">$</span>cat examples/{ex["file"]}</div>'
            f'<div class="eg">{cats_html}</div>'

The prefix uses the FULL filename ex["file"] (with NN_ prefix), not the stripped display name. This is intentional — the path inside the $ cat command should be the real filename you'd type.

  • Step 7.3: Regenerate the HTML

Run:

python scripts/build_examples_gallery.py > landing/examples/index.html

Expected: exits 0, file ~600KB.

  • Step 7.4: Verify in browser

Open landing/examples/index.html in a browser. Click any card to expand it.

Expected: - Above the existing tag links / Copy / GitHub / Docs buttons, a small monospace line reads: $ cat examples/01_hello_world.py (with the $ in green and the rest in the dim faint color). - The line is selectable (you can highlight + copy the path text). - Collapsing the card hides the prefix along with the rest of the body. - Expanding any other card shows its corresponding $ cat examples/NN_name.py prefix.

  • Step 7.5: Commit
git add scripts/build_examples_gallery.py landing/examples/index.html
git commit -m "$(cat <<'EOF'
feat(examples): add \$ cat prefix to expanded card bodies (§5)

When a card is expanded, a one-line monospace '\$ cat examples/NN_name.py'
prefix renders above the existing action buttons and source pane.
Visually frames the source as the output of a real shell command.

The prefix uses the full filename (with NN_ prefix) since that is the
real path you would type in a shell.

Spec §5: docs/superpowers/specs/2026-04-08-examples-page-overdrive-design.md
EOF
)"

Task 8: Final cleanup, audits, and polish

Goal: Run the reduced-motion audit, mobile audit, focus-ring audit, and remove any dead CSS/JS left over from the redesign.

Files: - Modify: scripts/build_examples_gallery.py — only if dead code is found

  • Step 8.1: Reduced-motion audit

Open landing/examples/index.html in a browser. DevTools → Rendering → Emulate prefers-reduced-motion: reduce. Reload.

Walk through every animation in the spec: - §6 nav .exec-dot: should be a static glow, no pulse. ✓ verify. - §1 terminal type-on: command should appear fully typed instantly. Caret should be static (visible but not blinking). ✓ verify. - §2 rail stamp sweep: should NOT play on viewport entry. Rail in rest state immediately. ✓ verify. - §2 rail click stamp: clicking a segment should NOT trigger a stamp. Selection still works. ✓ verify. - §4 card row enter: rows 0–29 should appear immediately without cascade. ✓ verify. - §4 chevron rotation: should be instant (0.01s) when opening/closing. ✓ verify.

If any animation still plays under reduced-motion, find the missing @media (prefers-reduced-motion: reduce) rule and add it. Common miss: a new @keyframes was added without a corresponding fallback.

  • Step 8.2: Mobile audit at 360px

Switch DevTools to a 360×640 viewport (smallest realistic mobile). Reload.

Walk through: - Nav: no horizontal overflow. The selectools examples brand fits. - Terminal header: only $ ls examples/... is visible. No wrapping in the prompt line. - Search input: glyph and / kbd hint both visible. - Category rail: horizontally scrollable. Touch-swipe scrolls smoothly. - Card rows: collapse to 2-line layout. Number on left of line 1, filename + chevron on line 1, size + key + desc on line 2. No horizontal overflow on any row. - Card expansion: source <pre> scrolls horizontally inside the card. Buttons wrap if needed.

Fix any horizontal overflow you find. Common cause: a fixed-width grid column that doesn't have a mobile fallback.

  • Step 8.3: Focus ring audit

Tab through the page from the top: - Nav links should each show a visible focus ring. - Search input shows its existing focus ring. - Category rail segments: each tabbable, each shows a visible focus ring. - Card rows: each tabbable, each shows a visible focus ring. - Inside an open card: Copy / GitHub / Docs buttons each show a visible focus ring.

If any focusable element has no visible ring, add :focus-visible { outline: 2px solid var(--cy); outline-offset: 2px; } for that element type.

  • Step 8.4: Dead-code grep

In scripts/build_examples_gallery.py, search for any classes/identifiers that the redesign removed but left in the CSS: - .cb — removed in Task 4 (was the chip class). Verify it is not in the CSS string anymore. If it is, delete those rules. - .cr — removed in Task 4 (was the chip row container). Same check. - .eh (as a flex layout) — the class is still applied to .ex-row elements for closest() compatibility, BUT the old .eh{{display:flex;...}} rule may have been deleted in Task 6. Confirm the flex rule is gone and that the surviving .eh references in JS still work (they use closest('.ec')). - .en, .ei, .et, .ed, .em, .ek, .enk, .eln — removed in Task 6 (old card header subclasses). Verify their CSS rules are deleted. - CAT_ICONS (Python dict at line 44) — no longer referenced after Task 4. Decide whether to delete the dict or keep it for potential future use. If keeping, leave it. If deleting, remove lines 44-62.

The decision criterion: delete only what is provably unused. If in doubt, leave it.

  • Step 8.5: Final regeneration and full smoke test

Run:

python scripts/build_examples_gallery.py > landing/examples/index.html

Open the regenerated file. Run through the full UX: 1. Page loads. Nav dot pulses. Terminal header types ls examples/. ✓ 2. Category rail sweeps in on first view. ✓ 3. Click rag in the rail. List filters to 12 cards. Prompt updates to --tags rag. Counter says # 12 files match. ✓ 4. Type embed in search. List filters to embedding-related RAG examples. Prompt updates to --tags rag | grep -i embed. ✓ 5. Clear search. Click all. Full list returns. ✓ 6. Click row 01 (hello_world.py). Card expands. $ cat examples/01_hello_world.py prefix shown. Source code rendered with syntax highlighting. ✓ 7. Click Copy. Source is copied to clipboard. Button text changes to "Copied!" briefly. ✓ 8. Click GitHub link. Opens the file on GitHub in a new tab. ✓ 9. Press / somewhere outside an input. Search box gains focus. ✓ 10. Tab through the page. Every interactive element has a focus ring. ✓ 11. Switch to mobile viewport (360px). Repeat steps 1-7. All work. ✓ 12. Enable reduced-motion. Reload. No animations play; all functionality intact. ✓

If any step fails, fix it now. Do not commit broken state.

  • Step 8.6: Commit cleanup

If Step 8.4 removed any dead code, or if any audit step required a fix, commit the cleanup:

git add scripts/build_examples_gallery.py landing/examples/index.html
git commit -m "$(cat <<'EOF'
chore(examples): remove dead CSS/JS from §1-§5 redesign cleanup

Final cleanup commit for the examples page overdrive redesign:
- Reduced-motion audit pass
- Mobile audit at 360px
- Focus ring audit
- Dead-code removal for classes superseded by .ex-* equivalents

Spec: docs/superpowers/specs/2026-04-08-examples-page-overdrive-design.md
EOF
)"

If no cleanup was needed, skip this commit and proceed to Step 8.7.

  • Step 8.7: Open the PR
gh pr create --title "feat(examples): overdrive redesign for /examples/" --body "$(cat <<'EOF'
## Summary
- Brings `landing/examples/index.html` into the same execution-pointer visual language as the redesigned landing page (`2026-04-07-landing-overdrive-fused-design.md`)
- Six sections rebuilt: nav dot, terminal-session header, proportional category rail, search row, ls -la card rows, $ cat card expansion
- Single file edited: `scripts/build_examples_gallery.py` (the generator). HTML is regenerated.

## Spec
`docs/superpowers/specs/2026-04-08-examples-page-overdrive-design.md`

## Test plan
- [ ] Open `/examples/` at 1440x900 — terminal header types `ls examples/`, category rail sweeps in, card rows render as ls -la columns
- [ ] Click a category segment — list filters, prompt rewrites with `--tags`, segment re-stamps
- [ ] Type in search — prompt rewrites with ` | grep -i`, counter updates to `# N files match`
- [ ] Press `/` — search input focused
- [ ] Tab through the page — every interactive element has a visible focus ring
- [ ] Expand a card — `$ cat examples/NN_name.py` prefix shown above source
- [ ] Mobile viewport (375px and 360px) — header collapses, rail scrolls, rows collapse to 2 lines
- [ ] `prefers-reduced-motion: reduce` — no animations play, all functionality intact
- [ ] Click between `/` and `/examples/` — same dot pulses in same place in nav
EOF
)"

Self-review

After writing the plan, I checked it against the spec and against the writing-plans skill rules. Findings:

Spec coverage (every spec section has a task): - §1 Terminal-session header → Task 3 ✓ - §2 Category rail → Task 4 ✓ - §3 Filter row and search input → Task 5 ✓ - §4 Card list as ls -la output → Task 6 ✓ - §5 Card expansion as $ cat output → Task 7 ✓ - §6 Nav bar → Task 2 ✓ - Setup (atom duplication) → Task 1 ✓ - Final audits → Task 8 ✓

Placeholder scan: no TBD / TODO / "implement appropriately" / "add error handling". Every code block contains the actual code. Every test step has the exact expected behavior to verify in browser.

Type / name consistency: - .ex-row class is used consistently across Tasks 6, 7, and 8. - .ex-row--enter modifier class introduced in Task 6 is referenced consistently in Task 8's reduced-motion audit. - syncPrompt() is introduced in Task 3 and called from Task 4's rail click handler — both call sites pass no arguments, both expect the function in the global scope. - --seg-weight and --seg-index are introduced in Task 4 and the corresponding CSS that consumes them is also in Task 4 — single-task introduction, no cross-task type drift. - --row-index is similarly self-contained in Task 6.

Bite-sized granularity: every step is one action (read a line, edit a line, run a command, verify in browser, commit). Each task takes ~20-30 minutes including verification.

TDD note: this is a static-site generator producing HTML, not unit-testable business logic. The test step in each task is a browser smoke test: load the regenerated page, exercise the new behavior, verify visually. This is the right test loop for HTML/CSS work — adding pytest tests for an HTML generator that already produces a valid file would be over-engineering.