antipatterns.mjs

  1const ANTIPATTERNS = [
  2  // ── AI slop: tells that something was AI-generated ──
  3  {
  4    id: 'side-tab',
  5    category: 'slop',
  6    name: 'Side-tab accent border',
  7    description:
  8      '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.',
  9    skillSection: 'Visual Details',
 10    skillGuideline: 'colored accent stripe',
 11  },
 12  {
 13    id: 'border-accent-on-rounded',
 14    category: 'slop',
 15    name: 'Border accent on rounded element',
 16    description:
 17      'Thick accent border on a rounded card — the border clashes with the rounded corners. Remove the border or the border-radius.',
 18    skillSection: 'Visual Details',
 19    skillGuideline: 'colored accent stripe',
 20  },
 21  {
 22    id: 'overused-font',
 23    category: 'slop',
 24    name: 'Overused font',
 25    description:
 26      '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.',
 27    skillSection: 'Typography',
 28    skillGuideline: 'overused fonts like Inter',
 29  },
 30  {
 31    id: 'single-font',
 32    category: 'slop',
 33    name: 'Single font for everything',
 34    description:
 35      'Only one font family is used for the entire page. Pair a distinctive display font with a refined body font to create typographic hierarchy.',
 36    skillSection: 'Typography',
 37    skillGuideline: 'only one font family for the entire page',
 38  },
 39  {
 40    id: 'flat-type-hierarchy',
 41    category: 'slop',
 42    name: 'Flat type hierarchy',
 43    description:
 44      '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).',
 45    skillSection: 'Typography',
 46    skillGuideline: 'flat type hierarchy',
 47  },
 48  {
 49    id: 'gradient-text',
 50    category: 'slop',
 51    name: 'Gradient text',
 52    description:
 53      'Gradient text is decorative rather than meaningful — a common AI tell, especially on headings and metrics. Use solid colors for text.',
 54    skillSection: 'Color & Contrast',
 55    skillGuideline: 'gradient text for',
 56  },
 57  {
 58    id: 'ai-color-palette',
 59    category: 'slop',
 60    name: 'AI color palette',
 61    description:
 62      'Purple/violet gradients and cyan-on-dark are the most recognizable tells of AI-generated UIs. Choose a distinctive, intentional palette.',
 63    skillSection: 'Color & Contrast',
 64    skillGuideline: 'AI color palette',
 65  },
 66  {
 67    id: 'nested-cards',
 68    category: 'slop',
 69    name: 'Nested cards',
 70    description:
 71      'Cards inside cards create visual noise and excessive depth. Flatten the hierarchy — use spacing, typography, and dividers instead of nesting containers.',
 72    skillSection: 'Layout & Space',
 73    skillGuideline: 'Nest cards inside cards',
 74  },
 75  {
 76    id: 'monotonous-spacing',
 77    category: 'slop',
 78    name: 'Monotonous spacing',
 79    description:
 80      'The same spacing value used everywhere — no rhythm, no variation. Use tight groupings for related items and generous separations between sections.',
 81    skillSection: 'Layout & Space',
 82    skillGuideline: 'same spacing everywhere',
 83  },
 84  {
 85    id: 'everything-centered',
 86    category: 'slop',
 87    name: 'Everything centered',
 88    description:
 89      'Every text element is center-aligned. Left-aligned text with asymmetric layouts feels more designed. Center only hero sections and CTAs.',
 90    skillSection: 'Layout & Space',
 91    skillGuideline: 'Center everything',
 92  },
 93  {
 94    id: 'bounce-easing',
 95    category: 'slop',
 96    name: 'Bounce or elastic easing',
 97    description:
 98      'Bounce and elastic easing feel dated and tacky. Real objects decelerate smoothly — use exponential easing (ease-out-quart/quint/expo) instead.',
 99    skillSection: 'Motion',
100    skillGuideline: 'bounce or elastic easing',
101  },
102  {
103    id: 'dark-glow',
104    category: 'slop',
105    name: 'Dark mode with glowing accents',
106    description:
107      '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.',
108    skillSection: 'Color & Contrast',
109    skillGuideline: 'dark mode with glowing accents',
110  },
111  {
112    id: 'icon-tile-stack',
113    category: 'slop',
114    name: 'Icon tile stacked above heading',
115    description:
116      '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.',
117    skillSection: 'Typography',
118    skillGuideline: 'large icons with rounded corners above every heading',
119  },
120  {
121    id: 'italic-serif-display',
122    category: 'slop',
123    name: 'Italic serif display headline',
124    description:
125      '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.',
126    skillSection: 'Typography',
127    skillGuideline: 'oversized italic serif as the hero headline',
128  },
129  {
130    id: 'hero-eyebrow-chip',
131    category: 'slop',
132    name: 'Hero eyebrow / pill chip',
133    description:
134      '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.',
135    skillSection: 'Typography',
136    skillGuideline: 'tiny uppercase tracked label above the hero headline',
137  },
138  {
139    id: 'repeated-section-kickers',
140    category: 'slop',
141    severity: 'advisory',
142    name: 'Repeated section kicker labels',
143    description:
144      '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.',
145    skillSection: 'Typography',
146    skillGuideline: 'repeated eyebrow or kicker labels as section scaffolding',
147  },
148
149  // ── Quality: general design and accessibility issues ──
150  {
151    id: 'pure-black-white',
152    category: 'quality',
153    name: 'Pure black background',
154    description:
155      'Pure #000000 as a background color looks harsh and unnatural. Tint it slightly toward your brand hue (e.g., oklch(12% 0.01 250)) for a more refined feel.',
156    skillSection: 'Color & Contrast',
157    skillGuideline: 'pure black (#000)',
158  },
159  {
160    id: 'gray-on-color',
161    category: 'quality',
162    name: 'Gray text on colored background',
163    description:
164      'Gray text looks washed out on colored backgrounds. Use a darker shade of the background color instead, or white/near-white for contrast.',
165    skillSection: 'Color & Contrast',
166    skillGuideline: 'gray text on colored backgrounds',
167  },
168  {
169    id: 'low-contrast',
170    category: 'quality',
171    name: 'Low contrast text',
172    description:
173      '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.',
174  },
175  {
176    id: 'layout-transition',
177    category: 'quality',
178    name: 'Layout property animation',
179    description:
180      'Animating width, height, padding, or margin causes layout thrash and janky performance. Use transform and opacity instead, or grid-template-rows for height animations.',
181    skillSection: 'Motion',
182    skillGuideline: 'Animate layout properties',
183  },
184  {
185    id: 'line-length',
186    category: 'quality',
187    name: 'Line length too long',
188    description:
189      '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.',
190    skillSection: 'Layout & Space',
191    skillGuideline: 'wrap beyond ~80 characters',
192  },
193  {
194    id: 'cramped-padding',
195    category: 'quality',
196    name: 'Cramped padding',
197    description:
198      'Text is too close to the edge of its container. Add at least 8px (ideally 12-16px) of padding inside bordered or colored containers.',
199  },
200  {
201    id: 'body-text-viewport-edge',
202    category: 'quality',
203    name: 'Body text touching viewport edge',
204    description:
205      '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.',
206  },
207  {
208    id: 'tight-leading',
209    category: 'quality',
210    name: 'Tight line height',
211    description:
212      '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.',
213  },
214  {
215    id: 'skipped-heading',
216    category: 'quality',
217    name: 'Skipped heading level',
218    description:
219      '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.',
220  },
221  {
222    id: 'justified-text',
223    category: 'quality',
224    name: 'Justified text',
225    description:
226      '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.',
227  },
228  {
229    id: 'tiny-text',
230    category: 'quality',
231    name: 'Tiny body text',
232    description:
233      'Body text below 12px is hard to read, especially on high-DPI screens. Use at least 14px for body content, 16px is ideal.',
234  },
235  {
236    id: 'all-caps-body',
237    category: 'quality',
238    name: 'All-caps body text',
239    description:
240      '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.',
241    skillSection: 'Typography',
242    skillGuideline: 'long body passages in uppercase',
243  },
244  {
245    id: 'wide-tracking',
246    category: 'quality',
247    name: 'Wide letter spacing on body text',
248    description:
249      'Letter spacing above 0.05em on body text disrupts natural character groupings and slows reading. Reserve wide tracking for short uppercase labels only.',
250  },
251];
252
253const RULE_ENGINE_SUPPORT = {
254  regex: new Set(['source', 'page-analyzer']),
255  'static-html': new Set(['element', 'page']),
256  browser: new Set(['element', 'page', 'layout']),
257  visual: new Set(['visual-contrast']),
258};
259
260function getAntipattern(id) {
261  return ANTIPATTERNS.find(rule => rule.id === id);
262}
263
264function getRulesForCategory(category) {
265  return ANTIPATTERNS.filter(rule => rule.category === category);
266}
267
268function getRuleEngineSupport(engine) {
269  return RULE_ENGINE_SUPPORT[engine] || new Set();
270}
271
272export {
273  ANTIPATTERNS,
274  RULE_ENGINE_SUPPORT,
275  getAntipattern,
276  getRulesForCategory,
277  getRuleEngineSupport,
278};