anti-patterns-catalog.js

  1/**
  2 * Manual metadata for the /anti-patterns page.
  3 *
  4 * The detection rules themselves live in src/detect-antipatterns.mjs and
  5 * are parsed at build time. This file adds three pieces of content that
  6 * can't be automated:
  7 *
  8 *  1. DETECTION_LAYERS: which layer (cli, browser, or llm) catches the
  9 *     rule. Manually classified by reading the detector source and the
 10 *     browser-only test file.
 11 *
 12 *  2. VISUAL_EXAMPLES: a tiny inline HTML snippet showing what the
 13 *     bad pattern actually looks like. Rendered inside each rule card.
 14 *     Snippets should be self-contained with inline styles, use the
 15 *     cream/paper/ink palette when possible, and sit naturally at
 16 *     ~100% width by ~120px height.
 17 *
 18 *  3. LLM_ONLY_RULES: DON'T lines from source/skills/impeccable/SKILL.md
 19 *     that do not map to any detection rule. These can only be caught by
 20 *     the /critique skill's LLM pass. They appear on the /anti-patterns
 21 *     page alongside detected rules with an 'llm' layer badge.
 22 */
 23
 24// ─── Detection layers ────────────────────────────────────────────────
 25
 26/**
 27 * Which layer catches each rule.
 28 *
 29 *  'cli':     static analysis or jsdom (works with `npx impeccable detect`
 30 *             on files, no browser required)
 31 *  'browser': requires real browser layout (getBoundingClientRect with
 32 *             actual dimensions). Works via Puppeteer or the browser
 33 *             extension, NOT via the CLI on raw HTML.
 34 *  'llm':     no deterministic detector; only caught by /critique's LLM
 35 *             assessment pass.
 36 *
 37 * Per tests/detect-antipatterns-browser.test.mjs: only two rules genuinely
 38 * need real browser layout. Everything else is 'cli'.
 39 */
 40export const DETECTION_LAYERS = {
 41  'side-tab': 'cli',
 42  'border-accent-on-rounded': 'cli',
 43  'overused-font': 'cli',
 44  'single-font': 'cli',
 45  'flat-type-hierarchy': 'cli',
 46  'icon-tile-stack': 'cli',
 47  'gradient-text': 'cli',
 48  'ai-color-palette': 'cli',
 49  'dark-glow': 'cli',
 50  'nested-cards': 'cli',
 51  'monotonous-spacing': 'cli',
 52  'everything-centered': 'cli',
 53  'bounce-easing': 'cli',
 54  'all-caps-body': 'cli',
 55  'pure-black-white': 'cli',
 56  'gray-on-color': 'cli',
 57  'low-contrast': 'cli',
 58  'layout-transition': 'cli',
 59  'tight-leading': 'cli',
 60  'skipped-heading': 'cli',
 61  'justified-text': 'cli',
 62  'tiny-text': 'cli',
 63  'wide-tracking': 'cli',
 64  // Browser-only: need real layout measurements.
 65  'cramped-padding': 'browser',
 66  'line-length': 'browser',
 67};
 68
 69export const LAYER_LABELS = {
 70  cli: 'CLI',
 71  browser: 'Browser',
 72  llm: 'LLM only',
 73};
 74
 75export const LAYER_DESCRIPTIONS = {
 76  cli: 'Deterministic. Runs from `npx impeccable detect` on files, no browser required.',
 77  browser: 'Deterministic, but needs real browser layout. Runs via the browser extension or Puppeteer, not the plain CLI.',
 78  llm: 'Not caught by any deterministic detector. Flagged by /critique during its LLM design review.',
 79};
 80
 81// ─── Visual examples ─────────────────────────────────────────────────
 82
 83/**
 84 * One tiny inline HTML snippet per rule showing what the bad pattern
 85 * looks like. Snippets use inline styles only and are sized to fit the
 86 * rule card preview area (~100% wide, ~120px tall).
 87 */
 88export const VISUAL_EXAMPLES = {
 89  'side-tab': `<div style="background: #fff; border: 1px solid #e8e4df; border-left: 4px solid oklch(60% 0.22 265); border-radius: 6px; padding: 14px 16px; width: 220px; font-family: system-ui, sans-serif; font-size: 13px; color: #111;"><div style="font-weight: 600; margin-bottom: 4px;">Alert title</div><div style="color: #666; font-size: 12px;">Thick colored stripe on one side.</div></div>`,
 90
 91  'border-accent-on-rounded': `<div style="background: #fff; border: 2px solid oklch(60% 0.22 290); border-radius: 16px; padding: 14px 18px; width: 220px; font-family: system-ui, sans-serif; font-size: 13px; color: #111;"><div style="font-weight: 600;">Rounded card</div><div style="color: #666; font-size: 12px;">Thick colored border clashes with the radius.</div></div>`,
 92
 93  'overused-font': `<div style="font-family: Inter, system-ui, sans-serif; font-size: 15px; color: #111; line-height: 1.4;"><div style="font-weight: 600; margin-bottom: 4px;">Just another Inter headline</div><div style="color: #555; font-size: 13px;">Every SaaS homepage looks like this.</div></div>`,
 94
 95  'single-font': `<div style="font-family: system-ui, sans-serif; font-size: 14px; color: #111;"><div style="font-size: 19px; font-weight: 600; margin-bottom: 6px;">Heading in the body font</div><div style="color: #555;">Body in the same font. No contrast. Flat.</div></div>`,
 96
 97  'flat-type-hierarchy': `<div style="font-family: system-ui, sans-serif; color: #111; line-height: 1.3;"><div style="font-size: 17px; font-weight: 600;">Heading</div><div style="font-size: 16px; font-weight: 500; margin: 2px 0;">Subheading</div><div style="font-size: 15px; color: #555;">Body text at almost the same size.</div></div>`,
 98
 99  'icon-tile-stack': `<div style="font-family: system-ui, sans-serif; color: #111;"><div style="width: 44px; height: 44px; border-radius: 10px; background: linear-gradient(135deg, oklch(62% 0.22 265), oklch(70% 0.20 320)); display: flex; align-items: center; justify-content: center; font-size: 20px; color: #fff; margin-bottom: 10px;">✦</div><div style="font-size: 14px; font-weight: 600; margin-bottom: 2px;">Feature name</div><div style="font-size: 12px; color: #666;">Rounded icon tile above heading.</div></div>`,
100
101  'gradient-text': `<div style="font-family: system-ui, sans-serif;"><div style="font-size: 28px; font-weight: 700; background: linear-gradient(135deg, oklch(65% 0.25 320), oklch(60% 0.25 265)); -webkit-background-clip: text; background-clip: text; color: transparent; line-height: 1.1;">Build the Future</div><div style="font-size: 12px; color: #888; margin-top: 4px;">Gradient text kills scannability.</div></div>`,
102
103  'ai-color-palette': `<div style="display: flex; gap: 6px;"><div style="width: 44px; height: 44px; border-radius: 6px; background: oklch(60% 0.22 265);"></div><div style="width: 44px; height: 44px; border-radius: 6px; background: oklch(62% 0.25 300);"></div><div style="width: 44px; height: 44px; border-radius: 6px; background: oklch(64% 0.25 340);"></div><div style="width: 44px; height: 44px; border-radius: 6px; background: oklch(70% 0.20 200);"></div></div>`,
104
105  'dark-glow': `<div style="background: #0a0b14; padding: 18px 20px; border-radius: 10px; font-family: system-ui, sans-serif;"><div style="color: oklch(78% 0.22 280); text-shadow: 0 0 12px oklch(78% 0.22 280 / 0.7); font-size: 16px; font-weight: 600;">Neon on dark</div><div style="color: oklch(60% 0.12 260); font-size: 12px; margin-top: 4px;">Cyberpunk-by-default slop.</div></div>`,
106
107  'nested-cards': `<div style="background: #f5f3ef; border: 1px solid #e0dcd4; border-radius: 10px; padding: 10px;"><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 8px; padding: 10px;"><div style="background: #f5f3ef; border: 1px solid #e8e4df; border-radius: 6px; padding: 8px; font-size: 12px; font-family: system-ui, sans-serif; color: #555;">Card inside card inside card.</div></div></div>`,
108
109  'monotonous-spacing': `<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;"><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 6px; height: 48px;"></div><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 6px; height: 48px;"></div><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 6px; height: 48px;"></div><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 6px; height: 48px;"></div><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 6px; height: 48px;"></div><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 6px; height: 48px;"></div></div>`,
110
111  'everything-centered': `<div style="font-family: system-ui, sans-serif; text-align: center; color: #111;"><div style="font-size: 16px; font-weight: 600; margin-bottom: 6px;">Centered headline</div><div style="font-size: 12px; color: #555; margin-bottom: 10px;">Everything centered by default.</div><div style="display: inline-block; background: #111; color: #fff; padding: 6px 14px; border-radius: 6px; font-size: 12px;">Call to action</div></div>`,
112
113  'bounce-easing': `<div style="font-family: system-ui, sans-serif; color: #111; display: flex; align-items: center; gap: 10px;"><div style="width: 36px; height: 36px; border-radius: 50%; background: oklch(65% 0.22 265); animation: bouncey 0.9s cubic-bezier(0.68, -0.55, 0.27, 1.55) infinite;"></div><div style="font-size: 12px; color: #555;">Bounce + elastic easing feels dated.</div><style>@keyframes bouncey { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-10px); } }</style></div>`,
114
115  'all-caps-body': `<div style="font-family: system-ui, sans-serif; color: #111; font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em; line-height: 1.5;">Long passages in uppercase are hard to read. We recognize words by their shape, which all-caps removes.</div>`,
116
117  'pure-black-white': `<div style="background: #ffffff; padding: 16px 18px; color: #000000; font-family: system-ui, sans-serif; font-size: 14px;"><div style="font-weight: 600; margin-bottom: 4px;">Pure black on pure white</div><div style="font-size: 12px; color: #000;">Neither exists in nature. Always tint.</div></div>`,
118
119  'gray-on-color': `<div style="background: oklch(60% 0.20 265); padding: 16px 18px; border-radius: 6px; font-family: system-ui, sans-serif;"><div style="color: #9ca3af; font-size: 13px;">Gray text on a colored background. Washed out and hard to read.</div></div>`,
120
121  'low-contrast': `<div style="background: #fff; padding: 16px 18px; font-family: system-ui, sans-serif;"><div style="color: #d4d4d4; font-size: 13px;">Light gray text on a white background. 1.6:1 contrast, fails WCAG.</div></div>`,
122
123  'layout-transition': `<div style="font-family: system-ui, sans-serif; color: #111; display: flex; align-items: center; gap: 10px;"><div style="background: oklch(65% 0.22 265); border-radius: 6px; animation: janky 1.2s ease-in-out infinite; width: 60px; height: 30px;"></div><div style="font-size: 12px; color: #555;">Animating width/height causes layout jank.</div><style>@keyframes janky { 0%,100% { width: 60px; } 50% { width: 120px; } }</style></div>`,
124
125  'cramped-padding': `<div style="font-family: system-ui, sans-serif;"><button style="background: #111; color: #fff; border: none; border-radius: 4px; padding: 2px 6px; font-size: 13px; font-weight: 500;">Buy now</button> <span style="color: #555; font-size: 12px; margin-left: 8px;">2px vertical padding.</span></div>`,
126
127  '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>`,
128
129  '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>`,
130
131  '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>`,
132
133  'tiny-text': `<div style="font-family: system-ui, sans-serif; color: #111;"><div style="font-size: 15px; margin-bottom: 6px;">Regular body text</div><div style="font-size: 9px; color: #555;">And then fine print at 9 pixels that no one will ever read.</div></div>`,
134
135  'wide-tracking': `<div style="font-family: system-ui, sans-serif; font-size: 13px; color: #111; letter-spacing: 0.22em; max-width: 230px; line-height: 1.6;">Wide tracking on body text slows reading by breaking up natural character groupings.</div>`,
136
137  'line-length': `<div style="font-family: system-ui, sans-serif; font-size: 13px; color: #111; line-height: 1.55; max-width: 100%;">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.</div>`,
138
139  // ── LLM-only rule visuals ─────────────────────────────────────────
140
141  'monospace-as-technical': `<div style="font-family: 'Courier New', monospace; color: #111;"><div style="font-size: 18px; font-weight: 700; margin-bottom: 6px;">TECHNICAL_TOOL</div><div style="font-size: 11px; color: #555;">Mono for "developer" vibes. Lazy.</div></div>`,
142
143  'dark-mode-default': `<div style="background: #0f1117; padding: 18px; border-radius: 8px; font-family: system-ui, sans-serif;"><div style="color: #e5e7eb; font-size: 14px; font-weight: 600; margin-bottom: 4px;">Dark by default</div><div style="color: #9ca3af; font-size: 11px;">Defaulting to dark is a retreat from a decision.</div></div>`,
144
145  'everything-in-cards': `<div style="background: #fff; border: 1px solid #e8e4df; border-radius: 8px; padding: 10px;"><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 6px; padding: 8px; font-family: system-ui, sans-serif; font-size: 12px; color: #111;"><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 4px; padding: 6px;">Title</div></div><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 6px; padding: 8px; margin-top: 6px; font-family: system-ui, sans-serif; font-size: 11px; color: #555;">Card around every single thing.</div></div>`,
146
147  'identical-card-grids': `<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; font-family: system-ui, sans-serif;">${'<div style="background: #fff; border: 1px solid #e8e4df; border-radius: 6px; padding: 10px; display: flex; flex-direction: column; align-items: flex-start; gap: 4px;"><div style="width: 18px; height: 18px; background: oklch(62% 0.20 265); border-radius: 4px;"></div><div style="font-size: 10px; font-weight: 600; color: #111;">Feature</div><div style="font-size: 9px; color: #888;">Short copy.</div></div>'.repeat(6)}</div>`,
148
149  'hero-metric-layout': `<div style="font-family: system-ui, sans-serif; text-align: left;"><div style="font-size: 42px; font-weight: 800; background: linear-gradient(135deg, oklch(65% 0.25 265), oklch(65% 0.25 340)); -webkit-background-clip: text; background-clip: text; color: transparent; line-height: 1;">10M+</div><div style="font-size: 10px; color: #888; text-transform: uppercase; letter-spacing: 0.1em; margin-top: 2px;">Active users</div><div style="display: flex; gap: 14px; margin-top: 10px; font-size: 10px; color: #555;"><span><strong>99.9%</strong> uptime</span><span><strong>200ms</strong> p50</span></div></div>`,
150
151  'glassmorphism': `<div style="position: relative; width: 100%; height: 100%; background: linear-gradient(135deg, oklch(70% 0.22 265), oklch(70% 0.25 340)); border-radius: 10px; overflow: hidden; display: flex; align-items: center; justify-content: center;"><div style="background: rgba(255,255,255,0.25); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); border: 1px solid rgba(255,255,255,0.4); border-radius: 10px; padding: 14px 18px; color: #fff; font-family: system-ui, sans-serif; font-size: 12px; font-weight: 600; box-shadow: 0 8px 30px rgba(0,0,0,0.12);">Frosted glass card</div></div>`,
152
153  'sparkline-decoration': `<div style="background: #fff; border: 1px solid #e8e4df; border-radius: 8px; padding: 14px 16px; width: 220px; font-family: system-ui, sans-serif;"><div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px;"><div><div style="font-size: 10px; color: #888; text-transform: uppercase; letter-spacing: 0.08em;">Revenue</div><div style="font-size: 20px; font-weight: 700; color: #111;">$42.1k</div></div><svg width="60" height="28" viewBox="0 0 60 28" style="flex-shrink: 0;"><polyline points="0,20 10,18 20,22 30,10 40,14 50,6 60,12" stroke="oklch(62% 0.22 265)" stroke-width="2" fill="none"/></svg></div><div style="font-size: 10px; color: #888;">Tiny chart, no real information.</div></div>`,
154
155  'generic-drop-shadows': `<div style="display: flex; gap: 10px;"><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 10px; width: 70px; height: 70px; box-shadow: 0 10px 30px rgba(0,0,0,0.08);"></div><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 10px; width: 70px; height: 70px; box-shadow: 0 10px 30px rgba(0,0,0,0.08);"></div><div style="background: #fff; border: 1px solid #e8e4df; border-radius: 10px; width: 70px; height: 70px; box-shadow: 0 10px 30px rgba(0,0,0,0.08);"></div></div>`,
156
157  'modal-reflex': `<div style="position: relative; width: 100%; height: 100%; background: #f5f3ef; border-radius: 8px; overflow: hidden;"><div style="position: absolute; inset: 0; background: rgba(0,0,0,0.35);"></div><div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; border-radius: 8px; padding: 14px 18px; width: 200px; font-family: system-ui, sans-serif; box-shadow: 0 20px 60px rgba(0,0,0,0.2);"><div style="font-size: 13px; font-weight: 600; color: #111; margin-bottom: 4px;">Are you sure?</div><div style="font-size: 11px; color: #666; margin-bottom: 8px;">Really, truly sure about this?</div><div style="display: flex; gap: 6px; justify-content: flex-end;"><div style="background: #eee; color: #555; padding: 4px 8px; border-radius: 4px; font-size: 10px;">Cancel</div><div style="background: oklch(60% 0.22 265); color: #fff; padding: 4px 8px; border-radius: 4px; font-size: 10px;">OK</div></div></div></div>`,
158
159  'every-button-primary': `<div style="display: flex; flex-direction: column; gap: 6px; font-family: system-ui, sans-serif;"><div style="display: flex; gap: 6px;"><button style="background: oklch(60% 0.22 265); color: #fff; border: none; border-radius: 5px; padding: 6px 12px; font-size: 11px; font-weight: 600;">Save</button><button style="background: oklch(60% 0.22 265); color: #fff; border: none; border-radius: 5px; padding: 6px 12px; font-size: 11px; font-weight: 600;">Cancel</button><button style="background: oklch(60% 0.22 265); color: #fff; border: none; border-radius: 5px; padding: 6px 12px; font-size: 11px; font-weight: 600;">Delete</button></div><div style="font-size: 10px; color: #888;">Every action shouts equally.</div></div>`,
160
161  'redundant-headers': `<div style="font-family: system-ui, sans-serif; color: #111; max-width: 230px;"><div style="font-size: 14px; font-weight: 600; margin-bottom: 4px;">Overview</div><div style="font-size: 11px; color: #555; line-height: 1.5;">This is the overview section, which provides an overview of the overview.</div></div>`,
162
163  'mobile-amputation': `<div style="font-family: system-ui, sans-serif;"><div style="display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: #fff; border: 1px solid #e8e4df; border-radius: 6px; margin-bottom: 4px; font-size: 12px; color: #999; text-decoration: line-through;"><span>Export to CSV</span></div><div style="font-size: 10px; color: #888; margin-top: 4px;">"Not available on mobile."</div></div>`,
164};
165
166/**
167 * Anti-patterns that live in the /impeccable skill's DON'T list but
168 * don't have a deterministic detector. These can only be caught by
169 * /critique running an LLM assessment pass.
170 *
171 * Each entry looks like a detection rule: id, category, name,
172 * description, skillSection. The generator merges these into the
173 * grouped sections alongside detected rules with an 'llm' layer badge.
174 */
175// ─── Gallery: real examples in the wild ──────────────────────────────
176
177/**
178 * Curated real-world examples of anti-patterns caught in the wild.
179 * Each entry maps to:
180 *   - public/antipattern-images/{id}.png  (preview thumbnail)
181 *   - public/antipattern-examples/{id}.html  (standalone live example)
182 * Rendered as a dedicated section on the /anti-patterns page, replacing
183 * the old /gallery route which was confusingly labeled in the top nav.
184 */
185export const GALLERY_ITEMS = [
186  {
187    id: 'purple-gradients',
188    title: 'Purple Gradients Everywhere',
189    desc:
190      'The AI color palette: purple-to-blue gradients on everything. Buttons, text, backgrounds, orbs. The new "make it pop."',
191  },
192  {
193    id: 'lazy-cool',
194    title: 'Lazy "Cool"',
195    desc:
196      'Glassmorphism, neon glows, blurred orbs, monospace everything. Looks like a hackathon project, not a product.',
197  },
198  {
199    id: 'lazy-impact',
200    title: 'Lazy "Impact"',
201    desc:
202      'When in doubt, animate everything. Bouncing buttons, wiggling icons, gradient text, floating badges. Motion without meaning.',
203  },
204  {
205    id: 'thick-border-cards',
206    title: 'Side-Tab Cards',
207    desc:
208      'A thick colored border on one side of a rounded card. The single most recognizable tell of AI-generated UI.',
209  },
210  {
211    id: 'cardocalypse',
212    title: 'Cardocalypse',
213    desc:
214      'Cards inside cards inside cards. Five levels of nesting, each with its own padding and shadow.',
215  },
216  {
217    id: 'layout-templates',
218    title: 'Copy-Paste Layouts',
219    desc:
220      'The same hero-metric-features template repeated with different colors. When every section looks the same, nothing stands out.',
221  },
222  {
223    id: 'inter-everywhere',
224    title: 'Inter Everywhere',
225    desc:
226      'One font for everything. Headings, body, labels, buttons. No typographic hierarchy, no personality, no design.',
227  },
228  {
229    id: 'massive-icons',
230    title: 'Massive Icons',
231    desc:
232      'Icon containers larger than the content they introduce. When the decoration is bigger than the message, priorities are backwards.',
233  },
234  {
235    id: 'bad-contrast',
236    title: 'Bad Contrast Choices',
237    desc:
238      'Gray text on colored backgrounds, low-contrast labels, unreadable combinations. Looking good and being readable should not conflict.',
239  },
240  {
241    id: 'redundant-ux-writing',
242    title: 'Redundant UX Writing',
243    desc:
244      'Label, sublabel, helper text, and hint text all saying the same thing in slightly different words. Say it once, say it well.',
245  },
246  {
247    id: 'modal-abuse',
248    title: 'Modal Abuse',
249    desc:
250      'Complex settings crammed into a modal. If it needs a scroll bar and three columns, it deserves its own page.',
251  },
252];
253
254// ─── LLM-only rules ──────────────────────────────────────────────────
255
256export const LLM_ONLY_RULES = [
257  {
258    id: 'monospace-as-technical',
259    category: 'slop',
260    name: 'Monospace as "technical" shorthand',
261    description:
262      'Using a monospace typeface to signal "developer / technical" vibes. Reach for real type choices instead of a lazy stereotype.',
263    skillSection: 'Typography',
264  },
265  {
266    id: 'dark-mode-default',
267    category: 'slop',
268    name: 'Defaulting to dark mode for "safety"',
269    description:
270      '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.',
271    skillSection: 'Color & Contrast',
272  },
273  {
274    id: 'everything-in-cards',
275    category: 'slop',
276    name: 'Wrapping everything in cards',
277    description:
278      'Not every piece of content needs a bordered container. Spacing and alignment create visual grouping without the overhead of a card.',
279    skillSection: 'Layout & Space',
280  },
281  {
282    id: 'identical-card-grids',
283    category: 'slop',
284    name: 'Identical card grids',
285    description:
286      'Same-sized cards with icon + heading + text repeated endlessly. The default AI homepage layout.',
287    skillSection: 'Layout & Space',
288  },
289  {
290    id: 'hero-metric-layout',
291    category: 'slop',
292    name: 'Hero metric layout',
293    description:
294      'Big number, small label, three supporting stats, gradient accent. Used everywhere, trusted nowhere.',
295    skillSection: 'Layout & Space',
296  },
297  {
298    id: 'glassmorphism',
299    category: 'slop',
300    name: 'Glassmorphism everywhere',
301    description:
302      'Blur effects, glass cards, and glow borders used as decoration rather than to solve a real layering problem.',
303    skillSection: 'Visual Details',
304  },
305  {
306    id: 'sparkline-decoration',
307    category: 'slop',
308    name: 'Sparklines as decoration',
309    description:
310      'Tiny charts that look sophisticated but convey no meaningful information. If the data matters, give it room.',
311    skillSection: 'Visual Details',
312  },
313  {
314    id: 'generic-drop-shadows',
315    category: 'slop',
316    name: 'Rounded rectangles with generic drop shadows',
317    description:
318      'The safest, most forgettable shape on the web. Could be the output of any AI. Commit to a stronger visual treatment.',
319    skillSection: 'Visual Details',
320  },
321  {
322    id: 'modal-reflex',
323    category: 'slop',
324    name: 'Reaching for modals by reflex',
325    description:
326      'Modals interrupt the user and are lazy as a design default. Use them only when there is truly no better place for the interaction.',
327    skillSection: 'Visual Details',
328  },
329  {
330    id: 'every-button-primary',
331    category: 'quality',
332    name: 'Every button is a primary button',
333    description:
334      'When every button looks equally important, nothing reads as the primary action. Use ghost buttons, text links, and secondary styles to build hierarchy.',
335    skillSection: 'Interaction',
336  },
337  {
338    id: 'redundant-headers',
339    category: 'quality',
340    name: 'Redundant information',
341    description:
342      'Intros that restate the heading. Section labels that repeat the page title. Cards that echo their own caption. Make every word earn its place.',
343    skillSection: 'Interaction',
344  },
345  {
346    id: 'mobile-amputation',
347    category: 'quality',
348    name: 'Amputating features on mobile',
349    description:
350      'Hiding critical functionality on mobile because it is inconvenient. Adapt the interface to the context, do not strip it.',
351    skillSection: 'Responsive',
352  },
353];