mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-15 23:56:29 +03:00
Consolidates eval-side detection logic into the canonical impeccable
detector. Before this change, the eval harness had its own duplicate
implementations of em-dash, copy-slop, and broken-image checks. They
now live alongside the existing 28 antipatterns in the impeccable
registry, available to the CLI, browser extension, critique skill,
and eval (via the existing slop grader child-process call).
New antipatterns:
- em-dash-overuse: 5+ em-dashes in body text content (threshold
permits legitimate prose use of em-dash; only triggers on AI
cadence-level density)
- broken-image: <img> with empty src, missing src, or src="#"
- marketing-buzzword: SaaS phrase list (streamline / empower /
supercharge / enterprise-grade / cutting-edge / etc)
- numbered-section-markers: repeated 01 / 02 / 03 sequence as
section labels — the AI editorial scaffold one tier deeper than
tracked eyebrow chips
- aphoristic-cadence: 3+ manufactured-contrast ("Not a X. A Y.")
or short-rebuttal ("Sentence. No clause." / "Sentence. Just
clause.") constructions in body text
Engine wiring:
- broken-image runs as a static-html element rule (selector: img)
and a fallback regex matcher (for non-HTML files)
- em-dash / buzzword / numbered / aphoristic run as regex
page-analyzers, factored into a new runTextContentAnalyzers()
helper that both detectText (non-HTML) and detectHtml (HTML)
call, so .html files get the same coverage as .css/.tsx
Tests: 166 detector + 12 browser + 24 fixture all pass.
Browser detector rebuilt (162.7 KB).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
316 lines
12 KiB
JavaScript
316 lines
12 KiB
JavaScript
const ANTIPATTERNS = [
|
|
// ── AI slop: tells that something was AI-generated ──
|
|
{
|
|
id: 'side-tab',
|
|
category: 'slop',
|
|
name: 'Side-tab accent border',
|
|
description:
|
|
'Thick colored border on one side of a card — the most recognizable tell of AI-generated UIs. Use a subtler accent or remove it entirely.',
|
|
skillSection: 'Visual Details',
|
|
skillGuideline: 'colored accent stripe',
|
|
},
|
|
{
|
|
id: 'border-accent-on-rounded',
|
|
category: 'slop',
|
|
name: 'Border accent on rounded element',
|
|
description:
|
|
'Thick accent border on a rounded card — the border clashes with the rounded corners. Remove the border or the border-radius.',
|
|
skillSection: 'Visual Details',
|
|
skillGuideline: 'colored accent stripe',
|
|
},
|
|
{
|
|
id: 'overused-font',
|
|
category: 'slop',
|
|
name: 'Overused font',
|
|
description:
|
|
'Inter, Roboto, Fraunces, Geist, Plus Jakarta Sans, and Space Grotesk are used on so many sites they no longer feel distinctive. Each new wave of AI-generated UIs converges on the same handful of faces. Choose a face that gives your interface personality.',
|
|
skillSection: 'Typography',
|
|
skillGuideline: 'overused fonts like Inter',
|
|
},
|
|
{
|
|
id: 'single-font',
|
|
category: 'slop',
|
|
name: 'Single font for everything',
|
|
description:
|
|
'Only one font family is used for the entire page. Pair a distinctive display font with a refined body font to create typographic hierarchy.',
|
|
skillSection: 'Typography',
|
|
skillGuideline: 'only one font family for the entire page',
|
|
},
|
|
{
|
|
id: 'flat-type-hierarchy',
|
|
category: 'slop',
|
|
name: 'Flat type hierarchy',
|
|
description:
|
|
'Font sizes are too close together — no clear visual hierarchy. Use fewer sizes with more contrast (aim for at least a 1.25 ratio between steps).',
|
|
skillSection: 'Typography',
|
|
skillGuideline: 'flat type hierarchy',
|
|
},
|
|
{
|
|
id: 'gradient-text',
|
|
category: 'slop',
|
|
name: 'Gradient text',
|
|
description:
|
|
'Gradient text is decorative rather than meaningful — a common AI tell, especially on headings and metrics. Use solid colors for text.',
|
|
skillSection: 'Color & Contrast',
|
|
skillGuideline: 'gradient text for',
|
|
},
|
|
{
|
|
id: 'ai-color-palette',
|
|
category: 'slop',
|
|
name: 'AI color palette',
|
|
description:
|
|
'Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.',
|
|
skillSection: 'Color & Contrast',
|
|
skillGuideline: 'AI color palette',
|
|
},
|
|
{
|
|
id: 'nested-cards',
|
|
category: 'slop',
|
|
name: 'Nested cards',
|
|
description:
|
|
'Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.',
|
|
skillSection: 'Layout & Space',
|
|
skillGuideline: 'Nest cards inside cards',
|
|
},
|
|
{
|
|
id: 'monotonous-spacing',
|
|
category: 'slop',
|
|
name: 'Monotonous spacing',
|
|
description:
|
|
'The same spacing value used everywhere — no rhythm, no variation. Use tight groupings for related items and generous separations between sections.',
|
|
skillSection: 'Layout & Space',
|
|
skillGuideline: 'same spacing everywhere',
|
|
},
|
|
{
|
|
id: 'everything-centered',
|
|
category: 'slop',
|
|
name: 'Everything centered',
|
|
description:
|
|
'Every text element is center-aligned. Left-aligned text with asymmetric layouts feels more designed. Center only hero sections and CTAs.',
|
|
skillSection: 'Layout & Space',
|
|
skillGuideline: 'Center everything',
|
|
},
|
|
{
|
|
id: 'bounce-easing',
|
|
category: 'slop',
|
|
name: 'Bounce or elastic easing',
|
|
description:
|
|
'Bounce and elastic easing feel dated and tacky. Real objects decelerate smoothly — use exponential easing (ease-out-quart/quint/expo) instead.',
|
|
skillSection: 'Motion',
|
|
skillGuideline: 'bounce or elastic easing',
|
|
},
|
|
{
|
|
id: 'dark-glow',
|
|
category: 'slop',
|
|
name: 'Dark mode with glowing accents',
|
|
description:
|
|
'Dark backgrounds with colored box-shadow glows are the default "cool" look of AI-generated UIs. Use subtle, purposeful lighting instead — or skip the dark theme entirely.',
|
|
skillSection: 'Color & Contrast',
|
|
skillGuideline: 'dark mode with glowing accents',
|
|
},
|
|
{
|
|
id: 'icon-tile-stack',
|
|
category: 'slop',
|
|
name: 'Icon tile stacked above heading',
|
|
description:
|
|
'A small rounded-square icon container above a heading is the universal AI feature-card template — every generator outputs this exact shape. Try a side-by-side icon and heading, or let the icon sit in flow without its own container.',
|
|
skillSection: 'Typography',
|
|
skillGuideline: 'large icons with rounded corners above every heading',
|
|
},
|
|
{
|
|
id: 'italic-serif-display',
|
|
category: 'slop',
|
|
name: 'Italic serif display headline',
|
|
description:
|
|
'Oversized italic serif (Fraunces, Recoleta, Playfair, Newsreader-italic) as the primary hero headline reads as taste in isolation but has become the universal AI-startup landing page hero. Set roman, or move to a non-serif display face. Editorial / magazine register may legitimately want this — judge by context.',
|
|
skillSection: 'Typography',
|
|
skillGuideline: 'oversized italic serif as the hero headline',
|
|
},
|
|
{
|
|
id: 'hero-eyebrow-chip',
|
|
category: 'slop',
|
|
name: 'Hero eyebrow / pill chip',
|
|
description:
|
|
'A tiny uppercase letter-spaced label sitting immediately above an oversized hero headline — or the same shape rendered as a pill chip — is now the default AI SaaS hero. Drop the eyebrow, integrate the kicker into the headline, or run it as a navigation breadcrumb instead.',
|
|
skillSection: 'Typography',
|
|
skillGuideline: 'tiny uppercase tracked label above the hero headline',
|
|
},
|
|
{
|
|
id: 'repeated-section-kickers',
|
|
category: 'slop',
|
|
severity: 'advisory',
|
|
name: 'Repeated section kicker labels',
|
|
description:
|
|
'Repeating tiny uppercase tracked labels above section headings turns a brand page into AI editorial scaffolding. Replace them with stronger structure, artifacts, imagery, or a deliberate brand system.',
|
|
skillSection: 'Typography',
|
|
skillGuideline: 'repeated eyebrow or kicker labels as section scaffolding',
|
|
},
|
|
{
|
|
id: 'numbered-section-markers',
|
|
category: 'slop',
|
|
severity: 'advisory',
|
|
name: 'Numbered section markers (01 / 02 / 03)',
|
|
description:
|
|
'Numbered display markers as section labels (01, 02, 03) are the AI editorial scaffold one tier deeper than tracked eyebrow chips. If you find yourself reaching for them, choose a different section cadence.',
|
|
skillSection: 'Layout & Space',
|
|
skillGuideline: 'numbered section markers',
|
|
},
|
|
{
|
|
id: 'em-dash-overuse',
|
|
category: 'slop',
|
|
name: 'Em-dash overuse',
|
|
description:
|
|
'More than two em-dashes (— or --) in body copy is an AI cadence tell. Use commas, colons, periods, or parentheses instead.',
|
|
skillSection: 'Copy',
|
|
skillGuideline: 'no em dashes',
|
|
},
|
|
{
|
|
id: 'marketing-buzzword',
|
|
category: 'slop',
|
|
name: 'Marketing buzzword',
|
|
description:
|
|
'Generic SaaS phrases (streamline / empower / supercharge / world-class / enterprise-grade / next-generation / cutting-edge / etc) are instant AI tells. Pick a specific verb and noun that says what the product literally does.',
|
|
skillSection: 'Copy',
|
|
skillGuideline: 'marketing buzzwords',
|
|
},
|
|
{
|
|
id: 'aphoristic-cadence',
|
|
category: 'slop',
|
|
name: 'Aphoristic-cadence copy',
|
|
description:
|
|
'Three or more sections landing on a short rebuttal sentence ("X. No Y." / "X. Just Y.") or a manufactured-contrast aphorism ("Not a feature. A platform.") reads as AI cadence, not voice. Once is fine; the pattern is the tell.',
|
|
skillSection: 'Copy',
|
|
skillGuideline: 'aphoristic cadence',
|
|
},
|
|
{
|
|
id: 'broken-image',
|
|
category: 'quality',
|
|
name: 'Broken or placeholder image',
|
|
description:
|
|
'<img> tags with empty src, missing src, or placeholder values ship as broken-image boxes. Use real images, generated assets, or remove the tag.',
|
|
skillSection: 'Imagery',
|
|
skillGuideline: 'broken image references',
|
|
},
|
|
|
|
// ── Quality: general design and accessibility issues ──
|
|
{
|
|
id: 'gray-on-color',
|
|
category: 'quality',
|
|
name: 'Gray text on colored background',
|
|
description:
|
|
'Gray text looks washed out on colored backgrounds. Use a darker shade of the background color instead, or white/near-white for contrast.',
|
|
skillSection: 'Color & Contrast',
|
|
skillGuideline: 'gray text on colored backgrounds',
|
|
},
|
|
{
|
|
id: 'low-contrast',
|
|
category: 'quality',
|
|
name: 'Low contrast text',
|
|
description:
|
|
'Text does not meet WCAG AA contrast requirements (4.5:1 for body, 3:1 for large text). Increase the contrast between text and background.',
|
|
},
|
|
{
|
|
id: 'layout-transition',
|
|
category: 'quality',
|
|
name: 'Layout property animation',
|
|
description:
|
|
'Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.',
|
|
skillSection: 'Motion',
|
|
skillGuideline: 'Animate layout properties',
|
|
},
|
|
{
|
|
id: 'line-length',
|
|
category: 'quality',
|
|
name: 'Line length too long',
|
|
description:
|
|
'Text lines wider than ~80 characters are hard to read. The eye loses its place tracking back to the start of the next line. Add a max-width (65ch to 75ch) to text containers.',
|
|
skillSection: 'Layout & Space',
|
|
skillGuideline: 'wrap beyond ~80 characters',
|
|
},
|
|
{
|
|
id: 'cramped-padding',
|
|
category: 'quality',
|
|
name: 'Cramped padding',
|
|
description:
|
|
'Text is too close to the edge of its container. Add at least 8px (ideally 12-16px) of padding inside bordered or colored containers.',
|
|
},
|
|
{
|
|
id: 'body-text-viewport-edge',
|
|
category: 'quality',
|
|
name: 'Body text touching viewport edge',
|
|
description:
|
|
'Body paragraphs render flush against the left or right viewport edge with no container providing horizontal padding. Wrap content in a container with at least 16px (ideally 24-32px) of horizontal padding, or apply max-width with mx-auto.',
|
|
},
|
|
{
|
|
id: 'tight-leading',
|
|
category: 'quality',
|
|
name: 'Tight line height',
|
|
description:
|
|
'Line height below 1.3x the font size makes multi-line text hard to read. Use 1.5 to 1.7 for body text so lines have room to breathe.',
|
|
},
|
|
{
|
|
id: 'skipped-heading',
|
|
category: 'quality',
|
|
name: 'Skipped heading level',
|
|
description:
|
|
'Heading levels should not skip (e.g. h1 then h3 with no h2). Screen readers use heading hierarchy for navigation. Skipping levels breaks the document outline.',
|
|
},
|
|
{
|
|
id: 'justified-text',
|
|
category: 'quality',
|
|
name: 'Justified text',
|
|
description:
|
|
'Justified text without hyphenation creates uneven word spacing ("rivers of white"). Use text-align: left for body text, or enable hyphens: auto if you must justify.',
|
|
},
|
|
{
|
|
id: 'tiny-text',
|
|
category: 'quality',
|
|
name: 'Tiny body text',
|
|
description:
|
|
'Body text below 12px is hard to read, especially on high-DPI screens. Use at least 14px for body content, 16px is ideal.',
|
|
},
|
|
{
|
|
id: 'all-caps-body',
|
|
category: 'quality',
|
|
name: 'All-caps body text',
|
|
description:
|
|
'Long passages in uppercase are hard to read. We recognize words by shape (ascenders and descenders), which all-caps removes. Reserve uppercase for short labels and headings.',
|
|
skillSection: 'Typography',
|
|
skillGuideline: 'long body passages in uppercase',
|
|
},
|
|
{
|
|
id: 'wide-tracking',
|
|
category: 'quality',
|
|
name: 'Wide letter spacing on body text',
|
|
description:
|
|
'Letter spacing above 0.05em on body text disrupts natural character groupings and slows reading. Reserve wide tracking for short uppercase labels only.',
|
|
},
|
|
];
|
|
|
|
const RULE_ENGINE_SUPPORT = {
|
|
regex: new Set(['source', 'page-analyzer']),
|
|
'static-html': new Set(['element', 'page']),
|
|
browser: new Set(['element', 'page', 'layout']),
|
|
visual: new Set(['visual-contrast']),
|
|
};
|
|
|
|
function getAntipattern(id) {
|
|
return ANTIPATTERNS.find(rule => rule.id === id);
|
|
}
|
|
|
|
function getRulesForCategory(category) {
|
|
return ANTIPATTERNS.filter(rule => rule.category === category);
|
|
}
|
|
|
|
function getRuleEngineSupport(engine) {
|
|
return RULE_ENGINE_SUPPORT[engine] || new Set();
|
|
}
|
|
|
|
export {
|
|
ANTIPATTERNS,
|
|
RULE_ENGINE_SUPPORT,
|
|
getAntipattern,
|
|
getRulesForCategory,
|
|
getRuleEngineSupport,
|
|
};
|