${escapeHtml(rule.id)}
+ ${categoryLabel}
+ ${escapeHtml(rule.name)}
+${escapeHtml(rule.description)}
+ ${skillLink} +diff --git a/public/css/sub-pages.css b/public/css/sub-pages.css index ac5019c65..e0fd0e030 100644 --- a/public/css/sub-pages.css +++ b/public/css/sub-pages.css @@ -570,6 +570,217 @@ main#main { background: var(--color-cream); } +/* ============================================ + ANTI-PATTERNS INDEX + ============================================ */ + +.anti-patterns-sidebar-list a { + display: flex !important; + align-items: baseline; + justify-content: space-between; + gap: var(--spacing-sm); +} + +.anti-patterns-sidebar-count { + font-size: 0.6875rem; + font-weight: 500; + color: var(--color-ash); + font-variant-numeric: tabular-nums; +} + +.anti-patterns-content { + max-width: 820px; +} + +.anti-patterns-header { + margin-bottom: clamp(2.5rem, 5vw, 4rem); +} + +.anti-patterns-header .sub-page-lede code { + font-family: var(--font-mono); + font-size: 0.875em; + color: var(--color-ink); + background: var(--color-cream); + padding: 2px 6px; + border-radius: 4px; + border: 1px solid var(--color-mist); +} + +.anti-patterns-header .sub-page-lede a { + color: var(--color-ink); + text-decoration: underline; + text-decoration-color: var(--color-accent); + text-decoration-thickness: 1px; + text-underline-offset: 4px; + font-family: var(--font-mono); + font-weight: 500; + font-size: 0.9375em; +} + +.anti-patterns-legend { + padding: var(--spacing-lg); + background: var(--color-cream); + border: 1px solid var(--color-mist); + border-radius: 10px; + margin-bottom: clamp(2.5rem, 5vw, 4rem); + max-width: 720px; +} + +.anti-patterns-legend-title { + font-family: var(--font-display); + font-size: 1.25rem; + font-style: italic; + font-weight: 500; + color: var(--color-ink); + margin-bottom: var(--spacing-sm); +} + +.anti-patterns-legend p { + font-size: 0.9375rem; + line-height: 1.7; + color: var(--color-charcoal); +} + +.anti-patterns-legend a { + color: var(--color-ink); + text-decoration: none; + border-bottom: 1px solid var(--color-accent); + font-family: var(--font-mono); + font-size: 0.875em; + font-weight: 500; +} + +.anti-patterns-legend a:hover { + color: var(--color-accent); +} + +.anti-patterns-sections { + display: flex; + flex-direction: column; + gap: clamp(3rem, 6vw, 4.5rem); +} + +.anti-patterns-section-header { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--spacing-md); + padding-bottom: var(--spacing-sm); + border-bottom: 1px solid var(--color-mist); + margin-bottom: var(--spacing-lg); +} + +.anti-patterns-section-title { + font-family: var(--font-display); + font-size: clamp(1.75rem, 3vw, 2.25rem); + font-weight: 500; + font-style: italic; + color: var(--color-ink); + letter-spacing: -0.01em; +} + +.anti-patterns-section-count { + font-family: var(--font-mono); + font-size: 0.6875rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--color-ash); +} + +.rule-card-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: var(--spacing-md); +} + +.rule-card { + padding: var(--spacing-md); + background: var(--color-paper); + border: 1px solid var(--color-mist); + border-radius: 8px; + display: flex; + flex-direction: column; + gap: 8px; + transition: border-color var(--duration-fast) var(--ease-out); +} + +.rule-card:hover { + border-color: var(--color-ash); +} + +.rule-card-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-sm); + margin-bottom: 2px; +} + +.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-category { + font-family: var(--font-mono); + font-size: 0.625rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.1em; + padding: 3px 8px; + border-radius: 99px; +} + +.rule-card-category[data-category="slop"] { + color: var(--color-accent); + background: var(--color-accent-dim); +} + +.rule-card-category[data-category="quality"] { + color: var(--color-charcoal); + background: var(--color-mist); +} + +.rule-card-name { + font-family: var(--font-body); + font-size: 1rem; + font-weight: 600; + color: var(--color-ink); + line-height: 1.35; +} + +.rule-card-desc { + font-size: 0.875rem; + line-height: 1.6; + color: var(--color-charcoal); + flex: 1; +} + +.rule-card-skill-link { + font-family: var(--font-mono); + font-size: 0.75rem; + font-weight: 500; + color: var(--color-charcoal); + text-decoration: none; + align-self: flex-start; + padding-top: 4px; + transition: color var(--duration-fast) var(--ease-out); +} + +.rule-card-skill-link:hover { + color: var(--color-accent); +} + +.rule-card-skill-link::after { + content: " →"; +} + /* ============================================ MOBILE: collapse sidebar into inline block ============================================ */ diff --git a/scripts/build-sub-pages.js b/scripts/build-sub-pages.js index 5111b5eb0..4a937036c 100644 --- a/scripts/build-sub-pages.js +++ b/scripts/build-sub-pages.js @@ -212,6 +212,124 @@ ${mainHtml} `; } +/** + * Group anti-pattern rules by skill section. + * Rules without a skillSection fall into a 'General quality' bucket. + */ +function groupRulesBySection(rules) { + const order = [ + 'Visual Details', + 'Typography', + 'Color & Contrast', + 'Layout & Space', + 'Motion', + 'General quality', + ]; + const bySection = {}; + for (const name of order) bySection[name] = []; + for (const rule of rules) { + const section = rule.skillSection || 'General quality'; + if (!bySection[section]) bySection[section] = []; + bySection[section].push(rule); + } + // Sort each bucket: slop first (they're the named tells), then quality. + for (const name of Object.keys(bySection)) { + bySection[name].sort((a, b) => { + if (a.category !== b.category) return a.category === 'slop' ? -1 : 1; + return a.name.localeCompare(b.name); + }); + } + return { order, bySection }; +} + +/** + * Render the anti-patterns sidebar: a table of contents of rule sections + * with per-section rule counts. Every entry anchor-jumps to the section + * in the main column. + */ +function renderAntiPatternsSidebar(grouped) { + const entries = grouped.order + .filter((section) => grouped.bySection[section]?.length > 0) + .map((section) => { + const slug = slugify(section); + const count = grouped.bySection[section].length; + return `
${escapeHtml(rule.id)}
+ ${categoryLabel}
+ ${escapeHtml(rule.description)}
+ ${skillLink} +${rules.length} ${rules.length === 1 ? 'rule' : 'rules'}
+${totalRules} detection rules
+These are the visible tells of AI-generated interfaces. Every rule in this catalog is implemented as a deterministic check in npx impeccable detect and in the browser extension. Run /critique on any page to see which ones it triggers.
Rules are grouped by the section of the /impeccable skill that teaches the pattern to avoid. AI slop rules flag the specific visual tells (gradient text, purple palettes, side-tab borders, nested cards). Quality rules flag general design mistakes that are not AI-specific but still hurt the work.
+