/**
* Manual metadata for the /anti-patterns page.
*
* The detection rules themselves live in cli/engine/registry/antipatterns.mjs.
* This file adds three pieces of content that
* can't be automated:
*
* 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
* 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 by ~120px height.
*
* 3. LLM_ONLY_RULES: DON'T lines from skill/SKILL.src.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.
*/
// ─── Detection layers ────────────────────────────────────────────────
/**
* 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.
*
* Per tests/detect-antipatterns-browser.test.mjs: only two rules genuinely
* need real browser layout. Everything else is 'cli'.
*/
export const DETECTION_LAYERS = {
'side-tab': 'cli',
'border-accent-on-rounded': 'cli',
'overused-font': 'cli',
'single-font': 'cli',
'flat-type-hierarchy': 'cli',
'icon-tile-stack': 'cli',
'gradient-text': 'cli',
'ai-color-palette': 'cli',
'dark-glow': 'cli',
'nested-cards': 'cli',
'monotonous-spacing': 'cli',
'everything-centered': 'cli',
'bounce-easing': 'cli',
'all-caps-body': 'cli',
'pure-black-white': 'cli',
'gray-on-color': 'cli',
'low-contrast': 'cli',
'layout-transition': 'cli',
'tight-leading': 'cli',
'skipped-heading': 'cli',
'justified-text': 'cli',
'tiny-text': 'cli',
'wide-tracking': 'cli',
// Browser-only: need real layout measurements.
'cramped-padding': 'browser',
'line-length': 'browser',
};
export const LAYER_LABELS = {
cli: 'CLI',
browser: 'Browser',
llm: 'LLM only',
};
export const LAYER_DESCRIPTIONS = {
cli: 'Deterministic. Runs from `npx impeccable detect` on files, no browser required.',
browser: 'Deterministic, but needs real browser layout. Runs via the browser extension or Puppeteer, not the plain CLI.',
llm: 'Not caught by any deterministic detector. Flagged by /impeccable critique during its LLM design review.',
};
// ─── Visual examples ─────────────────────────────────────────────────
/**
* One tiny inline HTML snippet per rule showing what the bad pattern
* looks like. Snippets use inline styles only and are sized to fit the
* rule card preview area (~100% wide, ~120px tall).
*/
export const VISUAL_EXAMPLES = {
'side-tab': `
Alert title
Thick colored stripe on one side.
`,
'border-accent-on-rounded': `
Rounded card
Thick colored border clashes with the radius.
`,
'overused-font': `
Just another Inter headline
Every SaaS homepage looks like this.
`,
'single-font': `
Heading in the body font
Body in the same font. No contrast. Flat.
`,
'flat-type-hierarchy': `
Heading
Subheading
Body text at almost the same size.
`,
'icon-tile-stack': `
✦
Feature name
Rounded icon tile above heading.
`,
'gradient-text': `
Build the Future
Gradient text kills scannability.
`,
'ai-color-palette': `
`,
'dark-glow': `
Neon on dark
Cyberpunk-by-default slop.
`,
'nested-cards': `
Card inside card inside card.
`,
'monotonous-spacing': `
`,
'everything-centered': `
Centered headline
Everything centered by default.
Call to action
`,
'bounce-easing': `
Bounce + elastic easing feels dated.
`,
'all-caps-body': `
Long passages in uppercase are hard to read. We recognize words by their shape, which all-caps removes.
`,
'pure-black-white': `
Pure black on pure white
Neither exists in nature. Always tint.
`,
'gray-on-color': `
Gray text on a colored background. Washed out and hard to read.
`,
'low-contrast': `
Light gray text on a white background. 1.6:1 contrast, fails WCAG.
`,
'layout-transition': `
Animating width/height causes layout jank.
`,
'cramped-padding': `
2px vertical padding.
`,
'tight-leading': `
Tight leading makes multi-line body text feel crammed and hard for the eye to track between lines.
`,
'skipped-heading': `
Page title (h1)
Subsection (h3), skipped h2
`,
'justified-text': `
Justified text on screens creates rivers of whitespace because browsers can't hyphenate well. Leave this for print.
`,
'tiny-text': `
Regular body text
And then fine print at 9 pixels that no one will ever read.
`,
'wide-tracking': `
Wide tracking on body text slows reading by breaking up natural character groupings.
`,
'line-length': `
Paragraphs wider than roughly 75 characters per line become fatiguing because the eye has to track an excessive distance back to the start of the next line, losing its place.
This is the overview section, which provides an overview of the overview.
`,
'mobile-amputation': `
Export to CSV
"Not available on mobile."
`,
};
/**
* Anti-patterns that live in the /impeccable skill's DON'T list but
* don't have a deterministic detector. These can only be caught by
* /critique running an LLM assessment pass.
*
* Each entry looks like a detection rule: id, category, name,
* description, skillSection. The generator merges these into the
* grouped sections alongside detected rules with an 'llm' layer badge.
*/
// ─── Gallery: real examples in the wild ──────────────────────────────
/**
* Curated real-world examples of anti-patterns caught in the wild.
* Each entry maps to:
* - public/antipattern-images/{id}.png (preview thumbnail)
* - public/antipattern-examples/{id}.html (standalone live example)
* Rendered as a dedicated section on the /anti-patterns page, replacing
* the old /gallery route which was confusingly labeled in the top nav.
*/
export const GALLERY_ITEMS = [
{
id: 'purple-gradients',
title: 'Purple Gradients Everywhere',
desc:
'The AI color palette: purple-to-blue gradients on everything. Buttons, text, backgrounds, orbs. The new "make it pop."',
},
{
id: 'lazy-cool',
title: 'Lazy "Cool"',
desc:
'Glassmorphism, neon glows, blurred orbs, monospace everything. Looks like a hackathon project, not a product.',
},
{
id: 'lazy-impact',
title: 'Lazy "Impact"',
desc:
'When in doubt, animate everything. Bouncing buttons, wiggling icons, gradient text, floating badges. Motion without meaning.',
},
{
id: 'thick-border-cards',
title: 'Side-Tab Cards',
desc:
'A thick colored border on one side of a rounded card. The single most recognizable tell of AI-generated UI.',
},
{
id: 'cardocalypse',
title: 'Cardocalypse',
desc:
'Cards inside cards inside cards. Five levels of nesting, each with its own padding and shadow.',
},
{
id: 'layout-templates',
title: 'Copy-Paste Layouts',
desc:
'The same hero-metric-features template repeated with different colors. When every section looks the same, nothing stands out.',
},
{
id: 'inter-everywhere',
title: 'Inter Everywhere',
desc:
'One font for everything. Headings, body, labels, buttons. No typographic hierarchy, no personality, no design.',
},
{
id: 'massive-icons',
title: 'Massive Icons',
desc:
'Icon containers larger than the content they introduce. When the decoration is bigger than the message, priorities are backwards.',
},
{
id: 'bad-contrast',
title: 'Bad Contrast Choices',
desc:
'Gray text on colored backgrounds, low-contrast labels, unreadable combinations. Looking good and being readable should not conflict.',
},
{
id: 'redundant-ux-writing',
title: 'Redundant UX Writing',
desc:
'Label, sublabel, helper text, and hint text all saying the same thing in slightly different words. Say it once, say it well.',
},
{
id: 'modal-abuse',
title: 'Modal Abuse',
desc:
'Complex settings crammed into a modal. If it needs a scroll bar and three columns, it deserves its own page.',
},
];
// ─── LLM-only rules ──────────────────────────────────────────────────
export const LLM_ONLY_RULES = [
{
id: 'monospace-as-technical',
category: 'slop',
name: 'Monospace as "technical" shorthand',
description:
'Using a monospace typeface to signal "developer / technical" vibes. Reach for real type choices instead of a lazy stereotype.',
skillSection: 'Typography',
},
{
id: 'dark-mode-default',
category: 'slop',
name: 'Defaulting to dark mode for "safety"',
description:
'Defaulting to light mode to be safe is the inverse of defaulting to dark mode to look cool. Either way you are retreating from a decision.',
skillSection: 'Color & Contrast',
},
{
id: 'everything-in-cards',
category: 'slop',
name: 'Wrapping everything in cards',
description:
'Not every piece of content needs a bordered container. Spacing and alignment create visual grouping without the overhead of a card.',
skillSection: 'Layout & Space',
},
{
id: 'identical-card-grids',
category: 'slop',
name: 'Identical card grids',
description:
'Same-sized cards with icon + heading + text repeated endlessly. The default AI homepage layout.',
skillSection: 'Layout & Space',
},
{
id: 'hero-metric-layout',
category: 'slop',
name: 'Hero metric layout',
description:
'Big number, small label, three supporting stats, gradient accent. Used everywhere, trusted nowhere.',
skillSection: 'Layout & Space',
},
{
id: 'glassmorphism',
category: 'slop',
name: 'Glassmorphism everywhere',
description:
'Blur effects, glass cards, and glow borders used as decoration rather than to solve a real layering problem.',
skillSection: 'Visual Details',
},
{
id: 'sparkline-decoration',
category: 'slop',
name: 'Sparklines as decoration',
description:
'Tiny charts that look sophisticated but convey no meaningful information. If the data matters, give it room.',
skillSection: 'Visual Details',
},
{
id: 'generic-drop-shadows',
category: 'slop',
name: 'Rounded rectangles with generic drop shadows',
description:
'The safest, most forgettable shape on the web. Could be the output of any AI. Commit to a stronger visual treatment.',
skillSection: 'Visual Details',
},
{
id: 'modal-reflex',
category: 'slop',
name: 'Reaching for modals by reflex',
description:
'Modals interrupt the user and are lazy as a design default. Use them only when there is truly no better place for the interaction.',
skillSection: 'Visual Details',
},
{
id: 'every-button-primary',
category: 'quality',
name: 'Every button is a primary button',
description:
'When every button looks equally important, nothing reads as the primary action. Use ghost buttons, text links, and secondary styles to build hierarchy.',
skillSection: 'Interaction',
},
{
id: 'redundant-headers',
category: 'quality',
name: 'Redundant information',
description:
'Intros that restate the heading. Section labels that repeat the page title. Cards that echo their own caption. Make every word earn its place.',
skillSection: 'Interaction',
},
{
id: 'mobile-amputation',
category: 'quality',
name: 'Amputating features on mobile',
description:
'Hiding critical functionality on mobile because it is inconvenient. Adapt the interface to the context, do not strip it.',
skillSection: 'Responsive',
},
];