Spacing
Five tokens, one content column, three breakpoints. The spacing system is deliberately small. Constraints prevent drift.
Spacing scale
Every margin, padding, and gap on the site uses one of five tokens.
| Token | Value | Pixels | Semantic use |
|---|---|---|---|
--space-xs | 0.25rem | 4px | Micro adjustments. Footer link padding on mobile. |
--space-sm | 0.5rem | 8px | Tight spacing. Post list padding, code inline padding, list margins, table cell padding, h3 margin-bottom, footnote item spacing. |
--space-md | 1rem | 16px | Standard unit. Paragraph margins, wrapper horizontal padding, code block padding, share modal gap, footer colophon margin-top, footnotes heading margin-bottom. |
--space-lg | 1.5rem | 24px | Section spacing. Footer padding, blockquote margin, image margin, pull quote padding, article header padding-bottom, intro padding-bottom, footnotes padding-top, share modal padding. |
--space-xl | 3rem | 48px | Major section breaks. Header margin-bottom, h2/h3 margin-top, <hr> vertical margin, wrapper vertical padding, footer margin-top, footnotes margin-top, toast bottom position, intro margin-bottom. |
No in-between values exist. If a spacing decision doesn’t map cleanly to one of these five, the design needs rethinking, not a sixth token.
Content column
--content-width: 42rem
Approximately 672px at 16px base. This produces a reading column of 65—70 characters per line at body text size, the optimal range for sustained reading. This column you’re reading is set to that width.
The value uses rem rather than px so the column scales proportionally if the base font size changes (it drops from 15px to 14px at the <= 600px breakpoint). A pixel-based column would hold its width while the text shrank, breaking the character-count relationship.
The content column is non-negotiable for reading pages. No component should push content wider than --content-width.
Layout structure
The page uses a flex column inside a fixed atmospheric frame. The z-index stack controls layering:
body
.scroll-progress fixed, top z-index: 100
.site-wrapper content column z-index: 2
header.site-header flex, baseline
main flex: 1
footer.site-footer colophon strip
.side-rail fixed, right z-index: 50
.vignette fixed, full-screen z-index: 0
.crop-marks fixed, corners z-index: 99
.paper-grain fixed, full-screen z-index: 1
.toast fixed, bottom-center z-index: 1000
.share-modal-overlay fixed, full-screen z-index: 2000
.site-wrapper centers the content column and establishes the vertical flex layout:
max-width: var(--content-width);
margin: 0 auto;
padding: var(--space-xl) var(--space-md);
min-height: 100vh;
display: flex;
flex-direction: column;
position: relative;
z-index: 2;
main takes flex: 1 to fill remaining vertical space, pushing the footer to the bottom regardless of content length.
The atmospheric layers (vignette, grain, crop marks) sit outside the content column as fixed overlays. They create the paper environment without affecting content flow. See Atmosphere for details.
Responsive breakpoints
Three breakpoints. Each is a discrete step, not a fluid scale.
Above 768px
Full layout. Crop marks visible. Paper grain visible. Default spacing and sizing throughout.
At 768px and below
Wrapper horizontal padding increases from --space-md to --space-lg to give content more breathing room relative to screen edges. Paper grain and crop marks are hidden (performance on mobile, and the atmospheric detail is lost at small sizes anyway).
| Change | Value |
|---|---|
.site-wrapper padding | var(--space-xl) var(--space-lg) |
.paper-grain | display: none |
.crop-marks | display: none |
At 600px and below
The small-screen breakpoint. Base font drops to 14px (from 15px), which cascades through all rem-based values. Desktop typographic details that don’t work at small sizes are disabled.
| Change | Value |
|---|---|
html font-size | 14px |
.site-wrapper padding | var(--space-lg) var(--space-md) |
| Homepage nameplate | 1.8rem (from 2.5rem) |
Article h1 | 1.6rem (from 2rem) |
| Drop cap | Disabled |
.post-list li padding | var(--space-md) 0 (larger touch targets) |
.footer-nav | justify-content: center |
.footer-nav a padding | var(--space-sm) var(--space-sm) (larger tap targets) |
.footer-settings | justify-content: center |
.accent-current | 22px x 22px |
.accent-option | 20px x 20px |
.shortlist-cat | Block layout, width: fit-content |
.share-modal padding | var(--space-md) |
.share-modal-image max-width | 100% |
.share-modal-actions button | min-height: 44px |
The 44px minimum button height at this breakpoint meets the touch target guidance from WCAG 2.5.8.
Do’s and don’ts
| Do | Don’t |
|---|---|
| Use spacing tokens for all margins, padding, gaps | Use pixel values or arbitrary rem values |
Keep reading content within --content-width | Widen the content column for reading pages |
| Test at all three breakpoints | Assume desktop layout works at 600px |
| Let the five-token scale constrain decisions | Add in-between values like --space-md-lg |