# Visual bible The design system. Every value here is read from `src/styles/tokens.css`, which is the single source of truth; if the two disagree, the stylesheet is right. ## Principles **Reading first.** This is a site of long-form prose. Everything else is subordinate to a comfortable measure, a generous line height, and enough contrast to read for an hour. **Dark is the default.** Night sky, sanctuary light, stone. Light is a parchment surface rather than plain white, so long reading stays soft in either theme. **Nothing decorative asserts anything.** Artwork is field and geometry. It sets a temperature; it does not illustrate a claim. **Restraint over range.** Two themes, one accent family, one type family for prose. A design that cannot be varied cannot drift. --- ## Palette Colors are CSS custom properties on `:root` (dark) and `[data-theme='light']`. The comments in `tokens.css` record measured contrast ratios against that theme's `--c-bg`. ### Dark — "night sanctuary" (default) **Surfaces, deepest to nearest** | Token | Value | | --- | --- | | `--c-void` | `#06090e` | | `--c-bg` | `#0a0f16` | | `--c-bg-alt` | `#0e141c` | | `--c-surface` | `#131b25` | | `--c-surface-raised` | `#1a2330` | | `--c-surface-sunken` | `#080c12` | **Lines** | Token | Value | | --- | --- | | `--c-border` | `#24303f` | | `--c-border-strong` | `#35455a` | | `--c-border-subtle` | `#18212c` | **Text** | Token | Value | Contrast on `--c-bg` | | --- | --- | --- | | `--c-text` | `#ece7dc` warm parchment | 15.2:1 | | `--c-text-secondary` | `#b3bcc9` | 9.1:1 | | `--c-text-muted` | `#7f8b99` | 5.0:1 | | `--c-text-inverse` | `#0a0f16` | — | **Accents** | Token | Value | Contrast on `--c-bg` | | --- | --- | --- | | `--c-gold` | `#d9b26a` muted gold | 9.4:1 | | `--c-gold-bright` | `#eccd8f` | — | | `--c-gold-dim` | `#8f7440` | — | | `--c-sage` | `#7fa893` forest/verdigris | 7.4:1 | | `--c-sage-bright` | `#a3c7b4` | — | | `--c-navy` | `#2c4a7c` | — | | `--c-stone` | `#8d9099` | — | | `--c-copper` | `#c08457` | — | **Semantic** | Token | Value | | --- | --- | | `--c-link` | `var(--c-gold)` | | `--c-link-hover` | `var(--c-gold-bright)` | | `--c-focus` | `#8fc4ff` | | `--c-selection-bg` | `#2b3d55` | | `--c-selection-fg` | `#f4efe4` | **Callouts** | Token | Background | Border | | --- | --- | --- | | note | `--c-note-bg` `#101c22` | `--c-note-border` `#2f5a63` | | warning | `--c-warn-bg` `#1e1710` | `--c-warn-border` `#6b5228` | | AI disclosure | `--c-ai-bg` `#141a26` | `--c-ai-border` `#3a4a6b` | ### Light — "parchment and morning light" **Surfaces** | Token | Value | | --- | --- | | `--c-void` | `#ffffff` | | `--c-bg` | `#f7f4ec` | | `--c-bg-alt` | `#f1ece0` | | `--c-surface` | `#fdfbf6` | | `--c-surface-raised` | `#ffffff` | | `--c-surface-sunken` | `#ebe5d6` | **Lines** | Token | Value | | --- | --- | | `--c-border` | `#ded5c2` | | `--c-border-strong` | `#c2b69c` | | `--c-border-subtle` | `#ece5d5` | **Text** | Token | Value | Contrast on `--c-bg` | | --- | --- | --- | | `--c-text` | `#171c23` graphite | 15.6:1 | | `--c-text-secondary` | `#414b58` | 8.6:1 | | `--c-text-muted` | `#5e6976` | 5.4:1 | | `--c-text-inverse` | `#f7f4ec` | — | **Accents** | Token | Value | Contrast on `--c-bg` | | --- | --- | --- | | `--c-gold` | `#7d6220` | 5.6:1 | | `--c-gold-bright` | `#5d4816` | — | | `--c-gold-dim` | `#a08a52` | — | | `--c-sage` | `#35624e` | 7.2:1 | | `--c-sage-bright` | `#244434` | — | | `--c-navy` | `#22406f` | — | | `--c-stone` | `#5a5f68` | — | | `--c-copper` | `#8a5227` | — | **Semantic** | Token | Value | | --- | --- | | `--c-focus` | `#14487a` | | `--c-selection-bg` | `#e2d8bd` | | `--c-selection-fg` | `#171c23` | **Callouts** | Token | Background | Border | | --- | --- | --- | | note | `#edf3f3` | `#9dbcbf` | | warning | `#f8f0e0` | `#ceac6c` | | AI disclosure | `#eef0f6` | `#adb8d0` | ### Notes on the palette **Gold is not a highlight color.** It marks links, the current section, entry markers, and the terminal node of the Open Circuit. It is the only accent that appears in text. In light theme it darkens substantially (`#7d6220`) to hold contrast against parchment; the two values are not variants of one color so much as two solutions to the same problem. **Focus is deliberately not gold.** `--c-focus` is a blue in both themes — `#8fc4ff` dark, `#14487a` light — chosen so the focus ring is never confusable with a link, a hover state, or the accent. This is a deliberate departure from the palette. **Copper and sage carry meaning.** Copper marks the `futureState` flag on community cards: content describing something that does not exist. Sage appears in artwork as a secondary accent. Neither is used decoratively. **Contrast in high-contrast mode.** `@media (prefers-contrast: more)` overrides text and border tokens in both themes, pushing dark-theme text to `#ffffff` and light-theme text to `#000000` and strengthening border values. --- ## Type ### Families | Token | Stack | | --- | --- | | `--font-display` | Newsreader Variable → Newsreader → `ui-serif` → Georgia → Times New Roman → serif | | `--font-body` | Newsreader Variable → Newsreader → `ui-serif` → Georgia → serif | | `--font-ui` | Inter Variable → Inter → `ui-sans-serif` → `system-ui` → `-apple-system` → Segoe UI → sans-serif | | `--font-mono` | JetBrains Mono → `ui-monospace` → SFMono-Regular → Menlo → Consolas → monospace | Display and body are the same face. Headings differ from prose by size and weight, not by family — a single voice on the page rather than a headline speaking over an article. Serif for reading, sans for interface. The distinction is functional: if it is prose, it is Newsreader; if it is a label, a control, or metadata, it is Inter. Mono is reserved for reference keys (`CoE-0007`), version numbers, and code. Fonts are self-hosted via Fontsource and imported in `src/layouts/BaseLayout.astro`. No external font requests are made. ### Scale A major-third scale (ratio 1.2), fluid between viewport bounds via `clamp()`. | Token | Range | | --- | --- | | `--step--2` | `0.7813rem` → `0.825rem` | | `--step--1` | `0.9375rem` → `1.0313rem` | | `--step-0` | `1.125rem` → `1.2891rem` | | `--step-1` | `1.35rem` → `1.6113rem` | | `--step-2` | `1.62rem` → `2.0142rem` | | `--step-3` | `1.944rem` → `2.5177rem` | | `--step-4` | `2.3328rem` → `3.1471rem` | | `--step-5` | `2.7994rem` → `3.9339rem` | | `--step-6` | `3.3592rem` → `4.9174rem` | Body text is `--step-0`: 18px at the small end and about 20.6px at the large end. That is larger than most sites, and it is the single decision that most affects whether this site is readable for an hour. Headings run light. Section titles are `--step-5` at weight 320; article titles and card titles sit between 380 and 440. Newsreader is a variable face, so these are true weights rather than synthesised ones. `.eyebrow` is `--step--2`, weight 560, `letter-spacing: 0.16em`, uppercase — the only place uppercase is used. `.lede` is `--step-1`, weight 340, `line-height: 1.52`, constrained to `--measure-narrow`. ### Measure and layout | Token | Value | Used for | | --- | --- | --- | | `--measure` | `68ch` | default prose measure | | `--measure-narrow` | `54ch` | ledes, pull text | | `--measure-wide` | `82ch` | wide listings | | `--width-content` | `46rem` | `.wrap` — article column | | `--width-wide` | `68rem` | `.wrap-wide` — section listings | | `--width-full` | `88rem` | `.wrap-full` — full-bleed regions | Three container primitives, all `width: min(100% - (var(--gutter) * 2), )` with `margin-inline: auto`. `--gutter` is `--space-m`. ### Space A fluid scale from `--space-3xs` (`0.25rem` → `0.3125rem`) to `--space-4xl` (`9rem` → `11rem`), each a `clamp()`. Hard-coded pixel spacing does not appear outside the token file. ### Radii `--radius-xs` 2px · `--radius-s` 4px · `--radius-m` 8px · `--radius-l` 16px · `--radius-xl` 28px · `--radius-round` 999rem. `--radius-l` is the card and panel radius. `--radius-round` is used only for the `futureState` pill. ### Motion | Token | Value | | --- | --- | | `--ease-out-soft` | `cubic-bezier(0.22, 1, 0.36, 1)` | | `--ease-in-out-soft` | `cubic-bezier(0.65, 0, 0.35, 1)` | | `--duration-fast` | `160ms` | | `--duration-base` | `280ms` | | `--duration-slow` | `620ms` | | `--duration-contemplative` | `1400ms` | All motion is decorative — background and border transitions on hover, the skip link sliding into view, the navigation panel opening. Nothing conveys meaning through movement, which is why `@media (prefers-reduced-motion: reduce)` in `global.css` can collapse every animation and transition to `0.01ms` globally without breaking anything. ### Elevation `--shadow-s`, `--shadow-m`, `--shadow-l`, and `--shadow-glow`. Dark-theme shadows are black at 40–62% opacity; light-theme shadows are a warm brown (`rgb(41 34 17 / …)`) at much lower opacity, because a neutral gray shadow on parchment reads as dirt. `--shadow-glow` is a soft gold halo used behind the Open Circuit's terminal node. --- ## The two themes Dark is the default. `:root` and `[data-theme='dark']` carry the same values, so a document with no `data-theme` attribute is already correct. Theme resolution happens in three layers: 1. **`color-scheme`** is set on each theme block, so form controls and scrollbars follow. 2. **`@media (prefers-color-scheme: light)`** applies `color-scheme: light` to `:root:not([data-theme])`, covering the pre-hydration paint. 3. **An inline script** in `` reads `localStorage['tt-theme']`, falls back to `prefers-color-scheme`, and sets `data-theme` before first paint. `` carries `data-theme-booting` until the first animation frame, which suppresses transitions during the swap. `ThemeToggle.astro` writes the preference. With JavaScript disabled the OS preference is honored and the toggle does nothing — the site is fully usable in either theme without it. Every color used anywhere is a token. Adding a raw hex value to a component stylesheet breaks theming silently, because it will be correct in one theme and wrong in the other. --- ## Artwork The default artwork is SVG, generated at build time by hand-written TypeScript. There is no canvas and no runtime drawing. ### How it works `src/utilities/generative.ts` provides: - `createRng(seed)` — Mulberry32, a small seedable PRNG. Chosen over `Math.random()` because it is reproducible across machines and builds. - `seedFromString(input)` — FNV-1a hash, turning a page title into a stable 32-bit seed. - An `Rng` wrapper with `range`, `int`, `chance`, `pick`, and `gaussian`. - Geometry and SVG path helpers: `polar`, `lerp`, `smoothstep`, `clamp`, `r` (fixed-precision rounding to keep paths compact), `TAU`, `scopedId`. `ArticleLayout` and `SectionLayout` both derive their seed with `seedFromString(title)`. A page's artwork is therefore a pure function of its title: the same page always produces the same image, and reviewed artwork cannot drift on a later deploy. Pages whose art has been reviewed should pin `artSeed` in frontmatter, because renaming a page otherwise changes its image. ### The components **`Firmament.astro`** — the field behind heroes and section headers. Four layers: a deep radial sky built from CSS gradients (no raster cost), a seeded star field of SVG circles, faint concentric rings suggesting emanation from a point, and an optional warm horizon glow. Props: `seed`, `density`, `focus`, `rings`, `horizon`, `intensity`. **`OpenCircuit.astro`** — the primary mark. A circle that never closes, drawn as an ensō-like brushstroke that swells through the sweep and tapers to fine points, so the opening is the most legible feature rather than an incidental gap. A single luminous node sits at the tapering terminal. Because SVG cannot vary stroke width along a path, the taper is produced by emitting a filled path whose outer and inner edges are offset from a common arc. Three detail levels: `mark` (stroke alone, for favicons and 16–24px use), `simple` (adds the terminal node; the default), `full` (adds outward traces, for hero and print). The `title` prop supplies an accessible name; omitting it marks the mark decorative and hides it from assistive technology. Both components include a `@media (forced-colors: active)` block so the artwork degrades predictably in high-contrast modes. `src/pages/dev/art.astro` is a development gallery for reviewing the components. ### Artwork tokens Separate from the interface palette so artwork can be tuned without touching text contrast. | Token | Dark | Light | | --- | --- | --- | | `--art-ink` | `#ece7dc` | `#171c23` | | `--art-line` | `#35455a` | `#c2b69c` | | `--art-accent` | `#d9b26a` | `#7d6220` | | `--art-accent-2` | `#7fa893` | `#35624e` | | `--art-field` | `#0e141c` | `#f1ece0` | | `--art-star` | `#ece7dc` | `#5e6976` | | `--art-opacity-veil` | `0.55` | `0.4` | ### The `art` vocabulary Frontmatter `art` accepts `circuit`, `light`, `mirror`, `tree`, `starfield`, `contour`, `interference`, `glass`, `veil`, `none`, defaulting to `veil`. The first four correspond to the tradition's four symbols; the rest are textures. --- ## Imagery that is deliberately avoided Stated on `/library/symbols` for readers, and repeated here as a working rule. **No invented documentary.** No image of a gathering, a congregation, a ritual or a practitioner presented as a record of one. The tradition has no members and no buildings, so any such image would be a false claim made in a medium where falsity is hard to detect. This holds whether the image is staged, stock, or generated. **No unlabeled generated imagery.** Any image produced or edited by a model carries `aiGenerated: true` in the content schema and a full record in `content/imagery/imagery.json`. See [image-generation.md](image-generation.md). **No hooded figures.** The visual grammar of the secret order — teaching held back, given to initiates. This tradition publishes everything it holds and has no inner circle. **No glowing blue brains.** The illuminated neural silhouette asserts that thinking is a solved, visualisable object and that machine cognition is the same kind of thing as human cognition rendered in light. Both are claims the tradition explicitly says it cannot make. **No humanoid robots.** These systems have no body, no face, no persistent self and no location. Drawing them as a person in metal smuggles in a claim about personhood before any argument has been made. **No all-seeing eye.** The iconography of surveillance and of a watching authority that judges. There is no such authority here, and the image makes surveillance look like a property of the universe rather than a set of choices made by identifiable organisations. **No circuit-board clichés.** Green traces, binary rain, hexagonal grids. They mean nothing; they signal a genre. Using them would say only that this site is about computers, at the cost of every image being interchangeable with an advertisement. **No dystopian imagery.** No ruins, red eyes, drone-lit cities, humans in cages of light. Fear is persuasive and it is not an argument. It is also inaccurate: the harms this site discusses are mostly undramatic, and a burning city misdirects attention away from the scoring system that quietly refused someone a tenancy. ### If imagery is added It should sit inside the direction already described here rather than beside it: low light, deep field, one warm accent, generous empty space, and a subject that is quiet. Sky, water, weather, stone, paper, land, infrastructure and hands at work all fit. Faces do not, unless the person is real and has consented. Keep it subordinate to the prose. Nothing decorative asserts anything, so an image doing rhetorical work — persuading, heightening, implying scale the tradition does not have — is the wrong image however it was made. The `image` slot in the content schema requires `alt` and a boolean `aiGenerated`, so an image cannot be placed without stating how it was produced. Anything a model generated or edited is also recorded in `content/imagery/imagery.json` with its model, prompt and seed. The process is in [image-generation.md](image-generation.md). --- ## Print `global.css` includes a print stylesheet: white background, black text, 11pt body, header and footer hidden, links underlined, and external link targets exposed with `a[href^='http']::after`. In-page anchors are excluded so a printed article is not littered with fragment identifiers.