Merge gallery into /anti-patterns, hide rule ids

Two fixes from the review.

1. Rule id chip hidden. The internal slugs (e.g. 'border-accent-on-rounded')
   are not useful to readers, only to detector code. Drop the
   .rule-card-id element from the card head entirely. The DOM id on
   the article stays so rules can still be anchor-linked.

2. Merge /gallery into /anti-patterns and drop 'Gallery' from the nav.
   'Gallery' in the top nav reads as 'things built with impeccable'
   when it is actually a curated collection of AI-generated UI in the
   wild — the complement to the rule catalog above.

   - Add GALLERY_ITEMS to content/site/anti-patterns-catalog.js
     (11 entries, same ids and copy as the old gallery.html)
   - Render a new 'In the wild' section at the bottom of
     /anti-patterns with a card grid of the 11 specimens, each linking
     to its standalone live example under /antipattern-examples/{id}.html
   - New .gallery-card CSS: square thumbnail, italic display title,
     charcoal body, hover lifts the card and tints the title accent
   - Add an 'In the wild' entry to the anti-patterns TOC sidebar so
     readers can jump to it
   - Drop the 'Gallery' link from the top-level nav in the shared
     header partial and the 4 hand-authored HTML pages. The old
     /gallery route still serves its page directly (for bookmarked
     links), but the nav no longer advertises it and the gallery page
     itself now marks Anti-Patterns as the active nav item.
This commit is contained in:
Paul Bakaus
2026-04-08 12:48:24 -07:00
parent c384e6b568
commit ebe07cbae5
9 changed files with 205 additions and 36 deletions
+81 -2
View File
@@ -165,8 +165,6 @@ export const VISUAL_EXAMPLES = {
'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>`,
};
// ─── LLM-only rules ──────────────────────────────────────────────────
/**
* 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
@@ -176,6 +174,87 @@ export const VISUAL_EXAMPLES = {
* 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: 'syne-display-font',
-1
View File
@@ -9,7 +9,6 @@
<a href="/" data-nav="home">Home</a>
<a href="/skills" data-nav="docs">Docs</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/gallery" data-nav="gallery">Gallery</a>
</nav>
<a href="https://github.com/pbakaus/impeccable" class="site-header-github" target="_blank" rel="noopener" aria-label="Impeccable on GitHub, 17k stars">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
-1
View File
@@ -176,7 +176,6 @@
<a href="/" data-nav="home">Home</a>
<a href="/skills" data-nav="docs">Docs</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/gallery" data-nav="gallery">Gallery</a>
</nav>
<a href="https://github.com/pbakaus/impeccable" class="site-header-github" target="_blank" rel="noopener" aria-label="Impeccable on GitHub, 17k stars">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
+84 -20
View File
@@ -812,27 +812,9 @@ main#main {
.rule-card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-sm);
margin-bottom: 2px;
min-height: 18px;
}
.rule-card-id {
font-family: var(--font-mono);
font-size: 0.75rem;
font-weight: 500;
color: var(--color-ash);
background: transparent;
padding: 0;
border: none;
}
.rule-card-badges {
display: inline-flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
margin-bottom: 2px;
flex-wrap: wrap;
}
.rule-card-category {
@@ -1123,6 +1105,88 @@ main#main {
max-width: 65ch;
}
/* ============================================
ANTI-PATTERNS: IN THE WILD (merged gallery)
============================================ */
.gallery-section {
margin-top: clamp(3rem, 6vw, 4.5rem);
}
.gallery-section-lede {
font-size: 0.9375rem;
line-height: 1.6;
color: var(--color-charcoal);
max-width: 60ch;
margin-bottom: var(--spacing-lg);
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: var(--spacing-md);
}
.gallery-card {
display: flex;
flex-direction: column;
background: var(--color-paper);
border: 1px solid var(--color-mist);
border-radius: 10px;
overflow: hidden;
text-decoration: none;
color: inherit;
transition: border-color var(--duration-fast) var(--ease-out),
transform var(--duration-fast) var(--ease-out);
}
.gallery-card:hover {
border-color: var(--color-ink);
transform: translateY(-2px);
}
.gallery-card-thumb {
aspect-ratio: 1;
overflow: hidden;
background: var(--color-cream);
border-bottom: 1px solid var(--color-mist);
}
.gallery-card-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: top left;
display: block;
}
.gallery-card-body {
padding: var(--spacing-md);
display: flex;
flex-direction: column;
gap: 6px;
flex: 1;
}
.gallery-card-title {
font-family: var(--font-display);
font-size: 1.125rem;
font-style: italic;
font-weight: 500;
color: var(--color-ink);
line-height: 1.25;
}
.gallery-card:hover .gallery-card-title {
color: var(--color-accent);
}
.gallery-card-desc {
font-size: 0.8125rem;
line-height: 1.55;
color: var(--color-charcoal);
}
/* ============================================
MOBILE: collapsible sidebar behind a toggle
============================================ */
+1 -2
View File
@@ -197,8 +197,7 @@
<nav class="site-header-nav" aria-label="Primary">
<a href="/" data-nav="home">Home</a>
<a href="/skills" data-nav="docs">Docs</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/gallery" data-nav="gallery" aria-current="page">Gallery</a>
<a href="/anti-patterns" data-nav="anti-patterns" aria-current="page">Anti-Patterns</a>
</nav>
<a href="https://github.com/pbakaus/impeccable" class="site-header-github" target="_blank" rel="noopener" aria-label="Impeccable on GitHub, 17k stars">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
-1
View File
@@ -59,7 +59,6 @@
<a href="/" data-nav="home" aria-current="page">Home</a>
<a href="/skills" data-nav="docs">Docs</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/gallery" data-nav="gallery">Gallery</a>
</nav>
<a href="https://github.com/pbakaus/impeccable" class="site-header-github" target="_blank" rel="noopener" aria-label="Impeccable on GitHub, 17k stars">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
-1
View File
@@ -34,7 +34,6 @@
<a href="/" data-nav="home">Home</a>
<a href="/skills" data-nav="docs">Docs</a>
<a href="/anti-patterns" data-nav="anti-patterns">Anti-Patterns</a>
<a href="/gallery" data-nav="gallery">Gallery</a>
</nav>
<a href="https://github.com/pbakaus/impeccable" class="site-header-github" target="_blank" rel="noopener" aria-label="Impeccable on GitHub, 17k stars">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/></svg>
+33 -7
View File
@@ -19,6 +19,7 @@ import {
CATEGORY_DESCRIPTIONS,
LAYER_LABELS,
LAYER_DESCRIPTIONS,
GALLERY_ITEMS,
} from './lib/sub-pages-data.js';
import { renderMarkdown, slugify } from './lib/render-markdown.js';
import { renderPage } from './lib/render-page.js';
@@ -356,11 +357,16 @@ function renderAntiPatternsSidebar(grouped) {
return `
<aside class="skills-sidebar anti-patterns-sidebar" aria-label="Anti-pattern sections">
<div class="skills-sidebar-inner">
<button class="skills-sidebar-toggle" type="button" aria-expanded="false" aria-controls="anti-patterns-sidebar-inner">
<span class="skills-sidebar-toggle-label">Sections</span>
<svg class="skills-sidebar-toggle-chevron" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
</button>
<div class="skills-sidebar-inner" id="anti-patterns-sidebar-inner">
<p class="skills-sidebar-label">Sections</p>
<div class="skills-sidebar-group">
<ul class="skills-sidebar-list anti-patterns-sidebar-list">
${entries}
<li><a href="#in-the-wild"><span>In the wild</span><span class="anti-patterns-sidebar-count">${GALLERY_ITEMS.length}</span></a></li>
</ul>
</div>
</div>
@@ -381,17 +387,13 @@ function renderRuleCard(rule) {
const visual = rule.visual
? `<div class="rule-card-visual" aria-hidden="true"><div class="rule-card-visual-inner">${rule.visual}</div></div>`
: '';
const ruleIdDisplay = rule.layer === 'llm' ? '' : `<code class="rule-card-id">${escapeHtml(rule.id)}</code>`;
return `
<article class="rule-card" id="rule-${rule.id}" data-layer="${layer}">
${visual}
<div class="rule-card-body">
<div class="rule-card-head">
${ruleIdDisplay}
<span class="rule-card-badges">
<span class="rule-card-category" data-category="${rule.category}">${categoryLabel}</span>
<span class="rule-card-layer" data-layer="${layer}" title="${escapeAttr(layerTitle)}">${escapeHtml(layerLabel)}</span>
</span>
<span class="rule-card-category" data-category="${rule.category}">${categoryLabel}</span>
<span class="rule-card-layer" data-layer="${layer}" title="${escapeAttr(layerTitle)}">${escapeHtml(layerLabel)}</span>
</div>
<h3 class="rule-card-name">${escapeHtml(rule.name)}</h3>
<p class="rule-card-desc">${escapeHtml(rule.description)}</p>
@@ -481,6 +483,19 @@ ${rules.map(renderRuleCard).join('\n')}
.filter((r) => r.layer !== 'llm').length;
const llmCount = totalRules - detectedCount;
const galleryHtml = GALLERY_ITEMS.map(
(item) => `
<a class="gallery-card" href="/antipattern-examples/${item.id}.html">
<div class="gallery-card-thumb">
<img src="/antipattern-images/${item.id}.png" alt="${escapeAttr(item.title)} example" loading="lazy" width="540" height="540">
</div>
<div class="gallery-card-body">
<h3 class="gallery-card-title">${escapeHtml(item.title)}</h3>
<p class="gallery-card-desc">${escapeHtml(item.desc)}</p>
</div>
</a>`,
).join('\n');
return `
<div class="anti-patterns-content">
<header class="anti-patterns-header">
@@ -507,6 +522,17 @@ ${rules.map(renderRuleCard).join('\n')}
<div class="anti-patterns-sections">
${sectionsHtml}
</div>
<section class="gallery-section" id="in-the-wild">
<header class="anti-patterns-section-header">
<h2 class="anti-patterns-section-title">In the wild</h2>
<p class="anti-patterns-section-count">${GALLERY_ITEMS.length} specimens</p>
</header>
<p class="gallery-section-lede">Real examples scraped from the web. Click any to see the live page with the overlay running.</p>
<div class="gallery-grid">
${galleryHtml}
</div>
</section>
</div>`;
}
+6 -1
View File
@@ -18,9 +18,14 @@ import {
DETECTION_LAYERS,
VISUAL_EXAMPLES,
LLM_ONLY_RULES,
GALLERY_ITEMS,
} from '../../content/site/anti-patterns-catalog.js';
export { LAYER_LABELS, LAYER_DESCRIPTIONS } from '../../content/site/anti-patterns-catalog.js';
export {
LAYER_LABELS,
LAYER_DESCRIPTIONS,
GALLERY_ITEMS,
} from '../../content/site/anti-patterns-catalog.js';
/**
* Skills that should be excluded from the index and not get a detail page.