From 5316809139d143aa37b3578bcd00f180b6dba6c1 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Thu, 23 Apr 2026 13:13:56 -0700 Subject: [PATCH] feat(site): merge /anti-patterns and /visual-mode into /slop Top nav was at six items and growing. The anti-patterns catalog and the visual-mode overlay demo were always two views of the same subject (the rule set and seeing it caught in place), so collapsing them into one page reduces nav weight and puts the catalog + live demo next to each other. Four sections in one scroll: See it (iframe demo), Try it live (specimen gallery), The catalog (full rule list), Run it yourself (invocation methods). Sidebar TOC nests the catalog sections under "The catalog" for deep linking. 301s from the old URLs preserve external links. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitignore | 1 + content/site/partials/header.html | 3 +- public/css/sub-pages.css | 196 +++++++++++++------- public/index.html | 8 +- public/live-mode/index.html | 3 +- public/privacy.html | 3 +- public/sitemap.xml | 10 +- scripts/build-sub-pages.js | 286 ++++++++++++++++-------------- scripts/build.js | 10 +- scripts/lib/render-page.js | 2 +- scripts/lib/utils.js | 5 +- server/index.js | 7 +- 12 files changed, 310 insertions(+), 224 deletions(-) diff --git a/.gitignore b/.gitignore index 59a14bf60..49d921109 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,7 @@ public/docs/ public/anti-patterns/ public/tutorials/ public/visual-mode/ +public/slop/ # Note: harness skill directories (.claude/skills/, .cursor/skills/, etc.) # are intentionally tracked. npx skills reads them from this repo at install diff --git a/content/site/partials/header.html b/content/site/partials/header.html index d28c67058..c2f496acf 100644 --- a/content/site/partials/header.html +++ b/content/site/partials/header.html @@ -9,9 +9,8 @@ Home Designing Docs - Anti-Patterns + Slop Live - Overlay diff --git a/public/css/sub-pages.css b/public/css/sub-pages.css index a4b577f22..0c5874a13 100644 --- a/public/css/sub-pages.css +++ b/public/css/sub-pages.css @@ -904,27 +904,135 @@ main#main { align-items: baseline; justify-content: space-between; gap: var(--spacing-md); - padding-bottom: var(--spacing-sm); + padding-bottom: 10px; border-bottom: 1px solid var(--color-mist); - margin-bottom: var(--spacing-lg); + margin-bottom: var(--spacing-md); } +/* Catalog subsections sit under the "The catalog" h2 on /slop, so they + must read clearly as h3 — smaller, uppercase mono label instead of + display italic, to avoid competing with the parent section heading. */ .anti-patterns-section-title { - font-family: var(--font-display); - font-size: clamp(1.75rem, 3vw, 2.25rem); - font-weight: 500; - font-style: italic; + font-family: var(--font-mono); + font-size: 0.8125rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.12em; color: var(--color-ink); - letter-spacing: -0.01em; } .anti-patterns-section-count { font-family: var(--font-mono); font-size: 0.6875rem; + font-weight: 500; + letter-spacing: 0.08em; + color: var(--color-ash); +} + +/* ============================================ + SLOP PAGE (/slop) + Merger of the former /anti-patterns + /visual-mode. + Reuses .anti-patterns-* for the catalog and .visual-mode-* + for the demo / gallery / methods blocks; the rules below wire + the four numbered top-level sections and the nested sidebar. + ============================================ */ + +.slop-content { width: 100%; } + +.slop-header { + max-width: 720px; +} + +.slop-section { + margin-top: clamp(3rem, 6vw, 4.5rem); + scroll-margin-top: 80px; +} + +.slop-section-heading { + font-family: var(--font-display); + font-size: clamp(2rem, 3.5vw, 2.75rem); + font-weight: 500; + font-style: italic; + color: var(--color-ink); + letter-spacing: -0.015em; + line-height: 1.15; + margin-bottom: var(--spacing-lg); + padding-bottom: var(--spacing-sm); + border-bottom: 1px solid var(--color-mist); + display: flex; + align-items: baseline; + gap: var(--spacing-md); +} + +.slop-section-num { + font-family: var(--font-mono); + font-size: 0.75rem; font-weight: 600; - text-transform: uppercase; + font-style: normal; letter-spacing: 0.12em; color: var(--color-ash); + text-transform: uppercase; + flex-shrink: 0; +} + +.slop-catalog-header { + margin-bottom: var(--spacing-lg); +} + +.slop-catalog-lede { + font-size: 0.9375rem; + line-height: 1.7; + color: var(--color-charcoal); + max-width: 72ch; + margin-top: var(--spacing-sm); +} + +.slop-catalog-lede strong { + color: var(--color-ink); + font-weight: 600; +} + +.slop-catalog-lede 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; +} + +.slop-catalog-lede a:hover { + color: var(--color-accent); +} + +/* Sidebar: nested catalog-section list under "The catalog". */ +.slop-sidebar-sublist { + list-style: none; + margin: 6px 0 4px 0; + padding: 0 0 0 var(--spacing-md); + border-left: 1px solid var(--color-mist); +} + +.slop-sidebar-sublist li { + margin: 0; +} + +.slop-sidebar-sublist a { + display: flex !important; + align-items: baseline; + justify-content: space-between; + gap: var(--spacing-sm); + font-size: 0.8125rem; + color: var(--color-ash); + padding: 4px 0; +} + +.slop-sidebar-sublist a:hover { + color: var(--color-ink); +} + +.slop-sidebar-sublist .anti-patterns-sidebar-count { + font-size: 0.625rem; } .rule-card-grid { @@ -1275,24 +1383,6 @@ main#main { VISUAL MODE PAGE ============================================ */ -.visual-mode-page-body main#main { - /* Reset the docs-layout override; this page uses its own max-width. */ - max-width: none; - margin: 0; - padding: 0; -} - -.visual-mode-page { - max-width: 1100px; - margin: 0 auto; - padding: clamp(2rem, 4vw, 3.5rem) clamp(var(--spacing-md), 4vw, var(--spacing-2xl)) clamp(4rem, 8vw, 6rem); -} - -.visual-mode-page-header { - max-width: 720px; - margin-bottom: clamp(2.5rem, 5vw, 3.5rem); -} - .visual-mode-demo-wrap { margin-bottom: clamp(3rem, 6vw, 4.5rem); } @@ -1305,12 +1395,11 @@ main#main { text-align: center; } -/* Mac-window chrome around the iframe. Scoped to .visual-mode-page so - the sub-page's preview styles don't leak onto the homepage, which - carries its own .visual-mode-preview rules in main.css. The auto - margins below would disable grid-item stretch on the homepage and - collapse the preview to the iframe's 300px intrinsic width. */ -.visual-mode-page .visual-mode-preview { +/* Mac-window chrome around the iframe. Lives inside sub-pages.css, which + is not loaded on the homepage — the homepage has its own copy in + main.css. The max-width + auto margins would disable grid stretch on + the homepage and collapse the preview to the iframe's intrinsic width. */ +.visual-mode-preview { border-radius: 10px; overflow: hidden; border: 1px solid var(--color-mist); @@ -1319,7 +1408,7 @@ main#main { margin: 0 auto; } -.visual-mode-page .visual-mode-preview-header { +.visual-mode-preview-header { display: flex; align-items: center; gap: 6px; @@ -1328,18 +1417,18 @@ main#main { border-bottom: 1px solid var(--color-mist); } -.visual-mode-page .visual-mode-preview-dot { +.visual-mode-preview-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; } -.visual-mode-page .visual-mode-preview-dot.red { background: #ff5f56; } -.visual-mode-page .visual-mode-preview-dot.yellow { background: #ffbd2e; } -.visual-mode-page .visual-mode-preview-dot.green { background: #27c93f; } +.visual-mode-preview-dot.red { background: #ff5f56; } +.visual-mode-preview-dot.yellow { background: #ffbd2e; } +.visual-mode-preview-dot.green { background: #27c93f; } -.visual-mode-page .visual-mode-preview-title { +.visual-mode-preview-title { margin-left: auto; font-family: var(--font-mono); font-size: 0.75rem; @@ -1354,21 +1443,8 @@ main#main { background: white; } -/* Three invocation methods. */ -.visual-mode-methods { - margin-bottom: clamp(3rem, 6vw, 4.5rem); -} - -.visual-mode-methods-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; - margin-bottom: var(--spacing-lg); -} - +/* Three invocation methods grid. Section wrapper margins come from + .slop-section; this block only controls the grid of cards. */ .visual-mode-methods-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); @@ -1453,26 +1529,18 @@ main#main { color: var(--color-accent); } -/* Gallery (specimens) section */ +/* Gallery (specimens) section header. Title comes from .slop-section-heading; + this block just scopes the lede + spacing under the heading. */ .visual-mode-gallery-header { margin-bottom: var(--spacing-lg); } -.visual-mode-gallery-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; - margin-bottom: var(--spacing-sm); -} - .visual-mode-gallery-lede { font-size: 0.9375rem; line-height: 1.6; color: var(--color-charcoal); max-width: 60ch; + margin-top: var(--spacing-sm); } /* ============================================ diff --git a/public/index.html b/public/index.html index 86989c01a..6dedd6be4 100644 --- a/public/index.html +++ b/public/index.html @@ -60,9 +60,8 @@ Home Designing Docs - Anti-Patterns + Slop Live - Overlay @@ -338,7 +337,7 @@ Bad Contrast - Browse the full catalog → + Browse the full catalog → @@ -1155,9 +1154,8 @@ diff --git a/public/privacy.html b/public/privacy.html index dd8a16008..21421ef0e 100644 --- a/public/privacy.html +++ b/public/privacy.html @@ -33,8 +33,7 @@ diff --git a/public/sitemap.xml b/public/sitemap.xml index eda7a842d..638e3b3e6 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -22,17 +22,11 @@ 0.9 - https://impeccable.style/anti-patterns - 2026-04-10 + https://impeccable.style/slop + 2026-04-23 weekly 0.9 - - https://impeccable.style/visual-mode - 2026-04-10 - weekly - 0.8 - https://impeccable.style/tutorials 2026-04-10 diff --git a/scripts/build-sub-pages.js b/scripts/build-sub-pages.js index 36ca8a383..b29316d71 100644 --- a/scripts/build-sub-pages.js +++ b/scripts/build-sub-pages.js @@ -1,13 +1,14 @@ /** - * Generate static HTML files for /skills, /anti-patterns, /tutorials. + * Generate static HTML files for /docs, /slop, /tutorials, /live-mode, + * /designing. * * Called from both scripts/build.js (before buildStaticSite) and * server/index.js (at module load), so dev and prod share the same * code path and output shape. * - * Output lives under public/docs/, public/anti-patterns/, - * public/tutorials/, all gitignored. Bun's HTML loader picks them up - * the same way it picks up the hand-authored pages. + * Output lives under public/docs/, public/slop/, public/tutorials/, all + * gitignored. Bun's HTML loader picks them up the same way it picks up + * the hand-authored pages. */ import fs from 'node:fs'; @@ -425,31 +426,42 @@ function groupRulesBySection(rules) { } /** - * 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. + * Render the /slop sidebar: a table of contents for the four top-level + * sections (See it / Try it live / The catalog / Run it yourself), with + * the catalog's per-section anchors nested under "The catalog". */ -function renderAntiPatternsSidebar(grouped) { - const entries = grouped.order +function renderSlopSidebar(grouped, gallerySize) { + const catalogEntries = grouped.order .filter((section) => grouped.bySection[section]?.length > 0) .map((section) => { const slug = slugify(section); const count = grouped.bySection[section].length; - return `
  • ${escapeHtml(section)}${count}
  • `; + return `
  • ${escapeHtml(section)}${count}
  • `; }) .join('\n'); + const catalogTotal = grouped.order + .reduce((sum, s) => sum + (grouped.bySection[s]?.length || 0), 0); + return ` -