New /visual-mode top-level page, pull gallery out of /anti-patterns

The 'In the wild' section at the bottom of /anti-patterns was
mischaracterizing synthetic fixtures as real examples and was buried
deep in a taxonomy of detection rules. The specimens belong somewhere
that frames them as what they actually are: live pages you can click
into to experience Visual Mode. Split them off into a new top-level
page that also finally gives Visual Mode first-class treatment.

- New /visual-mode page, top-level nav item, single-column layout (no
  sidebar). Structure:
    1. Editorial header with an "Live detection overlay" eyebrow.
    2. Live iframe embed of visual-mode-demo.html inside mac-window
       chrome, same preview component the homepage uses.
    3. "Three ways to run it" section with three method cards:
         - /critique runs the overlay inside its browser pass
         - `npx impeccable live` starts a standalone overlay server
         - Chrome extension, marked coming soon, with a cream bg
    4. "Try it live" gallery of the 11 synthetic specimens as
       clickable cards. Each links to /antipattern-examples/{id}.html
       where the detector script is already injected so the reader
       lands on a live overlay.
- scripts/build-sub-pages.js: new renderVisualModeMain(); visualMode
  added to outDirs; generator loop writes /visual-mode/index.html.
- server/index.js: new /visual-mode route serving the generated file.
- Top nav on every page gains 'Visual Mode' between Anti-Patterns
  and the GitHub pill. Updated the partial + all 4 hand-authored
  HTML pages.
- .gitignore adds public/visual-mode/.

- /anti-patterns: 'In the wild' section and its TOC entry removed.
  Replaced with a one-line pointer at the end of the lede: "Want to
  see them live on real pages? Try Visual Mode." GALLERY_ITEMS stays
  in the catalog file (now used by /visual-mode only).

- public/css/sub-pages.css: new .visual-mode-page-body + .visual-mode-*
  classes. Ports the mac-window chrome (dots + mono title) from
  main.css, adds three-card method grid, and reuses the existing
  .gallery-card styles for the specimen list.

Clean up a few em-dashes in the catalog (block comments + one visible
visual example) so the build-time validator stays clean.

Server restart required to pick up the new /visual-mode route.
This commit is contained in:
Paul Bakaus
2026-04-08 13:04:23 -07:00
parent ebe07cbae5
commit 27d1b13bc2
10 changed files with 315 additions and 39 deletions
+1
View File
@@ -37,3 +37,4 @@ evals/
public/skills/
public/anti-patterns/
public/tutorials/
public/visual-mode/
+13 -13
View File
@@ -5,18 +5,18 @@
* are parsed at build time. This file adds three pieces of content that
* can't be automated:
*
* 1. DETECTION_LAYERS which layer (cli, browser, or llm) catches the
* 1. DETECTION_LAYERS: which layer (cli, browser, or llm) catches the
* rule. Manually classified by reading the detector source and the
* browser-only test file.
*
* 2. VISUAL_EXAMPLES a tiny inline HTML snippet showing what the
* 2. VISUAL_EXAMPLES: a tiny inline HTML snippet showing what the
* bad pattern actually looks like. Rendered inside each rule card.
* Snippets should be self-contained with inline styles, use the
* cream/paper/ink palette when possible, and sit naturally at
* ~100% width × ~120px height.
* ~100% width by ~120px height.
*
* 3. LLM_ONLY_RULES DON'T lines from source/skills/impeccable/SKILL.md
* that don't map to any detection rule. These can only be caught by
* 3. LLM_ONLY_RULES: DON'T lines from source/skills/impeccable/SKILL.md
* that do not map to any detection rule. These can only be caught by
* the /critique skill's LLM pass. They appear on the /anti-patterns
* page alongside detected rules with an 'llm' layer badge.
*/
@@ -26,13 +26,13 @@
/**
* Which layer catches each rule.
*
* 'cli' static analysis or jsdom (works with `npx impeccable detect`
* on files, no browser required)
* 'browser' requires real browser layout (getBoundingClientRect with
* actual dimensions). Works via Puppeteer or the browser
* extension, NOT via the CLI on raw HTML.
* 'llm' no deterministic detector; only caught by /critique's LLM
* assessment pass.
* 'cli': static analysis or jsdom (works with `npx impeccable detect`
* on files, no browser required)
* 'browser': requires real browser layout (getBoundingClientRect with
* actual dimensions). Works via Puppeteer or the browser
* extension, NOT via the CLI on raw HTML.
* 'llm': no deterministic detector; only caught by /critique's LLM
* assessment pass.
*
* Per tests/detect-antipatterns-browser.test.mjs: only two rules genuinely
* need real browser layout. Everything else is 'cli'.
@@ -126,7 +126,7 @@ export const VISUAL_EXAMPLES = {
'tight-leading': `<div style="font-family: system-ui, sans-serif; font-size: 13px; color: #111; line-height: 1.0; max-width: 220px;">Tight leading makes multi-line body text feel crammed and hard for the eye to track between lines.</div>`,
'skipped-heading': `<div style="font-family: system-ui, sans-serif; color: #111;"><h1 style="font-size: 20px; font-weight: 700; margin: 0 0 4px;">Page title (h1)</h1><h3 style="font-size: 13px; font-weight: 600; margin: 0; color: #555;">Subsection (h3) skipped h2</h3></div>`,
'skipped-heading': `<div style="font-family: system-ui, sans-serif; color: #111;"><h1 style="font-size: 20px; font-weight: 700; margin: 0 0 4px;">Page title (h1)</h1><h3 style="font-size: 13px; font-weight: 600; margin: 0; color: #555;">Subsection (h3), skipped h2</h3></div>`,
'justified-text': `<div style="font-family: system-ui, sans-serif; font-size: 12px; color: #111; text-align: justify; max-width: 230px; line-height: 1.5;">Justified text on screens creates rivers of whitespace because browsers can't hyphenate well. Leave this for print.</div>`,
+1
View File
@@ -9,6 +9,7 @@
<a href="/" data-nav="home">Home</a>
<a href="/skills" data-nav="docs">Docs</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/visual-mode" data-nav="visual-mode">Visual Mode</a>
</nav>
<a href="https://github.com/pbakaus/impeccable" class="site-header-github" target="_blank" rel="noopener" aria-label="Impeccable on GitHub, 17k stars">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
+1
View File
@@ -176,6 +176,7 @@
<a href="/" data-nav="home">Home</a>
<a href="/skills" data-nav="docs">Docs</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/visual-mode" data-nav="visual-mode">Visual Mode</a>
</nav>
<a href="https://github.com/pbakaus/impeccable" class="site-header-github" target="_blank" rel="noopener" aria-label="Impeccable on GitHub, 17k stars">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
+201
View File
@@ -1105,6 +1105,207 @@ main#main {
max-width: 65ch;
}
/* ============================================
VISUAL MODE PAGE
============================================ */
.visual-mode-page-body main#main {
/* Reset the docs-layout override; this page uses its own max-width. */
max-width: none;
margin: 0;
padding: 0;
}
.visual-mode-page {
max-width: 1100px;
margin: 0 auto;
padding: clamp(2rem, 4vw, 3.5rem) clamp(var(--spacing-md), 4vw, var(--spacing-2xl)) clamp(4rem, 8vw, 6rem);
}
.visual-mode-page-header {
max-width: 720px;
margin-bottom: clamp(2.5rem, 5vw, 3.5rem);
}
.visual-mode-demo-wrap {
margin-bottom: clamp(3rem, 6vw, 4.5rem);
}
.visual-mode-demo-caption {
font-size: 0.8125rem;
color: var(--color-ash);
font-style: italic;
margin-top: var(--spacing-sm);
text-align: center;
}
/* Mac-window chrome around the iframe (same shape as the homepage
preview; ported here so sub-pages.css is self-contained). */
.visual-mode-preview {
border-radius: 10px;
overflow: hidden;
border: 1px solid var(--color-mist);
box-shadow: 0 12px 40px -8px rgba(0, 0, 0, 0.12);
max-width: 1040px;
margin: 0 auto;
}
.visual-mode-preview-header {
display: flex;
align-items: center;
gap: 6px;
padding: 10px 14px;
background: var(--color-cream);
border-bottom: 1px solid var(--color-mist);
}
.visual-mode-preview-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.visual-mode-preview-dot.red { background: #ff5f56; }
.visual-mode-preview-dot.yellow { background: #ffbd2e; }
.visual-mode-preview-dot.green { background: #27c93f; }
.visual-mode-preview-title {
margin-left: auto;
font-family: var(--font-mono);
font-size: 0.75rem;
color: var(--color-ash);
}
.visual-mode-frame {
display: block;
width: 100%;
height: 580px;
border: none;
background: white;
}
/* Three invocation methods. */
.visual-mode-methods {
margin-bottom: clamp(3rem, 6vw, 4.5rem);
}
.visual-mode-methods-title {
font-family: var(--font-display);
font-size: clamp(1.75rem, 3vw, 2.25rem);
font-weight: 500;
font-style: italic;
color: var(--color-ink);
letter-spacing: -0.01em;
margin-bottom: var(--spacing-lg);
}
.visual-mode-methods-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: var(--spacing-md);
}
.visual-mode-method {
padding: var(--spacing-lg);
background: var(--color-paper);
border: 1px solid var(--color-mist);
border-radius: 10px;
display: flex;
flex-direction: column;
gap: 8px;
transition: border-color var(--duration-fast) var(--ease-out);
}
.visual-mode-method:hover {
border-color: var(--color-ash);
}
.visual-mode-method[data-coming-soon] {
background: var(--color-cream);
}
.visual-mode-method-label {
font-family: var(--font-mono);
font-size: 0.625rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.14em;
color: var(--color-accent);
}
.visual-mode-method-name {
font-family: var(--font-display);
font-size: 1.375rem;
font-style: italic;
font-weight: 500;
color: var(--color-ink);
line-height: 1.25;
}
.visual-mode-method-name a {
color: inherit;
text-decoration: none;
border-bottom: 1px solid var(--color-accent);
}
.visual-mode-method-name a:hover {
color: var(--color-accent);
}
.visual-mode-method-name code {
font-family: var(--font-mono);
font-size: 0.875em;
font-style: normal;
background: var(--color-cream);
border: 1px solid var(--color-mist);
padding: 2px 8px;
border-radius: 4px;
color: var(--color-ink);
}
.visual-mode-method-desc {
font-size: 0.9375rem;
line-height: 1.6;
color: var(--color-charcoal);
flex: 1;
}
.visual-mode-method-desc a {
color: var(--color-ink);
text-decoration: none;
border-bottom: 1px solid var(--color-accent);
font-family: var(--font-mono);
font-size: 0.875em;
font-weight: 500;
}
.visual-mode-method-desc a:hover {
color: var(--color-accent);
}
/* Gallery (specimens) section */
.visual-mode-gallery-header {
margin-bottom: var(--spacing-lg);
}
.visual-mode-gallery-title {
font-family: var(--font-display);
font-size: clamp(1.75rem, 3vw, 2.25rem);
font-weight: 500;
font-style: italic;
color: var(--color-ink);
letter-spacing: -0.01em;
margin-bottom: var(--spacing-sm);
}
.visual-mode-gallery-lede {
font-size: 0.9375rem;
line-height: 1.6;
color: var(--color-charcoal);
max-width: 60ch;
}
/* ============================================
ANTI-PATTERNS: IN THE WILD (merged gallery)
============================================ */
+1
View File
@@ -198,6 +198,7 @@
<a href="/" data-nav="home">Home</a>
<a href="/skills" data-nav="docs">Docs</a>
<a href="/anti-patterns" data-nav="anti-patterns" aria-current="page">Anti-Patterns</a>
<a href="/visual-mode" data-nav="visual-mode">Visual Mode</a>
</nav>
<a href="https://github.com/pbakaus/impeccable" class="site-header-github" target="_blank" rel="noopener" aria-label="Impeccable on GitHub, 17k stars">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
+1
View File
@@ -59,6 +59,7 @@
<a href="/" data-nav="home" aria-current="page">Home</a>
<a href="/skills" data-nav="docs">Docs</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/visual-mode" data-nav="visual-mode">Visual Mode</a>
</nav>
<a href="https://github.com/pbakaus/impeccable" class="site-header-github" target="_blank" rel="noopener" aria-label="Impeccable on GitHub, 17k stars">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
+1
View File
@@ -34,6 +34,7 @@
<a href="/" data-nav="home">Home</a>
<a href="/skills" data-nav="docs">Docs</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/visual-mode" data-nav="visual-mode">Visual Mode</a>
</nav>
<a href="https://github.com/pbakaus/impeccable" class="site-header-github" target="_blank" rel="noopener" aria-label="Impeccable on GitHub, 17k stars">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
+94 -26
View File
@@ -366,7 +366,6 @@ function renderAntiPatternsSidebar(grouped) {
<div class="skills-sidebar-group">
<ul class="skills-sidebar-list anti-patterns-sidebar-list">
${entries}
<li><a href="#in-the-wild"><span>In the wild</span><span class="anti-patterns-sidebar-count">${GALLERY_ITEMS.length}</span></a></li>
</ul>
</div>
</div>
@@ -438,6 +437,82 @@ ${cards}
</div>`;
}
/**
* Render the /visual-mode page main content.
*
* Single-column layout, no sidebar. Editorial header, live iframe embed
* of the detector running on a synthetic slop page, three-card section
* explaining the invocation methods, then a grid of real specimens the
* user can click into to see the overlay on a different page.
*/
function renderVisualModeMain() {
const specimenCards = GALLERY_ITEMS.map(
(item) => `
<a class="gallery-card" href="/antipattern-examples/${item.id}.html">
<div class="gallery-card-thumb">
<img src="/antipattern-images/${item.id}.png" alt="${escapeAttr(item.title)} specimen" loading="lazy" width="540" height="540">
</div>
<div class="gallery-card-body">
<h3 class="gallery-card-title">${escapeHtml(item.title)}</h3>
<p class="gallery-card-desc">${escapeHtml(item.desc)}</p>
</div>
</a>`,
).join('\n');
return `
<div class="visual-mode-page">
<header class="visual-mode-page-header">
<p class="sub-page-eyebrow">Live detection overlay</p>
<h1 class="sub-page-title">Visual Mode</h1>
<p class="sub-page-lede">See every anti-pattern flagged directly on the page. No screenshots, no JSON to map back to line numbers. The overlay draws an outline and a label on every element the detector catches, so you fix them in place.</p>
</header>
<section class="visual-mode-demo-wrap" aria-label="Visual Mode demo">
<div class="visual-mode-preview">
<div class="visual-mode-preview-header">
<span class="visual-mode-preview-dot red"></span>
<span class="visual-mode-preview-dot yellow"></span>
<span class="visual-mode-preview-dot green"></span>
<span class="visual-mode-preview-title">Live on a synthetic slop page</span>
</div>
<iframe src="/antipattern-examples/visual-mode-demo.html" class="visual-mode-frame" loading="lazy" title="Impeccable overlay running on a demo page"></iframe>
</div>
<p class="visual-mode-demo-caption">Hover or tap any outlined element to see which rule fired.</p>
</section>
<section class="visual-mode-methods" aria-label="Where to run Visual Mode">
<h2 class="visual-mode-methods-title">Three ways to run it</h2>
<div class="visual-mode-methods-grid">
<article class="visual-mode-method">
<p class="visual-mode-method-label">Inside /critique</p>
<h3 class="visual-mode-method-name"><a href="/skills/critique">/critique</a></h3>
<p class="visual-mode-method-desc">The design review skill opens the overlay automatically during its browser assessment pass. You get the deterministic findings highlighted in place while the LLM runs its separate heuristic review.</p>
</article>
<article class="visual-mode-method">
<p class="visual-mode-method-label">Standalone CLI</p>
<h3 class="visual-mode-method-name"><code>npx impeccable live</code></h3>
<p class="visual-mode-method-desc">Starts a local overlay server, then loads any URL you paste in an iframe with the detector script injected. Works on your own dev server, a staging URL, or anyone's live page.</p>
</article>
<article class="visual-mode-method" data-coming-soon>
<p class="visual-mode-method-label">Coming soon</p>
<h3 class="visual-mode-method-name">Chrome extension</h3>
<p class="visual-mode-method-desc">One-click activation on any tab. Currently in Chrome Web Store review. <a href="https://impeccablestyle.substack.com" target="_blank" rel="noopener">Get notified when it lands &rarr;</a></p>
</article>
</div>
</section>
<section class="visual-mode-gallery" aria-label="Try it on synthetic specimens">
<header class="visual-mode-gallery-header">
<h2 class="visual-mode-gallery-title">Try it live</h2>
<p class="visual-mode-gallery-lede">These ${GALLERY_ITEMS.length} synthetic slop pages ship with the detector script baked in. Click any to see the overlay running on a real page, then scroll around and hover the outlined elements.</p>
</header>
<div class="gallery-grid">
${specimenCards}
</div>
</section>
</div>`;
}
/**
* Render a tutorial detail page main content.
*/
@@ -483,25 +558,12 @@ ${rules.map(renderRuleCard).join('\n')}
.filter((r) => r.layer !== 'llm').length;
const llmCount = totalRules - detectedCount;
const galleryHtml = GALLERY_ITEMS.map(
(item) => `
<a class="gallery-card" href="/antipattern-examples/${item.id}.html">
<div class="gallery-card-thumb">
<img src="/antipattern-images/${item.id}.png" alt="${escapeAttr(item.title)} example" loading="lazy" width="540" height="540">
</div>
<div class="gallery-card-body">
<h3 class="gallery-card-title">${escapeHtml(item.title)}</h3>
<p class="gallery-card-desc">${escapeHtml(item.desc)}</p>
</div>
</a>`,
).join('\n');
return `
<div class="anti-patterns-content">
<header class="anti-patterns-header">
<p class="sub-page-eyebrow">${totalRules} rules</p>
<h1 class="sub-page-title">Anti-patterns</h1>
<p class="sub-page-lede">The full catalog of patterns <a href="/skills/impeccable">/impeccable</a> teaches against. ${detectedCount} are caught by a deterministic detector (<code>npx impeccable detect</code> or the browser extension). ${llmCount} can only be flagged by <a href="/skills/critique">/critique</a>'s LLM review pass.</p>
<p class="sub-page-lede">The full catalog of patterns <a href="/skills/impeccable">/impeccable</a> teaches against. ${detectedCount} are caught by a deterministic detector (<code>npx impeccable detect</code> or the browser extension). ${llmCount} can only be flagged by <a href="/skills/critique">/critique</a>'s LLM review pass. Want to see them live on real pages? Try <a href="/visual-mode">Visual Mode</a>.</p>
</header>
<details class="anti-patterns-legend">
@@ -522,17 +584,6 @@ ${rules.map(renderRuleCard).join('\n')}
<div class="anti-patterns-sections">
${sectionsHtml}
</div>
<section class="gallery-section" id="in-the-wild">
<header class="anti-patterns-section-header">
<h2 class="anti-patterns-section-title">In the wild</h2>
<p class="anti-patterns-section-count">${GALLERY_ITEMS.length} specimens</p>
</header>
<p class="gallery-section-lede">Real examples scraped from the web. Click any to see the live page with the overlay running.</p>
<div class="gallery-grid">
${galleryHtml}
</div>
</section>
</div>`;
}
@@ -548,6 +599,7 @@ export async function generateSubPages(rootDir) {
skills: path.join(rootDir, 'public/skills'),
antiPatterns: path.join(rootDir, 'public/anti-patterns'),
tutorials: path.join(rootDir, 'public/tutorials'),
visualMode: path.join(rootDir, 'public/visual-mode'),
};
// Fresh output dirs each time so stale files don't linger.
@@ -630,6 +682,22 @@ export async function generateSubPages(rootDir) {
generated.push(out);
}
// Visual Mode: single standalone page, no sidebar, single-column layout.
{
const html = renderPage({
title: 'Visual Mode | Impeccable',
description:
'See every anti-pattern flagged directly on the page. Live detection overlay from Impeccable, available via /critique, npx impeccable live, or the upcoming Chrome extension.',
bodyHtml: renderVisualModeMain(),
activeNav: 'visual-mode',
canonicalPath: '/visual-mode',
bodyClass: 'sub-page visual-mode-page-body',
});
const out = path.join(outDirs.visualMode, 'index.html');
fs.writeFileSync(out, html, 'utf-8');
generated.push(out);
}
// Tutorial detail pages.
for (const tutorial of data.tutorials) {
const sidebar = renderDocsSidebar(data.skillsByCategory, data.tutorials, { kind: 'tutorial', slug: tutorial.slug });
+1
View File
@@ -53,6 +53,7 @@ const server = serve({
return serveGenerated(path.join(ROOT_DIR, `public/skills/${id}.html`));
},
"/anti-patterns": () => serveGenerated(path.join(ROOT_DIR, "public/anti-patterns/index.html")),
"/visual-mode": () => serveGenerated(path.join(ROOT_DIR, "public/visual-mode/index.html")),
"/tutorials": () => serveGenerated(path.join(ROOT_DIR, "public/tutorials/index.html")),
"/tutorials/:slug": (req) => {
const slug = req.params.slug.replace(/[^a-z0-9-]/gi, "");