Files
pbakaus_impeccable/site/public/tutorials/critique-with-overlay.html
T
Paul BakausandClaude Fable 5 90f9eeb99b Split service layer into private impeccable-site repo
The public repo keeps the OSS promise surface: skill, CLI, extension,
tests, and the provider build. The site, labs, concept/composition
catalogs, image pipeline, Cloudflare functions, and authoring guide move
to pbakaus/impeccable-site.

concept-seed tests run against a synthetic fixture catalog; the plugin
icon and skill categories moved in-repo; build validation narrows to
README prose and non-site counts; release notes read from a sibling
impeccable-site checkout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 22:41:53 -07:00

392 lines
21 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Critique with the visual overlay | Tutorials | Impeccable</title>
<meta name="description" content="Run a full design critique that combines LLM assessment, the automated detector, and a live browser overlay so you can see exactly which elements trigger which anti-patterns on the page you're looking at.">
<meta name="theme-color" content="#fafafa">
<link rel="canonical" href="https://impeccable.style/tutorials/critique-with-overlay">
<link rel="icon" type="image/svg+xml" href="../favicon.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=Instrument+Sans:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/sub-pages.css">
</head>
<body class="sub-page skills-layout-page tutorials-page">
<a href="#main" class="skip-link">Skip to content</a>
<!-- site-header v1 -->
<header class="site-header" data-site-header>
<a href="/" class="site-header-brand" aria-label="Impeccable home">
<svg class="site-header-brand-logo" viewBox="0 0 32 32" aria-hidden="true"><rect width="32" height="32" rx="6" fill="#1a1a1a"/><text x="16" y="24" font-family="system-ui, -apple-system, sans-serif" font-size="22" font-weight="500" fill="#f5f3ef" text-anchor="middle">/</text></svg>
<span class="site-header-brand-name">Impeccable</span>
</a>
<div class="site-header-right">
<nav class="site-header-nav" aria-label="Primary">
<a href="/" data-nav="home">Home</a>
<a href="/designing" data-nav="designing">Designing</a>
<a href="/docs" data-nav="docs" aria-current="page">Docs</a>
<a href="/slop" data-nav="slop">Slop</a>
<a href="/live-mode" data-nav="live">Live</a>
</nav>
<a href="https://github.com/pbakaus/impeccable" class="site-header-github" target="_blank" rel="noopener" aria-label="Impeccable on GitHub, 21k 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>
<span class="site-header-github-label">21k</span>
<svg class="site-header-github-star" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2l2.76 6.36L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l7.24-.91L12 2z"/></svg>
</a>
</div>
</header>
<main id="main">
<div class="skills-layout">
<aside class="skills-sidebar" aria-label="Documentation">
<button class="skills-sidebar-toggle" type="button" aria-expanded="false" aria-controls="skills-sidebar-inner">
<span class="skills-sidebar-toggle-label">Critique with the visual overlay</span>
<svg class="skills-sidebar-toggle-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
</button>
<div class="skills-sidebar-inner" id="skills-sidebar-inner">
<p class="skills-sidebar-label">Docs</p>
<div class="skills-sidebar-group" data-category="tutorials">
<p class="skills-sidebar-group-title">Tutorials</p>
<ul class="skills-sidebar-list">
<li><a href="/tutorials/getting-started">Getting started</a></li>
<li><a href="/tutorials/iterate-live">Iterate on UI with Live Mode</a></li>
<li><a href="/tutorials/brand-vs-product">Brand vs product, pick a register</a></li>
<li><a href="/tutorials/critique-with-overlay" aria-current="page">Critique with the visual overlay</a></li>
</ul>
</div>
<hr class="skills-sidebar-divider">
<div class="skills-sidebar-group" data-category="create">
<p class="skills-sidebar-group-title">Create</p>
<ul class="skills-sidebar-list">
<li><a href="/docs/impeccable">/impeccable</a></li>
<li><a href="/docs/craft">craft</a></li>
<li><a href="/docs/shape">shape</a></li>
</ul>
</div>
<div class="skills-sidebar-group" data-category="evaluate">
<p class="skills-sidebar-group-title">Evaluate</p>
<ul class="skills-sidebar-list">
<li><a href="/docs/audit">audit</a></li>
<li><a href="/docs/critique">critique</a></li>
</ul>
</div>
<div class="skills-sidebar-group" data-category="refine">
<p class="skills-sidebar-group-title">Refine</p>
<ul class="skills-sidebar-list">
<li><a href="/docs/animate">animate</a></li>
<li><a href="/docs/bolder">bolder</a></li>
<li><a href="/docs/colorize">colorize</a></li>
<li><a href="/docs/delight">delight</a></li>
<li><a href="/docs/layout">layout</a></li>
<li><a href="/docs/overdrive">overdrive</a></li>
<li><a href="/docs/quieter">quieter</a></li>
<li><a href="/docs/typeset">typeset</a></li>
</ul>
</div>
<div class="skills-sidebar-group" data-category="simplify">
<p class="skills-sidebar-group-title">Simplify</p>
<ul class="skills-sidebar-list">
<li><a href="/docs/adapt">adapt</a></li>
<li><a href="/docs/clarify">clarify</a></li>
<li><a href="/docs/distill">distill</a></li>
</ul>
</div>
<div class="skills-sidebar-group" data-category="harden">
<p class="skills-sidebar-group-title">Harden</p>
<ul class="skills-sidebar-list">
<li><a href="/docs/harden">harden</a></li>
<li><a href="/docs/onboard">onboard</a></li>
<li><a href="/docs/optimize">optimize</a></li>
<li><a href="/docs/polish">polish</a></li>
</ul>
</div>
<div class="skills-sidebar-group" data-category="system">
<p class="skills-sidebar-group-title">System</p>
<ul class="skills-sidebar-list">
<li><a href="/docs/document">document</a></li>
<li><a href="/docs/extract">extract</a></li>
<li><a href="/docs/live">live</a></li>
<li><a href="/docs/teach">teach</a></li>
</ul>
</div>
</div>
</aside>
<div class="skills-main">
<article class="tutorial-detail">
<header class="tutorial-detail-header">
<p class="skill-detail-eyebrow"><a href="/tutorials">Tutorials</a> / 04</p>
<h1 class="tutorial-detail-title">Critique with the visual overlay</h1>
<p class="tutorial-detail-tagline">Use /impeccable critique plus the browser overlay to review a live page with ground truth.</p>
</header>
<section class="tutorial-detail-body prose">
<h2 id="what-youll-build">What you&#39;ll build</h2>
<p>You will run a complete design critique against a live page in your browser, with every flagged anti-pattern highlighted directly on the element that caused it. No screenshots, no guesswork, no paragraph of findings you have to map back to the code.</p>
<p>Total time: about ten minutes.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Impeccable installed in your project (see <a href="/tutorials/getting-started">getting started</a> if you have not).</li>
<li>A harness with browser automation available (Claude Code with the Chrome extension, or similar).</li>
<li>A page you want to critique, either local (<code>localhost:3000/pricing</code>) or deployed.</li>
</ul>
<h2 id="step-1-run-impeccable-critique">Step 1. Run /impeccable critique</h2>
<p>From your harness, run:</p>
<div class="code-block-wrap"><pre class="code-block"><code>/impeccable critique the pricing page at localhost:3000/pricing</code></pre><button class="code-block-copy" type="button" data-copy="/impeccable critique the pricing page at localhost:3000/pricing" aria-label="Copy to clipboard"></button></div>
<p>The skill kicks off two independent assessments in parallel. They run in separate sub-agents so one does not bias the other.</p>
<h3 id="what-the-llm-assessment-does">What the LLM assessment does</h3>
<p>The first assessment reads your source code and, if browser automation is available, opens the live page in a new tab. It walks the full impeccable skill DO/DON&#39;T catalog and scores the page against Nielsen&#39;s 10 heuristics, the 8-item cognitive load checklist, and the brand fit from your <code>PRODUCT.md</code>.</p>
<p>It labels the tab it opens with <code>[LLM]</code> in the title so you can tell which one is which.</p>
<h3 id="what-the-automated-detector-does">What the automated detector does</h3>
<p>The second assessment runs <code>npx impeccable detect</code> against the page. This is deterministic: around thirty specific pattern checks that fire or do not fire. Gradient text, purple palettes, side-tab borders, nested cards, line length problems, low contrast, tiny body text, and the rest. The <a href="/anti-patterns">full catalog</a> lists every rule and which layer (CLI, browser, or LLM-only) catches it.</p>
<p>You get back a JSON list of every finding with its element selector, the rule that fired, and a short description.</p>
<h2 id="step-2-open-the-visual-overlay">Step 2. Open the visual overlay</h2>
<p>Impeccable ships with a visual mode that highlights every detected anti-pattern directly on the page. Here is what it looks like running on a deliberately-bad synthwave landing page:</p>
<div class="tutorial-embed">
<div class="tutorial-embed-header">
<span class="tutorial-embed-dot red"></span>
<span class="tutorial-embed-dot yellow"></span>
<span class="tutorial-embed-dot green"></span>
<span class="tutorial-embed-title">Live detection overlay</span>
</div>
<iframe src="/antipattern-examples/visual-mode-demo.html" class="tutorial-embed-iframe" loading="lazy" title="Impeccable visual overlay running on a demo page"></iframe>
</div>
<p>Every outlined element has a floating label naming the rule that fired. Hover an outline to see the full finding. This is exactly what you will see on your own page.</p>
<p>You have two ways to open it:</p>
<ol>
<li><strong><a href="https://chromewebstore.google.com/detail/impeccable/bdkgmiklpdmaojlpflclinlofgjfpabf" target="_blank" rel="noopener">Chrome extension</a></strong>: one-click activation on any page. Click the Impeccable icon in the toolbar and every anti-pattern gets highlighted instantly.</li>
<li><strong>Inside <code>/impeccable critique</code></strong>: the skill opens a browser tab labeled <code>[Human]</code> with the detector active during the browser portion of the assessment. You do not need to do anything extra.</li>
</ol>
<p>For this tutorial, the easiest option is the Chrome extension. Install it, navigate to your pricing page, and click the Impeccable icon. You will see the overlay appear immediately on the live page.</p>
<h2 id="step-3-merge-the-two-assessments">Step 3. Merge the two assessments</h2>
<p>Back in your harness, <code>/impeccable critique</code> has finished and produced a combined report. It looks something like:</p>
<div class="code-block-wrap"><pre class="code-block"><code>AI slop verdict: FAIL
Detected tells: gradient-text (2), ai-color-palette (1),
nested-cards (1), side-tab (3)
Heuristic scores (avg 2.8/4):
Visibility of status: 3 (good)
Match between system and real world: 2 (partial)
Consistency and standards: 2 (partial)
...
Cognitive load: 3/8 failures (moderate)
Visible options at primary decision: 6 (flag)
Decision points stacked at top: yes (flag)
Progressive disclosure: absent on advanced pricing toggles
What&#39;s working:
- Clear price hierarchy
- Strong headline
Priority issues:
1. Hero uses gradient text on the main price
Why: AI tell, reduces contrast, hurts scannability
Fix: solid ink color at one weight heavier
2. Feature comparison table has 4 nested card levels
Why: visual noise, unclear hierarchy
Fix: flatten to a table with zebra striping
Questions to answer:
- Is the free tier a real product or a funnel?
- What does a user feel when they land here from an ad vs from search?</code></pre><button class="code-block-copy" type="button" data-copy="AI slop verdict: FAIL
Detected tells: gradient-text (2), ai-color-palette (1),
nested-cards (1), side-tab (3)
Heuristic scores (avg 2.8/4):
Visibility of status: 3 (good)
Match between system and real world: 2 (partial)
Consistency and standards: 2 (partial)
...
Cognitive load: 3/8 failures (moderate)
Visible options at primary decision: 6 (flag)
Decision points stacked at top: yes (flag)
Progressive disclosure: absent on advanced pricing toggles
What's working:
- Clear price hierarchy
- Strong headline
Priority issues:
1. Hero uses gradient text on the main price
Why: AI tell, reduces contrast, hurts scannability
Fix: solid ink color at one weight heavier
2. Feature comparison table has 4 nested card levels
Why: visual noise, unclear hierarchy
Fix: flatten to a table with zebra striping
Questions to answer:
- Is the free tier a real product or a funnel?
- What does a user feel when they land here from an ad vs from search?" aria-label="Copy to clipboard"></button></div>
<h2 id="step-4-fix-the-findings">Step 4. Fix the findings</h2>
<p>The report gives you a priority list. You can work through them one at a time, ask the model to fix them all at once, or anything in between. What matters is using the overlay to verify:</p>
<ol>
<li>Keep the overlay open in one tab.</li>
<li>Make fixes in code (or ask the model to fix everything).</li>
<li>Reload. The overlay re-scans and resolved findings disappear.</li>
</ol>
<p>This feedback loop is the reason the overlay matters. You see fixes land in real time, and you never ship a &quot;fix&quot; that did not actually satisfy the rule.</p>
<h2 id="step-5-re-run-when-you-are-done">Step 5. Re-run when you are done</h2>
<p>After you have worked through the priority list, run <code>/impeccable critique</code> again. The goal is a clean AI slop verdict and at least a 3.5 average on the heuristics. Cognitive load should be below 2 failures.</p>
<p>If something still fires, fix it or write a suppression comment explaining why the rule does not apply in your context (the detector respects a small set of opt-out pragmas, but use them sparingly).</p>
<h2 id="what-to-try-next">What to try next</h2>
<ul>
<li><a href="/tutorials/iterate-live">Iterate on the critique findings with Live Mode</a>. Pick the element critique flagged, drop a comment, get three redirections hot-swapped in place, and write the accepted one back to source.</li>
<li><code>/impeccable audit the same page</code> to catch the implementation issues critique does not cover (accessibility, performance, theming).</li>
<li><code>/impeccable polish</code> if the critique report is clean and you want the last-mile refinement pass.</li>
<li><code>/impeccable distill</code> if critique flagged &quot;too busy&quot; or &quot;cognitive load&quot;. Distill removes what should not be there.</li>
</ul>
<h2 id="common-issues">Common issues</h2>
<ul>
<li><strong>The overlay shows no findings but critique says there are problems</strong>. The detector catches deterministic patterns. Critique catches judgment calls. They are complementary, not redundant.</li>
<li><strong>The LLM assessment and the detector disagree</strong>. That is normal. The LLM is subjective. The detector is deterministic. When they disagree, look at both and make a call.</li>
<li><strong>The overlay breaks the page layout</strong>. Rare, but some CSS can interact with the injected overlay styles. Use the <a href="https://chromewebstore.google.com/detail/impeccable/bdkgmiklpdmaojlpflclinlofgjfpabf" target="_blank" rel="noopener">Chrome extension</a> for the most reliable experience, or run <code>npx impeccable detect</code> from the CLI and apply findings manually.</li>
</ul>
</section>
</article>
</div>
</div>
</main>
<script>
// Copy buttons on rendered code blocks
document.addEventListener('click', (e) => {
const btn = e.target.closest('[data-copy]');
if (!btn) return;
const text = btn.getAttribute('data-copy');
if (!text) return;
navigator.clipboard.writeText(text).then(() => {
btn.classList.add('is-copied');
setTimeout(() => btn.classList.remove('is-copied'), 1500);
}).catch(() => {});
});
// Mobile sidebar toggle (shown on narrow viewports, hidden on desktop).
document.addEventListener('click', (e) => {
const toggle = e.target.closest('.skills-sidebar-toggle');
if (!toggle) return;
const expanded = toggle.getAttribute('aria-expanded') === 'true';
toggle.setAttribute('aria-expanded', String(!expanded));
});
// Before/after split-compare: drag on touch, hover OR drag on mouse.
// Pointer events attach to the padded .split-comparison wrapper so
// there is a ~20px invisible buffer around the visible box. The
// divider only snaps back when the pointer leaves that outer buffer.
(function initSplitCompare() {
const wrappers = document.querySelectorAll('.split-comparison');
if (wrappers.length === 0) return;
const hasHover = matchMedia('(hover: hover)').matches;
const DEFAULT_POSITION = 50;
for (const wrapper of wrappers) {
const container = wrapper.querySelector('.split-container');
const splitAfter = wrapper.querySelector('.split-after');
const splitDivider = wrapper.querySelector('.split-divider');
if (!container || !splitAfter || !splitDivider) continue;
const tanAngle = Math.tan(10 * Math.PI / 180);
let skewOffset = 8;
const recalcSkew = () => {
const r = container.getBoundingClientRect();
if (r.width > 0 && r.height > 0) {
skewOffset = 50 * r.height * tanAngle / r.width;
}
};
recalcSkew();
window.addEventListener('resize', recalcSkew, { passive: true });
let targetX = DEFAULT_POSITION;
let currentX = DEFAULT_POSITION;
let rafId = null;
const paint = (pct) => {
const x = Math.max(-skewOffset, Math.min(100 + skewOffset, pct));
splitAfter.style.clipPath =
`polygon(${x + skewOffset}% 0%, 100% 0%, 100% 100%, ${x - skewOffset}% 100%)`;
splitDivider.style.left = `${x}%`;
};
const step = () => {
currentX += (targetX - currentX) * 0.2;
if (Math.abs(targetX - currentX) < 0.1) {
currentX = targetX;
rafId = null;
} else {
rafId = requestAnimationFrame(step);
}
paint(currentX);
};
const setTarget = (pct) => {
targetX = pct;
if (rafId === null) rafId = requestAnimationFrame(step);
};
paint(DEFAULT_POSITION);
// Percentage is always relative to the VISIBLE .split-container,
// not the padded .split-comparison wrapper. The pointer event
// target is the wrapper but the clip-path math uses the inner box.
const pctFromClientX = (clientX) => {
const rect = container.getBoundingClientRect();
return ((clientX - rect.left) / rect.width) * 100;
};
let hovering = false;
let dragging = false;
wrapper.addEventListener('pointerenter', (e) => {
if (hasHover && e.pointerType === 'mouse') {
hovering = true;
}
});
wrapper.addEventListener('pointerdown', (e) => {
dragging = true;
wrapper.setPointerCapture(e.pointerId);
setTarget(pctFromClientX(e.clientX));
});
wrapper.addEventListener('pointermove', (e) => {
if (dragging || hovering) {
setTarget(pctFromClientX(e.clientX));
}
});
const endDrag = (e) => {
if (dragging) {
dragging = false;
try { wrapper.releasePointerCapture(e.pointerId); } catch {}
}
};
wrapper.addEventListener('pointerup', endDrag);
wrapper.addEventListener('pointercancel', endDrag);
wrapper.addEventListener('pointerleave', (e) => {
endDrag(e);
if (hovering) {
hovering = false;
setTarget(DEFAULT_POSITION);
}
});
}
})();
</script>
</body>
</html>