fix: purge AI slop from judgment essay, add voice linting pipeline
- Killed 16 em-dashes (Claude fingerprint, 15.9/1000 words -> 0) - Replaced all clause-dash-elaboration patterns with periods, colons, restructuring - Removed 'The One-Sentence Version' section (restated the intro, voice anti-pattern) - Broke tricolon at lines 56-58 (too-clean parallel structure) - Collapsed 'How to Actually Help' listicle into connective prose - Added receipt link for ~100x inference cost claim - Heading dashes replaced with colons (Tradesman Analogy, Excellence vs Functional) New tooling: - scripts/lint-voice.sh: mechanical anti-slop linter (em-dashes, trigger words, hedging, filler, receipts, sentence uniformity) - .amplifier/skills/voice-check/SKILL.md: LLM-as-judge voice authenticity check (8 dimensions against VOICE.md profile) - .amplifier/AGENTS.md: standing rule requiring both checks before any publish 🤖 Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,584 @@
|
||||
---
|
||||
feature: DesignTokenSystem
|
||||
date: 2026-05-26
|
||||
status: planned
|
||||
project: crash-test-dev
|
||||
tags: [tokens, system, color, typography, spacing, layout]
|
||||
related: [AESTHETIC-GUIDE.md]
|
||||
---
|
||||
|
||||
# Design Token System — Crash Test Dev
|
||||
|
||||
## User's Spark (preserved)
|
||||
|
||||
> A well-used workshop. Not a pristine showroom — the one where someone actually builds things.
|
||||
> Tools are organized because disorganization wastes time. The wood has warmth.
|
||||
> The light is good because you need to see details. There's a coffee cup somewhere.
|
||||
|
||||
**Metaphor:** The workbench, not the gallery.
|
||||
**Materials vocabulary:** Paper, ink, wood, stone, iron.
|
||||
**Personality:** Dry warmth. Quiet confidence. Evidence over performance.
|
||||
|
||||
---
|
||||
|
||||
## Complete Token System
|
||||
|
||||
```json
|
||||
{
|
||||
"colors": {
|
||||
"_meta": {
|
||||
"temperature": "warm across all values — no blue-grays, no cool neutrals",
|
||||
"philosophy": "Aged paper, brown-black ink, fired earth, tarnished brass. Every color drawn from the workbench vocabulary: materials that have been used, heated, and cooled."
|
||||
},
|
||||
|
||||
"surface": {
|
||||
"base": { "value": "#F5F0E8", "description": "Page background. Heavy unbleached paper stock — warm off-white, not pure white, not yellow." },
|
||||
"card": { "value": "#FEFCF7", "description": "Reading surface. White card stock laid on the paper base — for article bodies, elevated panels." },
|
||||
"code": { "value": "#EEEAE2", "description": "Code block background. Heavier paper weight — subtle warm-gray shift from prose background." },
|
||||
"inset": { "value": "#E8E3DA", "description": "Callout/aside background. Slightly recessed surface, like a different stock weight." }
|
||||
},
|
||||
|
||||
"text": {
|
||||
"primary": { "value": "#2B2421", "description": "Body text. Warm near-black — brown-black ink, NOT blue-black digital default.", "contrastOnBase": "13.44:1 (AAA)", "contrastOnCard": "14.87:1 (AAA)" },
|
||||
"secondary": { "value": "#6B5F55", "description": "Metadata, captions, bylines. Warm mid-gray with stone undertone.", "contrastOnBase": "5.46:1 (AA)", "contrastOnCard": "6.04:1 (AA)" },
|
||||
"tertiary": { "value": "#948880", "description": "Timestamps, tags, disabled text. Lighter warm gray.", "contrastOnBase": "3.04:1 (AA-UI)", "contrastOnCard": "3.36:1 (AA-UI)" }
|
||||
},
|
||||
|
||||
"accent": {
|
||||
"primary": { "value": "#A0522D", "description": "Links, emphasis, code block left-edge accent. Muted terracotta/sienna — fired earth, not bright red.", "contrastOnBase": "4.95:1 (AA)", "contrastOnCard": "5.48:1 (AA)" },
|
||||
"primaryHover": { "value": "#8B4726", "description": "Link hover state. Darker rust — the terracotta after more firing.", "contrastOnBase": "6.12:1 (AA)" },
|
||||
"secondary": { "value": "#7D6C2F", "description": "Special callouts, highlights. Tarnished brass/dark gold — aged metal.", "contrastOnBase": "4.56:1 (AA)", "contrastOnCard": "5.05:1 (AA)" },
|
||||
"secondaryHover": { "value": "#6B5C28", "description": "Brass hover state. Darker, more patinated." }
|
||||
},
|
||||
|
||||
"code": {
|
||||
"text": { "value": "#3D342E", "description": "Code text. Warm dark brown, slightly lighter than body text for the different register.", "contrastOnCode": "10.12:1 (AAA)" }
|
||||
},
|
||||
|
||||
"border": {
|
||||
"subtle": { "value": "#DDD7CD", "description": "Decorative score lines, faint dividers. Barely there — like a fold in paper. No contrast requirement (decorative)." },
|
||||
"default": { "value": "#C2BAA9", "description": "General-purpose separators, card edges. Visible but quiet." },
|
||||
"strong": { "value": "#8E857A", "description": "Functional UI borders (form inputs, focused elements). Meets 3:1 on both base and card.", "contrastOnBase": "3.20:1 (AA-UI)", "contrastOnCard": "3.54:1 (AA-UI)" }
|
||||
},
|
||||
|
||||
"focus": {
|
||||
"ring": { "value": "#A0522D", "description": "Keyboard focus indicator. Terracotta — visible, warm, consistent with accent.", "contrastOnBase": "4.95:1 (AA-UI)" }
|
||||
},
|
||||
|
||||
"state": {
|
||||
"success": {
|
||||
"bg": { "value": "#E8EDDF", "description": "Warm sage green tint." },
|
||||
"text": { "value": "#4A5E3A", "description": "Dark moss green.", "contrastOnBg": "5.96:1 (AA)" },
|
||||
"border": { "value": "#B5C4A0", "description": "Muted sage border." }
|
||||
},
|
||||
"warning": {
|
||||
"bg": { "value": "#F2E8D0", "description": "Warm amber tint." },
|
||||
"text": { "value": "#7A5C1F", "description": "Dark warm amber.", "contrastOnBg": "5.10:1 (AA)" },
|
||||
"border": { "value": "#D4BC7C", "description": "Muted gold border." }
|
||||
},
|
||||
"error": {
|
||||
"bg": { "value": "#F2DED6", "description": "Warm rose/clay tint." },
|
||||
"text": { "value": "#8B3A2A", "description": "Dark terracotta.", "contrastOnBg": "5.92:1 (AA)" },
|
||||
"border": { "value": "#CFA090", "description": "Muted clay border." }
|
||||
},
|
||||
"info": {
|
||||
"bg": { "value": "#E5E2DA", "description": "Warm neutral tint." },
|
||||
"text": { "value": "#4A4540", "description": "Warm dark gray.", "contrastOnBg": "7.32:1 (AAA)" },
|
||||
"border": { "value": "#B8B2A8", "description": "Warm mid gray border." }
|
||||
}
|
||||
},
|
||||
|
||||
"shadow": {
|
||||
"color": { "value": "rgba(43, 36, 33, 0.06)", "description": "Warm-toned shadow base. Derived from text-primary. Barely-there desk shadow." }
|
||||
}
|
||||
},
|
||||
|
||||
"typography": {
|
||||
"_meta": {
|
||||
"philosophy": "Three voices, three jobs. Serif for prose (Ken's voice). Sans for structure (navigation, headings). Mono for evidence (code, proof). The reader always knows which voice is speaking.",
|
||||
"scaleRatio": 1.25,
|
||||
"scaleName": "Major Third",
|
||||
"scaleBase": "18px"
|
||||
},
|
||||
|
||||
"families": {
|
||||
"prose": { "value": "'Source Serif 4', 'Source Serif Pro', Georgia, 'Times New Roman', serif", "role": "Reading voice — body text, blockquotes, long-form prose" },
|
||||
"structure": { "value": "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", "role": "Navigation voice — headings, UI labels, metadata" },
|
||||
"evidence": { "value": "'JetBrains Mono', 'Fira Code', 'SF Mono', Consolas, monospace", "role": "Proof voice — code blocks, terminal output, file paths" }
|
||||
},
|
||||
|
||||
"scale": {
|
||||
"xs": { "value": "0.75rem", "px": 12, "step": -2, "use": "footnotes, legal text" },
|
||||
"sm": { "value": "0.875rem", "px": 14, "step": -1, "use": "captions, meta, timestamps, tags" },
|
||||
"base": { "value": "1.125rem", "px": 18, "step": 0, "use": "body prose" },
|
||||
"lg": { "value": "1.375rem", "px": 22, "step": 1, "use": "h4 headings" },
|
||||
"xl": { "value": "1.75rem", "px": 28, "step": 2, "use": "h3 headings" },
|
||||
"2xl": { "value": "2.25rem", "px": 36, "step": 3, "use": "h2 headings — section titles" },
|
||||
"3xl": { "value": "2.75rem", "px": 44, "step": 4, "use": "h1 headings — article title" }
|
||||
},
|
||||
|
||||
"lineHeight": {
|
||||
"tight": { "value": 1.15, "use": "display headings (h1)" },
|
||||
"snug": { "value": 1.25, "use": "headings (h2, h3)" },
|
||||
"heading": { "value": 1.35, "use": "smaller headings (h4)" },
|
||||
"normal": { "value": 1.5, "use": "UI text, captions" },
|
||||
"relaxed": { "value": 1.6, "use": "code blocks, blockquotes" },
|
||||
"prose": { "value": 1.7, "use": "body prose — generous for dense technical narrative" }
|
||||
},
|
||||
|
||||
"weight": {
|
||||
"regular": { "value": 400, "use": "body prose, code" },
|
||||
"medium": { "value": 500, "use": "UI labels, caption emphasis" },
|
||||
"semibold": { "value": 600, "use": "headings, inline emphasis in prose" },
|
||||
"bold": { "value": 700, "use": "h1 title, strong emphasis" }
|
||||
},
|
||||
|
||||
"styles": {
|
||||
"body": {
|
||||
"fontFamily": "prose",
|
||||
"fontSize": "1.125rem",
|
||||
"fontWeight": 400,
|
||||
"lineHeight": 1.7,
|
||||
"letterSpacing": "normal",
|
||||
"description": "Ken's voice on the page. 18px Source Serif 4 at generous line-height for sustained technical reading."
|
||||
},
|
||||
"bodySmall": {
|
||||
"fontFamily": "prose",
|
||||
"fontSize": "0.875rem",
|
||||
"fontWeight": 400,
|
||||
"lineHeight": 1.6,
|
||||
"letterSpacing": "0.005em",
|
||||
"description": "Footnotes, secondary prose passages. Still serif — still Ken's voice, just quieter."
|
||||
},
|
||||
"h1": {
|
||||
"fontFamily": "structure",
|
||||
"fontSize": "2.75rem",
|
||||
"fontWeight": 700,
|
||||
"lineHeight": 1.15,
|
||||
"letterSpacing": "-0.02em",
|
||||
"description": "Article title. 44px Inter Bold — confident, unhurried. Negative tracking tightens at display size."
|
||||
},
|
||||
"h2": {
|
||||
"fontFamily": "structure",
|
||||
"fontSize": "2.25rem",
|
||||
"fontWeight": 600,
|
||||
"lineHeight": 1.25,
|
||||
"letterSpacing": "-0.015em",
|
||||
"description": "Section heading. 36px Inter Semibold — chapter breaks in the narrative."
|
||||
},
|
||||
"h3": {
|
||||
"fontFamily": "structure",
|
||||
"fontSize": "1.75rem",
|
||||
"fontWeight": 600,
|
||||
"lineHeight": 1.3,
|
||||
"letterSpacing": "-0.01em",
|
||||
"description": "Subsection heading. 28px Inter Semibold — organizes without competing with prose."
|
||||
},
|
||||
"h4": {
|
||||
"fontFamily": "structure",
|
||||
"fontSize": "1.375rem",
|
||||
"fontWeight": 600,
|
||||
"lineHeight": 1.35,
|
||||
"letterSpacing": "-0.005em",
|
||||
"description": "Minor heading. 22px Inter Semibold — just enough hierarchy shift to signal structure."
|
||||
},
|
||||
"blockquote": {
|
||||
"fontFamily": "prose",
|
||||
"fontSize": "1.25rem",
|
||||
"fontWeight": 400,
|
||||
"lineHeight": 1.6,
|
||||
"letterSpacing": "normal",
|
||||
"fontStyle": "italic",
|
||||
"description": "Pull quotes, cited text. 20px Source Serif 4 italic — a gentle size step up signals 'someone else's words' without shouting."
|
||||
},
|
||||
"caption": {
|
||||
"fontFamily": "structure",
|
||||
"fontSize": "0.875rem",
|
||||
"fontWeight": 500,
|
||||
"lineHeight": 1.5,
|
||||
"letterSpacing": "0.01em",
|
||||
"description": "Image captions, metadata labels, timestamps, tags. 14px Inter Medium — the structure voice at small scale."
|
||||
},
|
||||
"codeInline": {
|
||||
"fontFamily": "evidence",
|
||||
"fontSize": "0.9em",
|
||||
"fontWeight": 400,
|
||||
"lineHeight": "inherit",
|
||||
"letterSpacing": "normal",
|
||||
"description": "Inline code within prose. 0.9em relative to parent — the evidence voice embedded in narrative."
|
||||
},
|
||||
"codeBlock": {
|
||||
"fontFamily": "evidence",
|
||||
"fontSize": "1rem",
|
||||
"fontWeight": 400,
|
||||
"lineHeight": 1.6,
|
||||
"letterSpacing": "normal",
|
||||
"description": "Code blocks. 16px JetBrains Mono — factual, verifiable, this-is-what-actually-happened."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"spacing": {
|
||||
"_meta": {
|
||||
"base": "8px",
|
||||
"philosophy": "The page breathes like a well-set book. Generous vertical rhythm for dense technical narrative. Space above H2 is a full breath — section breaks are chapter breaks."
|
||||
},
|
||||
|
||||
"scale": {
|
||||
"0": { "value": "0", "px": 0 },
|
||||
"px": { "value": "1px", "px": 1 },
|
||||
"0.5":{ "value": "0.25rem", "px": 4, "gridMultiple": "0.5x" },
|
||||
"1": { "value": "0.5rem", "px": 8, "gridMultiple": "1x" },
|
||||
"1.5":{ "value": "0.75rem", "px": 12, "gridMultiple": "1.5x" },
|
||||
"2": { "value": "1rem", "px": 16, "gridMultiple": "2x" },
|
||||
"2.5":{ "value": "1.25rem", "px": 20, "gridMultiple": "2.5x" },
|
||||
"3": { "value": "1.5rem", "px": 24, "gridMultiple": "3x" },
|
||||
"4": { "value": "2rem", "px": 32, "gridMultiple": "4x" },
|
||||
"5": { "value": "2.5rem", "px": 40, "gridMultiple": "5x" },
|
||||
"6": { "value": "3rem", "px": 48, "gridMultiple": "6x" },
|
||||
"8": { "value": "4rem", "px": 64, "gridMultiple": "8x" },
|
||||
"10": { "value": "5rem", "px": 80, "gridMultiple": "10x" },
|
||||
"12": { "value": "6rem", "px": 96, "gridMultiple": "12x" }
|
||||
},
|
||||
|
||||
"semantic": {
|
||||
"paragraphGap": { "value": "1.5rem", "px": 24, "note": "~1.33em at 18px body. Generous — lets ideas land." },
|
||||
"headingAboveH2": { "value": "4rem", "px": 64, "note": "Section breaks are chapter breaks. Full breath between sections." },
|
||||
"headingBelowH2": { "value": "1rem", "px": 16, "note": "Tight — the heading belongs to its content, not the space above." },
|
||||
"headingAboveH3": { "value": "2.5rem", "px": 40, "note": "Sub-section breath. Less than H2, still generous." },
|
||||
"headingBelowH3": { "value": "0.75rem", "px": 12, "note": "Snug — content follows immediately." },
|
||||
"headingAboveH4": { "value": "2rem", "px": 32, "note": "Minor heading spacing." },
|
||||
"headingBelowH4": { "value": "0.5rem", "px": 8, "note": "Very tight — H4 is almost inline with its content." },
|
||||
"codeBlockVertical": { "value": "2.5rem", "px": 40, "note": "Code blocks as exhibited artifacts — generous vertical clearance." },
|
||||
"codeBlockPadding": { "value": "1.5rem", "px": 24, "note": "Internal padding of code blocks." },
|
||||
"codeBlockAccentWidth": { "value": "3px", "note": "Left-edge accent line on code blocks." },
|
||||
"listItemGap": { "value": "0.5rem", "px": 8, "note": "Breathing room between list items." },
|
||||
"blockquoteIndent": { "value": "1.5rem", "px": 24, "note": "Left indent/border offset for blockquotes." },
|
||||
"sectionDivider": { "value": "3rem", "px": 48, "note": "Vertical space when using a horizontal rule." }
|
||||
}
|
||||
},
|
||||
|
||||
"rounded": {
|
||||
"_meta": {
|
||||
"philosophy": "Workshop surfaces have worn edges, not machined radii. Just enough softness to not feel sharp, not enough to feel 'designed.'"
|
||||
},
|
||||
"none": { "value": "0" },
|
||||
"sm": { "value": "2px", "use": "inline code, small tags, badges" },
|
||||
"md": { "value": "4px", "use": "code blocks, buttons, small elements" },
|
||||
"lg": { "value": "6px", "use": "cards, callout boxes" },
|
||||
"xl": { "value": "8px", "use": "larger panels, modals (if ever needed)" },
|
||||
"full": { "value": "9999px", "use": "pills, avatars, toggle tracks" }
|
||||
},
|
||||
|
||||
"layout": {
|
||||
"_meta": {
|
||||
"philosophy": "Prose lives in a reading column. Code breaks wider. The width shift signals the register change from narrative to evidence."
|
||||
},
|
||||
"prose": { "value": "45rem", "px": 720, "approxCh": "~65ch at 18px serif", "description": "Reading column max-width. Sweet spot for sustained reading." },
|
||||
"codeBreak": { "value": "52.5rem", "px": 840, "description": "Code block max-width. Wider breakout creates narrow-wide-narrow rhythm." },
|
||||
"container": { "value": "68.75rem","px": 1100, "description": "Outer container max-width. Provides margin for the prose column." },
|
||||
"sideGutter": { "value": "1.5rem", "px": 24, "description": "Minimum horizontal padding on small screens." }
|
||||
},
|
||||
|
||||
"shadows": {
|
||||
"_meta": {
|
||||
"philosophy": "Nothing gleams. Nothing floats. If something casts a shadow, it's the small soft shadow of a card sitting on a desk — barely there."
|
||||
},
|
||||
"sm": { "value": "0 1px 3px rgba(43, 36, 33, 0.04)", "use": "Subtle lift — barely perceptible. Cards at rest." },
|
||||
"md": { "value": "0 2px 8px rgba(43, 36, 33, 0.06)", "use": "Card on a desk — the maximum depth in this system." },
|
||||
"none": { "value": "none", "use": "Explicit no-shadow override." }
|
||||
},
|
||||
|
||||
"motion": {
|
||||
"_meta": {
|
||||
"philosophy": "Fast, barely-there. The confidence of something that doesn't need to announce itself. Reduce to zero for prefers-reduced-motion."
|
||||
},
|
||||
"duration": {
|
||||
"instant": { "value": "0ms", "use": "Reduced motion fallback." },
|
||||
"fast": { "value": "100ms", "use": "Color transitions (hover, focus)." },
|
||||
"normal": { "value": "150ms", "use": "Standard interactions — the site's default." },
|
||||
"slow": { "value": "250ms", "use": "Layout shifts, expand/collapse (rare)." }
|
||||
},
|
||||
"easing": {
|
||||
"default": { "value": "cubic-bezier(0.25, 0.1, 0.25, 1.0)", "use": "Standard ease — smooth, natural deceleration." },
|
||||
"inOut": { "value": "cubic-bezier(0.45, 0.05, 0.55, 0.95)", "use": "Symmetric transitions." }
|
||||
},
|
||||
"reducedMotion": {
|
||||
"rule": "@media (prefers-reduced-motion: reduce)",
|
||||
"behavior": "All durations set to 0ms. All transitions disabled. Non-negotiable."
|
||||
}
|
||||
},
|
||||
|
||||
"rationale": {
|
||||
"colorPhilosophy": {
|
||||
"title": "Why these specific colors",
|
||||
"explanation": [
|
||||
"The surface-base (#F5F0E8) was tuned to sit precisely between 'warm' and 'yellow.' Its RGB channels (245, 240, 232) taper from R→G→B, placing the warmth in the red channel without letting it become cream or beige. It reads as 'good light on heavy paper' — the brief's exact intent.",
|
||||
"Text-primary (#2B2421) avoids the blue-black of browser defaults (which typically live around #1a1a2e or #333). The brown-black comes from keeping R > G > B at the dark end: (43, 36, 33). This is ink with warmth, not ink with coldness.",
|
||||
"The accent-primary (#A0522D, CSS 'sienna') is a historically grounded earth pigment — literally fired earth. At 4.95:1 contrast on base, it passes AA for body text and works as link color without being 'red' or 'alarming.' The hover state (#8B4726) darkens toward the earth rather than toward black.",
|
||||
"Accent-secondary (#7D6C2F) captures tarnished brass — the green-gold of aged copper alloy, not the bright gold of decoration. It needed darkening from the first candidate to meet AA (4.56:1), but the constraint improved the color: real tarnished brass IS darker than you'd first guess.",
|
||||
"State colors (success, warning, error, info) were designed within the warm palette. No saturated primaries. Success uses sage-moss (#4A5E3A on #E8EDDF), warning uses amber (#7A5C1F on #F2E8D0), error uses deeper terracotta (#8B3A2A on #F2DED6). Even the info state avoids blue entirely — it's warm neutral (#4A4540 on #E5E2DA)."
|
||||
]
|
||||
},
|
||||
|
||||
"contrastStrategy": {
|
||||
"title": "How we exceeded WCAG requirements",
|
||||
"explanation": [
|
||||
"The brief demanded WCAG AAA (7:1) for body text on base. We achieved 13.44:1 — nearly double the requirement. This provides comfortable reading even in imperfect lighting conditions or on devices with lower contrast ratios.",
|
||||
"Secondary text at 5.46:1 (AA requires 4.5:1) gives comfortable headroom. Tertiary text at 3.04:1 meets AA-UI (3:1) — appropriate since it's used only for supplementary information like timestamps and tags, never for essential content.",
|
||||
"Code text (#3D342E) on the code surface (#EEEAE2) achieves 10.12:1 (AAA). Code is evidence — it must be maximally readable.",
|
||||
"Border tokens are split into three tiers: subtle (decorative, no contrast requirement — the 'score line'), default (separators), and strong (#8E857A at 3.20:1, for functional UI elements). This acknowledges that a publishing site's dividers are decorative, not interactive, while still providing accessible borders when needed."
|
||||
]
|
||||
},
|
||||
|
||||
"typographyRationale": {
|
||||
"title": "Three voices, mathematically grounded",
|
||||
"explanation": [
|
||||
"The Major Third ratio (1.25) was chosen because the brief explicitly asked for conservative hierarchy. At 1.25, the jump from body (18px) to H4 (22px) is perceptible but not dramatic — the family change from serif to sans does most of the work. Weight and family, not size, create the hierarchy.",
|
||||
"Heading sizes round to clean values: 22, 28, 36, 44px. Each pair's ratio stays within 1.17-1.29 of true Major Third — close enough for mathematical coherence, rounded enough for pixel clarity.",
|
||||
"The 18px body base was set by the brief, and it's correct for sustained technical reading in a serif face. At 1.125rem, it inherits browser zoom gracefully.",
|
||||
"Code at 16px (1rem / 0.9em of body) respects the 'different register' described in the aesthetic guide. JetBrains Mono at 16px with 1.6 line-height gives comfortable code reading without competing with the prose voice.",
|
||||
"Letter-spacing uses negative tracking for headings (-0.02em at h1, tapering to -0.005em at h4) because Inter at display sizes needs tightening to feel intentional. Body prose and code use normal tracking — the fonts were designed for these sizes.",
|
||||
"Blockquote at 20px italic is the only off-scale value — sitting between base (18) and lg (22). This is deliberate: a pull quote should feel like a different register, not a heading. The italic + size shift + left border treatment (defined in spacing) makes the voice unmistakable."
|
||||
]
|
||||
},
|
||||
|
||||
"spacingRationale": {
|
||||
"title": "Book typography meets code breakout",
|
||||
"explanation": [
|
||||
"The 8px grid provides the full range from fine-grain (4px component padding) to macro (96px section spacing). All semantic tokens map onto grid multiples.",
|
||||
"H2 above-spacing (64px / 4rem) creates the 'chapter break' the aesthetic guide describes. At nearly 4x the paragraph gap, it creates a decisive pause in the scroll. H2 below-spacing (16px / 1rem) is deliberately tight — the heading belongs to what follows, not what precedes it.",
|
||||
"Paragraph gap (24px) is ~1.33em at 18px base — within the 1.2-1.5em range specified, and landing on the 8px grid (3x base).",
|
||||
"Code block vertical margin (40px / 2.5rem) positions code as 'exhibited artifacts' — clearly separated from prose without creating visual gaps. The 3px left accent line in terracotta signals the register change from narrative to evidence.",
|
||||
"The prose column at 720px (45rem) produces ~65 characters per line at 18px Source Serif 4 — the typographic sweet spot for sustained reading. Code breaks to 840px (52.5rem) creating the narrow-wide-narrow rhythm described in the aesthetic guide."
|
||||
]
|
||||
},
|
||||
|
||||
"borderRadiusRationale": {
|
||||
"title": "Worn edges, not machined radii",
|
||||
"explanation": [
|
||||
"Four meaningful stops: 2px (inline code), 4px (code blocks, buttons), 6px (cards), 8px (larger panels). The progression feels like increasingly worn edges on workshop materials — slight softening, never roundness.",
|
||||
"No radius exceeds 8px except 'full' (pills/avatars). Nothing on this site should look like a mobile app button or a glassmorphism card."
|
||||
]
|
||||
},
|
||||
|
||||
"motionRationale": {
|
||||
"title": "Fast, barely-there, respectful",
|
||||
"explanation": [
|
||||
"150ms is the site's default transition — fast enough to feel instant, slow enough to not be jarring. Color transitions (hover, focus) at 100ms. Layout transitions at 250ms (rare — this is a reading site, not an app).",
|
||||
"prefers-reduced-motion reduces all durations to 0ms, no exceptions. This is non-negotiable.",
|
||||
"Only GPU-accelerated properties (transform, opacity) should be animated. Color transitions are the exception — they're so fast (100ms) that the CPU cost is negligible."
|
||||
]
|
||||
},
|
||||
|
||||
"whatWeDidNotDo": {
|
||||
"title": "Intentional omissions",
|
||||
"explanation": [
|
||||
"No dark mode tokens. The aesthetic guide explicitly says 'Ken's work happens in daylight.' Dark mode can be added later if needed — the semantic naming makes it straightforward.",
|
||||
"No elevation system. The brief says 'nothing floats.' Two shadow tokens (sm, md) are the maximum depth vocabulary.",
|
||||
"No animation choreography. A publishing site doesn't need entrance animations, scroll-triggered effects, or page transitions. Links change color. That's it.",
|
||||
"No breakpoint tokens. The layout is a single column that narrows gracefully. The prose column (720px) and code breakout (840px) handle their own responsive behavior within the 1100px container.",
|
||||
"No z-index scale. Nothing overlaps. The page is flat paper with ink on it."
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contrast Audit Results
|
||||
|
||||
Every text/background pairing was tested computationally. Full results:
|
||||
|
||||
### Body Text (WCAG AAA — 7:1 required)
|
||||
|
||||
| Pairing | Ratio | Result |
|
||||
|---|---|---|
|
||||
| `text-primary` (#2B2421) on `surface-base` (#F5F0E8) | **13.44:1** | PASS |
|
||||
| `text-primary` (#2B2421) on `surface-card` (#FEFCF7) | **14.87:1** | PASS |
|
||||
| `text-primary` (#2B2421) on `surface-code` (#EEEAE2) | **12.71:1** | PASS |
|
||||
| `text-primary` (#2B2421) on `surface-inset` (#E8E3DA) | **11.93:1** | PASS |
|
||||
|
||||
### Secondary Text (WCAG AA — 4.5:1 required)
|
||||
|
||||
| Pairing | Ratio | Result |
|
||||
|---|---|---|
|
||||
| `text-secondary` (#6B5F55) on `surface-base` (#F5F0E8) | **5.46:1** | PASS |
|
||||
| `text-secondary` (#6B5F55) on `surface-card` (#FEFCF7) | **6.04:1** | PASS |
|
||||
| `text-secondary` (#6B5F55) on `surface-code` (#EEEAE2) | **5.16:1** | PASS |
|
||||
|
||||
### Tertiary Text (WCAG AA UI — 3:1 required)
|
||||
|
||||
| Pairing | Ratio | Result |
|
||||
|---|---|---|
|
||||
| `text-tertiary` (#948880) on `surface-base` (#F5F0E8) | **3.04:1** | PASS |
|
||||
| `text-tertiary` (#948880) on `surface-card` (#FEFCF7) | **3.36:1** | PASS |
|
||||
|
||||
### Accent Links (WCAG AA — 4.5:1 required)
|
||||
|
||||
| Pairing | Ratio | Result |
|
||||
|---|---|---|
|
||||
| `accent-primary` (#A0522D) on `surface-base` (#F5F0E8) | **4.95:1** | PASS |
|
||||
| `accent-primary` (#A0522D) on `surface-card` (#FEFCF7) | **5.48:1** | PASS |
|
||||
| `accent-primary` (#A0522D) on `surface-code` (#EEEAE2) | **4.68:1** | PASS |
|
||||
| `accent-primaryHover` (#8B4726) on `surface-base` (#F5F0E8) | **6.12:1** | PASS |
|
||||
| `accent-secondary` (#7D6C2F) on `surface-base` (#F5F0E8) | **4.56:1** | PASS |
|
||||
| `accent-secondary` (#7D6C2F) on `surface-card` (#FEFCF7) | **5.05:1** | PASS |
|
||||
|
||||
### Code Text (WCAG AAA — 7:1 required)
|
||||
|
||||
| Pairing | Ratio | Result |
|
||||
|---|---|---|
|
||||
| `code-text` (#3D342E) on `surface-code` (#EEEAE2) | **10.12:1** | PASS |
|
||||
|
||||
### State Colors (WCAG AA — 4.5:1 required)
|
||||
|
||||
| Pairing | Ratio | Result |
|
||||
|---|---|---|
|
||||
| `success-text` (#4A5E3A) on `success-bg` (#E8EDDF) | **5.96:1** | PASS |
|
||||
| `warning-text` (#7A5C1F) on `warning-bg` (#F2E8D0) | **5.10:1** | PASS |
|
||||
| `error-text` (#8B3A2A) on `error-bg` (#F2DED6) | **5.92:1** | PASS |
|
||||
| `info-text` (#4A4540) on `info-bg` (#E5E2DA) | **7.32:1** | PASS |
|
||||
|
||||
### Functional Borders (WCAG AA UI — 3:1 required)
|
||||
|
||||
| Pairing | Ratio | Result |
|
||||
|---|---|---|
|
||||
| `border-strong` (#8E857A) on `surface-base` (#F5F0E8) | **3.20:1** | PASS |
|
||||
| `border-strong` (#8E857A) on `surface-card` (#FEFCF7) | **3.54:1** | PASS |
|
||||
|
||||
**Note:** `border-subtle` and `border-default` are decorative (score lines, separators) and do not carry WCAG contrast requirements per [WCAG 1.4.11 Non-text Contrast](https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html) — decorative borders that don't convey information are exempt. `border-strong` is provided for any functional UI borders that DO need 3:1.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### CSS Custom Properties (recommended structure)
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Surfaces */
|
||||
--surface-base: #F5F0E8;
|
||||
--surface-card: #FEFCF7;
|
||||
--surface-code: #EEEAE2;
|
||||
--surface-inset: #E8E3DA;
|
||||
|
||||
/* Text */
|
||||
--text-primary: #2B2421;
|
||||
--text-secondary: #6B5F55;
|
||||
--text-tertiary: #948880;
|
||||
|
||||
/* Accents */
|
||||
--accent-primary: #A0522D;
|
||||
--accent-primary-hover: #8B4726;
|
||||
--accent-secondary: #7D6C2F;
|
||||
--accent-secondary-hover: #6B5C28;
|
||||
|
||||
/* Code */
|
||||
--code-text: #3D342E;
|
||||
|
||||
/* Borders */
|
||||
--border-subtle: #DDD7CD;
|
||||
--border-default: #C2BAA9;
|
||||
--border-strong: #8E857A;
|
||||
|
||||
/* Focus */
|
||||
--focus-ring: #A0522D;
|
||||
|
||||
/* State: Success */
|
||||
--success-bg: #E8EDDF;
|
||||
--success-text: #4A5E3A;
|
||||
--success-border: #B5C4A0;
|
||||
|
||||
/* State: Warning */
|
||||
--warning-bg: #F2E8D0;
|
||||
--warning-text: #7A5C1F;
|
||||
--warning-border: #D4BC7C;
|
||||
|
||||
/* State: Error */
|
||||
--error-bg: #F2DED6;
|
||||
--error-text: #8B3A2A;
|
||||
--error-border: #CFA090;
|
||||
|
||||
/* State: Info */
|
||||
--info-bg: #E5E2DA;
|
||||
--info-text: #4A4540;
|
||||
--info-border: #B8B2A8;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 3px rgba(43, 36, 33, 0.04);
|
||||
--shadow-md: 0 2px 8px rgba(43, 36, 33, 0.06);
|
||||
|
||||
/* Typography families */
|
||||
--font-prose: 'Source Serif 4', 'Source Serif Pro', Georgia, 'Times New Roman', serif;
|
||||
--font-structure: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--font-evidence: 'JetBrains Mono', 'Fira Code', 'SF Mono', Consolas, monospace;
|
||||
|
||||
/* Spacing */
|
||||
--space-0: 0;
|
||||
--space-px: 1px;
|
||||
--space-0-5: 0.25rem;
|
||||
--space-1: 0.5rem;
|
||||
--space-1-5: 0.75rem;
|
||||
--space-2: 1rem;
|
||||
--space-2-5: 1.25rem;
|
||||
--space-3: 1.5rem;
|
||||
--space-4: 2rem;
|
||||
--space-5: 2.5rem;
|
||||
--space-6: 3rem;
|
||||
--space-8: 4rem;
|
||||
--space-10: 5rem;
|
||||
--space-12: 6rem;
|
||||
|
||||
/* Border radius */
|
||||
--rounded-none: 0;
|
||||
--rounded-sm: 2px;
|
||||
--rounded-md: 4px;
|
||||
--rounded-lg: 6px;
|
||||
--rounded-xl: 8px;
|
||||
--rounded-full: 9999px;
|
||||
|
||||
/* Layout */
|
||||
--width-prose: 45rem;
|
||||
--width-code: 52.5rem;
|
||||
--width-container: 68.75rem;
|
||||
--gutter: 1.5rem;
|
||||
|
||||
/* Motion */
|
||||
--duration-fast: 100ms;
|
||||
--duration-normal: 150ms;
|
||||
--duration-slow: 250ms;
|
||||
--ease-default: cubic-bezier(0.25, 0.1, 0.25, 1.0);
|
||||
--ease-in-out: cubic-bezier(0.45, 0.05, 0.55, 0.95);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
:root {
|
||||
--duration-fast: 0ms;
|
||||
--duration-normal: 0ms;
|
||||
--duration-slow: 0ms;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Font Loading (recommended)
|
||||
|
||||
```html
|
||||
<!-- Preconnect -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
|
||||
<!-- Fonts — load only weights used -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,wght@0,400;0,600;1,400&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- **Functional:** All tokens resolve to concrete CSS values. Zero undefined variables.
|
||||
- **Aesthetic:** Every color reads warm. The page feels like paper and ink under good light.
|
||||
- **Accessibility:** AAA body text (13.44:1), AA secondary (5.46:1), AA accent links (4.95:1). All verified.
|
||||
- **Performance:** Three fonts, minimal weights. No heavy shadow/blur calculations.
|
||||
- **Longevity:** Nothing here will look dated in 5 years. Paper, ink, and careful spacing don't go out of style.
|
||||
Reference in New Issue
Block a user