mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-18 09:06:53 +03:00
Generate /tutorials index + 2 walk-throughs
Third and final new section. Adds the tutorials browser with two
hand-written walk-throughs: "Getting started" and "Critique with the
visual overlay". Same docs-browser shell as skills and anti-patterns.
- content/site/tutorials/getting-started.md
Five-minute install, teach, and first polish pass. Covers npx skills
add, answering the teach interview honestly, and running /polish on
an existing page. Includes common issues and what to try next.
- content/site/tutorials/critique-with-overlay.md
Ten-minute walk-through of the full /critique workflow with the
browser overlay. Covers the two-assessment model (LLM + detector),
the three ways to open the overlay (extension, inside critique, or
standalone via `npx impeccable live`), the fix-reload-verify loop,
and how to interpret merged reports.
- scripts/build-sub-pages.js
- renderTutorialsSidebar(): tutorial list with current-item marking
- renderTutorialsIndexMain(): card grid with mono order numbers,
italic display titles, taglines, arrow affordances
- renderTutorialDetail(): breadcrumb eyebrow, italic display title,
plain tagline, rendered markdown body inside .prose
- Wire both into the main generator loop.
- sub-pages-data.js already reads content/site/tutorials/*.md and
sorts by frontmatter.order, so no data-layer change was needed.
- public/css/sub-pages.css
- .tutorial-card grid (number, body, arrow) with hover border
darken + arrow slide. Mono order numbers, italic display titles.
- .tutorial-detail header with italic display title and plain sans
tagline (matching the visual rhythm of skill detail pages without
the mono command style since tutorials are prose, not commands).
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
---
|
||||
title: Critique with the visual overlay
|
||||
tagline: "Use /critique plus the browser overlay to review a live page with ground truth."
|
||||
order: 2
|
||||
description: "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."
|
||||
---
|
||||
|
||||
## What you'll build
|
||||
|
||||
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.
|
||||
|
||||
Total time: about ten minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Impeccable installed in your project (see [getting started](/tutorials/getting-started) if you have not).
|
||||
- A harness with browser automation available (Claude Code with the Chrome extension, or similar).
|
||||
- A page you want to critique, either local (`localhost:3000/pricing`) or deployed.
|
||||
|
||||
## Step 1. Run /critique
|
||||
|
||||
From your harness, run:
|
||||
|
||||
```
|
||||
/critique the pricing page at localhost:3000/pricing
|
||||
```
|
||||
|
||||
The skill kicks off two independent assessments in parallel. Do not skip the "independent" part. They are in separate sub-agents or separate tabs so one does not bias the other.
|
||||
|
||||
### What the LLM assessment does
|
||||
|
||||
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'T catalog and scores the page against Nielsen's 10 heuristics, the 8-item cognitive load checklist, and the brand fit from your `.impeccable.md`.
|
||||
|
||||
It labels the tab it opens with `[LLM]` in the title so you can tell which one is which.
|
||||
|
||||
### What the automated detector does
|
||||
|
||||
The second assessment runs `npx impeccable detect` against the page. This is deterministic: 25 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.
|
||||
|
||||
You get back a JSON list of every finding with its element selector, the rule that fired, and a short description.
|
||||
|
||||
## Step 2. Open the visual overlay
|
||||
|
||||
Impeccable ships with a visual mode that highlights every detected anti-pattern directly on the page. You have three ways to run it:
|
||||
|
||||
1. **Chrome extension (coming soon)**: one-click activation on any page, yours or anyone's.
|
||||
2. **Inside `/critique`**: the skill automatically opens the overlay during the browser portion of the assessment.
|
||||
3. **Standalone CLI**: `npx impeccable live` starts a local overlay server, then you paste the URL you want to inspect.
|
||||
|
||||
For this tutorial we will use the standalone CLI so you can see it without depending on the extension.
|
||||
|
||||
In a new terminal:
|
||||
|
||||
```
|
||||
npx impeccable live
|
||||
```
|
||||
|
||||
This starts a server on `localhost:5199`. Open it. Paste the URL of your pricing page. The page loads inside an iframe with the detector script injected. Every flagged anti-pattern gets an outline and a floating label naming the rule.
|
||||
|
||||
## Step 3. Merge the two assessments
|
||||
|
||||
Back in your harness, `/critique` has finished and produced a combined report. It looks something like:
|
||||
|
||||
```
|
||||
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?
|
||||
```
|
||||
|
||||
## Step 4. Fix the findings in order
|
||||
|
||||
Do not try to fix everything at once. The report gives you a priority list. Work through it top to bottom.
|
||||
|
||||
For each issue:
|
||||
|
||||
1. Keep the overlay open in one tab.
|
||||
2. Make the fix in code.
|
||||
3. Reload. The overlay re-scans and the label for that rule should disappear.
|
||||
4. Move to the next.
|
||||
|
||||
This loop (fix, reload, verify, next) is the reason the overlay matters. You see your fixes land in real time, and you never ship a "fix" that did not actually satisfy the rule.
|
||||
|
||||
## Step 5. Re-run /critique when you are done
|
||||
|
||||
After you have worked through the priority list, run `/critique` 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.
|
||||
|
||||
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).
|
||||
|
||||
## What to try next
|
||||
|
||||
- `/audit the same page` to catch the implementation issues critique does not cover (accessibility, performance, theming).
|
||||
- `/polish` if the critique report is clean and you want the last-mile refinement pass.
|
||||
- `/distill` if critique flagged "too busy" or "cognitive load". Distill removes what should not be there.
|
||||
|
||||
## Common issues
|
||||
|
||||
- **The overlay shows no findings but critique says there are problems**. The detector catches deterministic patterns. Critique catches judgment calls. They are complementary, not redundant.
|
||||
- **The LLM assessment and the detector disagree**. That is normal. The LLM is subjective. The detector is deterministic. When they disagree, look at both and make a call.
|
||||
- **The overlay breaks the page layout**. The overlay runs inside an iframe. Some pages with strict frame-ancestors CSP will not load. Use the Chrome extension in that case (once it ships) or run the detector from the CLI and apply findings manually.
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: Getting started
|
||||
tagline: "From zero to your first /polish pass in five minutes."
|
||||
order: 1
|
||||
description: "Install Impeccable, run /impeccable teach once to establish project context, and run /polish on something that already exists. The fastest path to seeing what Impeccable changes about AI-generated design."
|
||||
---
|
||||
|
||||
## What you'll build
|
||||
|
||||
You will end this tutorial with Impeccable installed in your project, a `.impeccable.md` file that captures your brand and audience, and one hand-polished page that went through a `/polish` pass. Total time: about five minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An AI coding harness: Claude Code, Cursor, Gemini CLI, Codex CLI, or any of the other supported tools.
|
||||
- A project with at least one HTML or component file you want to improve. A fresh scaffolded landing page works fine.
|
||||
|
||||
## Step 1. Install
|
||||
|
||||
From the root of your project, run:
|
||||
|
||||
```
|
||||
npx skills add pbakaus/impeccable
|
||||
```
|
||||
|
||||
The installer auto-detects your harness and drops the skill files in the right place. It does not touch any existing code. You should see output like:
|
||||
|
||||
```
|
||||
✓ Detected Claude Code (.claude/skills/)
|
||||
✓ Installed 21 skills + impeccable foundation
|
||||
```
|
||||
|
||||
Start your harness (or reload it) and type `/`. You should see `/impeccable`, `/polish`, `/critique`, and the other commands in the autocomplete.
|
||||
|
||||
## Step 2. Teach Impeccable about your project
|
||||
|
||||
This is the most important step. Design without context produces generic output. The `/impeccable teach` command runs a short discovery interview and writes a `.impeccable.md` file at the root of your project.
|
||||
|
||||
Run:
|
||||
|
||||
```
|
||||
/impeccable teach
|
||||
```
|
||||
|
||||
The skill will ask you a handful of questions:
|
||||
|
||||
- **Who is this product for?** Be specific. Not "users" but "solo founders evaluating a new tool on their phone between meetings".
|
||||
- **What is the brand voice in three words?** Pick real words. "Warm and mechanical and opinionated" is better than "modern and clean".
|
||||
- **What should the interface feel like?** Concrete adjectives. "Calm, trustworthy, fast" or "playful, bold, a little chaotic".
|
||||
- **Any visual references?** Screenshots, sites, design systems you admire.
|
||||
- **Anti-references?** Things the product should explicitly not look like.
|
||||
|
||||
Answer in your own words. The skill writes a `.impeccable.md` file with the answers. Every future skill call reads it automatically.
|
||||
|
||||
Open `.impeccable.md` and read what it wrote. Edit anything that does not feel right. The file is yours.
|
||||
|
||||
## Step 3. Polish something
|
||||
|
||||
Pick a page that already exists. An about page, a settings screen, a pricing table, anything. Run:
|
||||
|
||||
```
|
||||
/polish the pricing page
|
||||
```
|
||||
|
||||
The skill will walk through alignment, spacing, typography, color, interaction states, transitions, and copy. It makes targeted fixes, not a rewrite. Expect a handful of small diffs that together lift the page from "done" to "done well".
|
||||
|
||||
A typical polish pass looks like:
|
||||
|
||||
```
|
||||
Visual alignment: fixed 3 off-grid elements
|
||||
Typography: tightened h1 kerning, fixed widow on feature list
|
||||
Color: replaced one hardcoded hex with --color-accent token
|
||||
Interaction: added missing hover state on FAQ items
|
||||
Motion: softened modal entrance to 220ms ease-out-quart
|
||||
Copy: removed stray 'Lorem' placeholder
|
||||
```
|
||||
|
||||
Review the diff. If something does not feel right, ask the model to explain the change. If it still does not feel right, revert it. Impeccable is opinionated but not infallible.
|
||||
|
||||
## What to try next
|
||||
|
||||
- `/critique the landing page` runs a full design review with scoring, persona tests, and automated detection. It is the best way to find what to fix next.
|
||||
- `/audit the checkout` runs accessibility, performance, theming, responsive, and anti-pattern checks against the implementation. Useful before shipping.
|
||||
- `/impeccable craft a pricing page for enterprise customers` runs the full shape-then-build flow on a brand new feature.
|
||||
|
||||
## Common issues
|
||||
|
||||
- **The skill says "no design context found"**. You skipped step 2. Run `/impeccable teach` first.
|
||||
- **Commands do not appear in the harness**. Make sure your harness supports skills and has skills enabled. Cursor needs Nightly channel plus Agent Skills in Settings. Gemini CLI needs the preview version.
|
||||
- **The polish pass rewrote something you liked**. Say so. Revert the change, tell the model which specific edit to undo, and continue from there.
|
||||
@@ -781,6 +781,120 @@ main#main {
|
||||
content: " →";
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TUTORIALS INDEX
|
||||
============================================ */
|
||||
|
||||
.tutorials-content {
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.tutorial-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.tutorial-card {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
gap: var(--spacing-md);
|
||||
align-items: center;
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
background: var(--color-paper);
|
||||
border: 1px solid var(--color-mist);
|
||||
border-radius: 10px;
|
||||
color: var(--color-ink);
|
||||
text-decoration: none;
|
||||
transition: border-color var(--duration-fast) var(--ease-out),
|
||||
transform var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.tutorial-card:hover {
|
||||
border-color: var(--color-ink);
|
||||
}
|
||||
|
||||
.tutorial-card-number {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-ash);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.tutorial-card:hover .tutorial-card-number {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.tutorial-card-body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tutorial-card-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.375rem;
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
color: var(--color-ink);
|
||||
line-height: 1.2;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tutorial-card-tagline {
|
||||
font-size: 0.9375rem;
|
||||
color: var(--color-charcoal);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.tutorial-card-arrow {
|
||||
font-family: var(--font-body);
|
||||
font-size: 1.25rem;
|
||||
color: var(--color-ash);
|
||||
transition: color var(--duration-fast) var(--ease-out),
|
||||
transform var(--duration-fast) var(--ease-out);
|
||||
}
|
||||
|
||||
.tutorial-card:hover .tutorial-card-arrow {
|
||||
color: var(--color-accent);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
TUTORIAL DETAIL
|
||||
============================================ */
|
||||
|
||||
.tutorial-detail {
|
||||
max-width: 680px;
|
||||
}
|
||||
|
||||
.tutorial-detail-header {
|
||||
margin-bottom: clamp(2.5rem, 5vw, 3.5rem);
|
||||
}
|
||||
|
||||
.tutorial-detail-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(2.5rem, 5vw, 4rem);
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--color-ink);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.tutorial-detail-tagline {
|
||||
font-family: var(--font-body);
|
||||
font-size: clamp(1.0625rem, 1.5vw, 1.1875rem);
|
||||
line-height: 1.55;
|
||||
color: var(--color-charcoal);
|
||||
max-width: 60ch;
|
||||
}
|
||||
|
||||
.tutorial-detail-body {
|
||||
max-width: 65ch;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
MOBILE: collapse sidebar into inline block
|
||||
============================================ */
|
||||
|
||||
@@ -290,6 +290,83 @@ function renderRuleCard(rule) {
|
||||
</article>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the tutorials sidebar: list of tutorial pages in order, with
|
||||
* the current tutorial marked via aria-current.
|
||||
*/
|
||||
function renderTutorialsSidebar(tutorials, currentSlug = null) {
|
||||
const entries = tutorials
|
||||
.map((t) => {
|
||||
const current = t.slug === currentSlug ? ' aria-current="page"' : '';
|
||||
return ` <li><a href="/tutorials/${t.slug}"${current}>${escapeHtml(t.title)}</a></li>`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return `
|
||||
<aside class="skills-sidebar tutorials-sidebar" aria-label="Tutorials">
|
||||
<div class="skills-sidebar-inner">
|
||||
<p class="skills-sidebar-label">Tutorials</p>
|
||||
<div class="skills-sidebar-group">
|
||||
<p class="skills-sidebar-group-title">Walk-throughs</p>
|
||||
<ul class="skills-sidebar-list">
|
||||
${entries}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</aside>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the /tutorials index main content.
|
||||
*/
|
||||
function renderTutorialsIndexMain(tutorials) {
|
||||
const cards = tutorials
|
||||
.map(
|
||||
(t) => `
|
||||
<a class="tutorial-card" href="/tutorials/${t.slug}">
|
||||
<span class="tutorial-card-number">${String(t.order).padStart(2, '0')}</span>
|
||||
<div class="tutorial-card-body">
|
||||
<h2 class="tutorial-card-title">${escapeHtml(t.title)}</h2>
|
||||
<p class="tutorial-card-tagline">${escapeHtml(t.tagline || t.description)}</p>
|
||||
</div>
|
||||
<span class="tutorial-card-arrow">→</span>
|
||||
</a>`,
|
||||
)
|
||||
.join('\n');
|
||||
|
||||
return `
|
||||
<div class="tutorials-content">
|
||||
<header class="sub-page-header">
|
||||
<p class="sub-page-eyebrow">${tutorials.length} walk-throughs</p>
|
||||
<h1 class="sub-page-title">Tutorials</h1>
|
||||
<p class="sub-page-lede">Short, opinionated walk-throughs of the highest-leverage workflows. Each one takes around ten minutes and ends with something working in your project.</p>
|
||||
</header>
|
||||
|
||||
<div class="tutorial-cards">
|
||||
${cards}
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a tutorial detail page main content.
|
||||
*/
|
||||
function renderTutorialDetail(tutorial, knownSkillIds) {
|
||||
const bodyHtml = renderMarkdown(tutorial.body, { knownSkillIds });
|
||||
return `
|
||||
<article class="tutorial-detail">
|
||||
<header class="tutorial-detail-header">
|
||||
<p class="skill-detail-eyebrow"><a href="/tutorials">Tutorials</a> / ${String(tutorial.order).padStart(2, '0')}</p>
|
||||
<h1 class="tutorial-detail-title">${escapeHtml(tutorial.title)}</h1>
|
||||
${tutorial.tagline ? `<p class="tutorial-detail-tagline">${escapeHtml(tutorial.tagline)}</p>` : ''}
|
||||
</header>
|
||||
|
||||
<section class="tutorial-detail-body prose">
|
||||
${bodyHtml}
|
||||
</section>
|
||||
</article>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the /anti-patterns main column content.
|
||||
*/
|
||||
@@ -407,5 +484,39 @@ export async function generateSubPages(rootDir) {
|
||||
generated.push(out);
|
||||
}
|
||||
|
||||
// Tutorials index.
|
||||
if (data.tutorials.length > 0) {
|
||||
const sidebar = renderTutorialsSidebar(data.tutorials, null);
|
||||
const main = renderTutorialsIndexMain(data.tutorials);
|
||||
const html = renderPage({
|
||||
title: 'Tutorials | Impeccable',
|
||||
description: `${data.tutorials.length} short, opinionated walk-throughs of the highest-leverage Impeccable workflows.`,
|
||||
bodyHtml: wrapInDocsLayout(sidebar, main),
|
||||
activeNav: 'tutorials',
|
||||
canonicalPath: '/tutorials',
|
||||
bodyClass: 'sub-page skills-layout-page tutorials-page',
|
||||
});
|
||||
const out = path.join(outDirs.tutorials, 'index.html');
|
||||
fs.writeFileSync(out, html, 'utf-8');
|
||||
generated.push(out);
|
||||
}
|
||||
|
||||
// Tutorial detail pages.
|
||||
for (const tutorial of data.tutorials) {
|
||||
const sidebar = renderTutorialsSidebar(data.tutorials, tutorial.slug);
|
||||
const main = renderTutorialDetail(tutorial, data.knownSkillIds);
|
||||
const html = renderPage({
|
||||
title: `${tutorial.title} | Tutorials | Impeccable`,
|
||||
description: tutorial.description || tutorial.tagline || '',
|
||||
bodyHtml: wrapInDocsLayout(sidebar, main),
|
||||
activeNav: 'tutorials',
|
||||
canonicalPath: `/tutorials/${tutorial.slug}`,
|
||||
bodyClass: 'sub-page skills-layout-page tutorials-page',
|
||||
});
|
||||
const out = path.join(outDirs.tutorials, `${tutorial.slug}.html`);
|
||||
fs.writeFileSync(out, html, 'utf-8');
|
||||
generated.push(out);
|
||||
}
|
||||
|
||||
return { files: generated };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user