From d03dca1209a41940324d61bb6751b679a0c796a8 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Wed, 22 Apr 2026 11:52:40 -0700 Subject: [PATCH] fix(site): restore Antidote section with curated anti-pattern list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DO/DONT extractor in readPatterns stopped finding anything after the skill went human-authored (new SKILL.md uses plain bullets, one-word section headings, no DO:/DON'T: prefixes). Replace the extractor with a hand-curated category list: six categories, three or four DOs and DON'Ts each. Editorial tone, tight, deliberately a teaser — the full catalog still lives on /anti-patterns. The legacy SKILL.md parser is retained in the file as _legacyReadPatterns in case we want to revive it later with a different format. Small CSS fix: .faq-question was display:flex with justify-content: space-between, so inline in a summary got treated as its own flex item and pushed apart. Switched to relative+absolute positioning so the + icon sits in the right margin and text flows naturally. Changelog font size reduced to 0.9375rem for the expanded v3.0 entry. New FAQ entry on pinning standalone commands back. Pin section added to /docs/impeccable editorial. --- public/css/workflow.css | 12 ++--- scripts/lib/utils.js | 97 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/public/css/workflow.css b/public/css/workflow.css index 55da43f0f..cbdf3c5cf 100644 --- a/public/css/workflow.css +++ b/public/css/workflow.css @@ -1247,12 +1247,10 @@ font-size: 1.125rem; font-weight: 500; color: var(--color-ink); - padding: var(--spacing-md) 0; + padding: var(--spacing-md) calc(var(--spacing-md) + 1.5rem) var(--spacing-md) 0; cursor: pointer; list-style: none; - display: flex; - align-items: center; - justify-content: space-between; + position: relative; transition: color 0.2s ease; } @@ -1267,10 +1265,14 @@ font-weight: 300; color: var(--color-accent); transition: transform 0.3s var(--ease-out); + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); } .faq-item[open] .faq-question::after { - transform: rotate(45deg); + transform: translateY(-50%) rotate(45deg); } .faq-question:hover { diff --git a/scripts/lib/utils.js b/scripts/lib/utils.js index 9f1c1d764..9c060894a 100644 --- a/scripts/lib/utils.js +++ b/scripts/lib/utils.js @@ -226,7 +226,102 @@ export function writeFile(filePath, content) { * * Returns { patterns: [...], antipatterns: [...] } */ -export function readPatterns(rootDir, relativePath = 'source/skills/impeccable/SKILL.md') { +// Curated short-list for the homepage Antidote section. Intentionally +// hand-written (not auto-extracted) so the copy stays tight and +// editorial. The long-form catalog lives on /anti-patterns — this is +// the teaser. +const CURATED_CATEGORIES = [ + { + name: 'Typography', + do: [ + 'Pair a distinctive display face with a restrained body face; vary across projects.', + 'Use a ≥1.25 scale ratio between hierarchy steps. Flat scales read as bland.', + 'Cap body line length at 65–75ch. Wider is fatiguing.', + ], + dont: [ + 'Inter, Roboto, Plex, Fraunces, or any other reflex default. Look further.', + 'Monospace as lazy shorthand for "technical."', + 'Long passages in uppercase. Reserve all-caps for short labels.', + ], + }, + { + name: 'Color & Contrast', + do: [ + 'Use OKLCH. Reduce chroma near lightness extremes.', + 'Tint neutrals toward the brand hue. Chroma 0.005–0.01 is enough.', + 'Pick a color strategy before picking colors (Restrained, Committed, Full, Drenched).', + ], + dont: [ + 'Pure #000 or #fff. Always tint.', + 'Dark mode + purple-to-cyan gradients. The AI tell.', + 'Gradient text via background-clip. Use weight or size for emphasis.', + ], + }, + { + name: 'Layout & Space', + do: [ + 'Vary spacing for rhythm. Tight groupings, generous separations.', + 'Use the simplest tool: Flexbox for 1D, Grid for 2D, plain flow often enough.', + 'Let whitespace carry hierarchy before reaching for color or scale.', + ], + dont: [ + 'Wrap everything in cards. Nested cards are always wrong.', + 'Identical card grids of icon + heading + text, repeated endlessly.', + 'The hero-metric template: big number, small label, supporting stats, gradient accent.', + ], + }, + { + name: 'Visual Details', + do: [ + 'Commit to an aesthetic direction and execute it with precision.', + 'Use ornament only where it earns its place.', + ], + dont: [ + 'Side-stripe borders (border-left/-right > 1px). The dashboard tell.', + 'Glassmorphism everywhere. Rare and purposeful or nothing.', + 'Rounded rectangles with generic drop shadows. "Could be any AI output."', + ], + }, + { + name: 'Motion', + do: [ + 'Use transform and opacity. Animate the composited properties only.', + 'Ease out with exponential curves (quart / quint / expo).', + 'Respect prefers-reduced-motion on every transition.', + ], + dont: [ + 'Animate layout (width, height, padding, margin).', + 'Bounce or elastic easing. Feels dated and tacky.', + 'Decorative motion for its own sake. Motion should signal state.', + ], + }, + { + name: 'Interaction', + do: [ + 'Use optimistic UI: update immediately, sync later.', + 'Design empty states that teach the interface, not just say "nothing here."', + 'Progressive disclosure: start simple, reveal sophistication on demand.', + ], + dont: [ + 'Make every button primary. Hierarchy matters.', + 'Default to a modal. Exhaust inline alternatives first.', + 'Repeat information the user can already see.', + ], + }, +]; + +export function readPatterns(_rootDir, _relativePath) { + // Hand-curated list — see CURATED_CATEGORIES above. The homepage + // Antidote teaser uses this; the full catalog lives on /anti-patterns. + return { + patterns: CURATED_CATEGORIES.map((c) => ({ name: c.name, items: c.do })), + antipatterns: CURATED_CATEGORIES.map((c) => ({ name: c.name, items: c.dont })), + }; +} + +// Previous SKILL.md parser retained below but disabled; kept as a +// reference for how prefix-style extraction used to work. +function _legacyReadPatterns(rootDir, relativePath = 'source/skills/impeccable/SKILL.md') { const skillPath = path.join(rootDir, relativePath); if (!fs.existsSync(skillPath)) {