mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
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:
@@ -37,3 +37,4 @@ evals/
|
||||
public/skills/
|
||||
public/anti-patterns/
|
||||
public/tutorials/
|
||||
public/visual-mode/
|
||||
|
||||
@@ -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>`,
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
============================================ */
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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 →</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 });
|
||||
|
||||
@@ -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, "");
|
||||
|
||||
Reference in New Issue
Block a user