SPB Git 3.0 — design system "Monograph"
SPB Git shares the visual language of www.spboucher.ai: an editorial monograph, not a dashboard. Warm paper, ink, one indigo accent, hairlines, square corners, generous white space. Colour carries meaning (links, active state, additions/deletions, languages) and is never decoration.
1. Direction (the fifteen lines)
- Paper and ink. The page is warm paper with a faint graph grid in the top corner; surfaces are a slightly lighter sheet.
- One accent, indigo (
oklch(0.44 0.155 268)on paper). No gradients, no glow, no second brand colour. - Three voices: Fraunces for names, titles and numerals; Inter for text; IBM Plex Mono for labels, metadata and code.
- Labels are small uppercase mono with wide tracking (10.5–11 px, 0.14–0.2 em). Sections open with a short rule and a numbered eyebrow ("01 SELECTED").
- Square corners everywhere; 2 px only softens small controls. Borders are 1 px hairlines; a 2 px ink rule opens a block of figures or a document.
- No shadows at rest. A card lifts 1 px on hover; only dialogs cast a shadow.
- Figures are set like a table of statistics: rule, "01", serif numeral, mono caption — never rounded stat tiles.
- Lists are typographic indexes and ledgers (hairlines, aligned columns, tabular numerals), not grids of identical cards.
- The primary action is ink on paper (black button, paper text), inverted in the dark themes. Secondary actions are outlined; tertiary are text.
- Code is quiet: house syntax themes "paper" and "ink" (ink text, grey italic comments, indigo keywords, sienna strings, ochre constants, teal functions, plum types).
- A README is a document page: a sheet with a measured column and an "On this page" outline beside it.
- Captions speak like a paper: "Fig. 1 — clone over HTTPS".
- Motion is short (120–280 ms) and functional; everything honours
prefers-reduced-motion. - Mobile first: the base styles are the phone layout; nothing scrolls sideways except code, diffs, the calendar and tab strips, inside their own block.
- One icon set (Lucide, ISC, stroke 1.75) and one monogram: the black "SB" tile.
2. Tokens (src/web/assets/css/tokens.css)
Semantic only; components never use raw colours.
| Group | Tokens |
|---|---|
| Surfaces | --bg (paper) · --surface-0 (sheet, cards, code) · --surface-1..3 (bands, hover, tracks) · --bg-elev (dialogs) · --glass, --nav-bg |
| Ink | --fg · --fg-bright · --fg-muted · --fg-faint (all ≥ 4.5:1 on --bg and --surface-0) · --ink / --on-ink (primary button, monogram, selected states) |
| Lines | --border (hairline) · --border-strong (controls) · --rule (2 px ink rules) · --grid-line |
| Accent | --accent · --accent-hover · --accent-soft · --accent-line |
| Status | --ok · --warn · --danger (+ -soft) · --ember (rare warm marker) |
| Diff | --add-bg · --add-bg-strong · --add-text · --del-bg · --del-bg-strong · --del-text · --hunk-bg |
| Code | --code-bg · --code-head; syntax colours come from the shiki themes (--shiki-light, --shiki-dark) |
| Calendar | --heat-0..4 — a single indigo ramp |
| Space | --space-1..11 = 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 88 px |
| Geometry | --radius 2 px (controls) · --radius-lg 0 (cards) · --touch 44 px · --header-h 60/54 px · --container 1200 px · --measure 860 px |
| Motion | --dur-fast 120 ms · --dur 180 ms · --dur-slow 280 ms · --ease · --ease-out |
Themes
| Theme | Paper | Ink | Accent | Use |
|---|---|---|---|---|
light |
oklch(0.972 0.006 85) |
oklch(0.245 0.016 75) |
oklch(0.44 0.155 268) |
the paper of spboucher.ai (default when the OS is light) |
dark |
oklch(0.185 0.008 75) |
oklch(0.925 0.01 85) |
oklch(0.77 0.105 268) |
its night edition (default when the OS is dark) |
midnight |
#000 |
oklch(0.955 0.004 270) |
oklch(0.82 0.1 268) |
true black, maximum contrast, for OLED screens |
theme-boot.js resolves auto | light | dark | midnight from
localStorage['spbgit-theme'] before first paint. Every theme is checked with
scripts/qa/axe.py (WCAG 2.1 AA).
3. Type
| Role | Family | Size / weight | Where |
|---|---|---|---|
| Masthead | Fraunces 600, italic 400 for the name | --text-hero clamp(40, 6vw, 70) / 1.04 |
front page |
| Page title | Fraunces 600 | --text-3xl clamp(30, 3.6vw, 40) |
search, about pages |
| Repository title | Fraunces 600 (owner 400) | clamp(30, 4.6vw, 50); 22–28 on sub-pages | repository header |
| Section title | Fraunces 600 | --text-2xl 28 |
numbered sections |
| Numerals | Fraunces 600, lining tabular | 40 / 36 / 24 | figure rows |
| Names in lists | Fraunces 600 | 19–22 | index rows, cards, results |
| Text | Inter 400/500/600 | --text-base 15 / 1.6; README 16 / 1.65 |
everything else |
| Label | IBM Plex Mono 500, uppercase | --text-2xs 10.5–11, tracking 0.14–0.2 em |
eyebrows, captions, tabs, nav, column heads |
| Meta | IBM Plex Mono 400/500 | 11–12.5, tabular | dates, sizes, SHAs, paths |
| Code | IBM Plex Mono 400 (italic for comments) | 12.75 / 1.7 | blobs, diffs, blame, fences |
Fonts are self-hosted and tracked in git (src/web/assets/fonts/, OFL, latin
subset, from the @fontsource packages): Fraunces variable (opsz + wght) and
italic 400, Inter variable, IBM Plex Mono 400/400i/500/600. Inter and Fraunces
are preloaded; font-display: swap with metric-adjusted fallbacks
(Inter Fallback, Fraunces Fallback, Plex Mono Fallback) so the swap does
not move the layout. scripts/fetch-fonts.mjs only verifies/repairs the files.
4. Components (views/components/ui.njk, assets/css/app.css)
| Component | Markup | Notes |
|---|---|---|
| Icon | ui.icon(name, size) |
Lucide subset, generated path data; add the name to the macro before use; app.js carries the client-side subset (svgIcon) |
| Monogram | .brand-mark |
"SB" in Plex Mono 600 on an ink tile; assets/img/logo.svg and the PNG icons are the same outlines |
| Eyebrow | .eyebrow + .eyebrow-rule |
short accent rule, optional number, mono caps |
| Section head | ui.sectionHead(id, no, eyebrow, title, sub, count) |
caller block = trailing control or link |
| Figure row | ui.statStrip(items, cls) |
.stat-strip-hero (one rule per figure) · .stat-strip-repo (one rule for the row) |
| Label head | .label-head |
mono caps over a 2 px rule: side lists, result groups |
| Button | .btn · .btn-primary · .btn-ghost · .btn-sm · .seg |
38/30 px, 44 px on touch; pressed/active = ink |
| Tag | ui.chip(label, kind) |
square mono tag; topic, accent, ok, warn, danger |
| Tabs | ui.tabs(items, active) |
mono caps, 2 px accent underline, scrolls inside its strip |
| Breadcrumbs / ref picker | ui.breadcrumbs, ui.refSelect |
mono; native <select> for iOS |
| Card | .card (+ .card-head, .card-pad) |
hairline sheet, no radius, no shadow |
| Feature card | partials/repo-card.njk |
numbered plate ("No. 01"), lifts 1 px on hover |
| Index row | partials/repo-row.njk |
CSS counter numbering, serif name, mono facts, sparkline |
| Ledger | .ref-list, .rank-list, .activity-feed, .search-results |
hairline rows, mono meta |
| Plate / figure | .plate, .figure-block + figcaption |
framed specimen with offset grid; captions "Fig. n — …" |
| File list | partials/file-table.njk |
latest-commit strip, 40 px rows |
| Document | .readme-section, .prose-layout, markdown.css |
sheet + outline (.readme-toc, .prose-nav) |
| Code | .code-view, .diff-file, .blame-table |
sticky file header, line numbers behind a hairline |
| States | ui.emptyState, ui.errorState, ui.skeleton, .notice |
dashed frame, serif title; notice = accent bar |
| Dialogs | .sheet, .popover, .cmdk, .confirm, .toast |
bottom sheets under 768 px, ink-bordered panels above |
| Progress | html.is-loading |
2 px accent hairline while the next page loads |
5. Rules
- Run
python3 -I scripts/qa/screens.py <url> <out>(must printOK — 0 failing) andpython3 -I scripts/qa/axe.py <url>(0 violations) before any CSS change ships. - Every grid track is
minmax(0, 1fr); scroll containers are bounded and keyboard-focusable. - Essential controls are ≥ 44 × 44 px on coarse pointers.
- Cards with sticky headers use
overflow: clip, neveroverflow: hidden. - No emoji or text glyphs as icons in the chrome; no gradient fills; no pill shapes.
- Dates show a relative label with the exact instant in
title(dateexactfilter). - Repository authors' Markdown is content: badges keep their natural height, centred blocks stay centred, tables scroll inside themselves.
- Bump
package.jsonon every release: assets are cache-busted with?v=<version>. - Print: README, files and prose print as plain documents (
@media printinapp.css).
6. Screenshots
docs/screenshots/v3/ holds eight before/after pairs of the 2.0 → 3.0 redesign
(before-<page>-<width>-<theme>.png / after-…, development data) and the
production views taken after the deployment (prod-*.png). The 1.1 and 2.0
sets (docs/screenshots/baseline/, docs/screenshots/v2/) were removed from
the tree in 3.0; they remain in the git history (commit 7864e4d).