Add Calm Archive design system docs and Utopia skill
Add the Domus "Calm Archive" design system to the repo:
- docs/design/design-system.md — the full spec (principles, foundations,
color/type tokens, components, voice), from Claude Design handoff.
- docs/design/domus-tokens.css — companion fluid (Utopia) type/space and
warm paper/ink color tokens the spec references.
Update AGENTS.md with a Design system section pointing agents at the spec
and tokens, plus a Scaling section pointing at the new utopia skill.
Add a project skill for working with Utopia fluid scales
(.claude/skills/utopia/): documents the project's Utopia config and the
clamp() math, with a deterministic generator script (scripts/utopia.py)
whose output matches the committed tokens exactly.
diff --git a/.claude/skills/utopia/SKILL.md b/.claude/skills/utopia/SKILL.md
new file mode 100644
index 0000000..6c4209f
--- /dev/null
+++ b/.claude/skills/utopia/SKILL.md
@@ -0,0 +1,98 @@
+---
+name: utopia
+description: "Use this skill when working with Domus's fluid type and space scales — the Utopia-generated clamp() tokens (--step-* and --space-*) in docs/design/domus-tokens.css and public/app.css. Triggers: adding or regenerating a fluid type/space step, computing a clamp() value, changing the viewport/body/ratio config, or reasoning about why a size scales the way it does. Utopia (utopia.fyi) makes type and spacing interpolate fluidly between a min and max viewport with a single clamp() per step, so there are no breakpoints to manage."
+---
+
+# Utopia fluid type & space
+
+Domus scales type and spacing with [Utopia](https://utopia.fyi): each step is
+one `clamp()` that interpolates between a **min** size (at the min viewport)
+and a **max** size (at the max viewport). Below the min viewport the value
+pins to the min; above the max viewport it pins to the max. Everything is in
+`rem`, so it also respects the user's browser font-size preference.
+
+## Where the tokens live
+
+- **`docs/design/domus-tokens.css`** — canonical token set referenced by the
+ design system spec (`docs/design/design-system.md`).
+- **`public/app.css`** — the tokens actually shipped to the browser.
+
+Keep these consistent: regenerate from the same config and update both.
+
+## Project config
+
+The design-system scale uses:
+
+| Setting | Min (320px viewport) | Max (1240px viewport) |
+|---|---|---|
+| Viewport | 320px | 1240px |
+| Body (step 0) | 18px | 20px |
+| Type ratio | 1.20 (Minor Third) | 1.25 (Major Third) |
+| Space base | = body (18px) | = body (20px) |
+
+Space multiples off the base: 0.25 / 0.5 / 0.75 / 1 / 1.5 / 2 / 3 / 4 / 6
+(→ `3xs` … `3xl`). Utopia can also emit one-up pairs (`--space-s-m`, …).
+
+> The shipped `public/app.css` currently carries an older scale (viewport
+> 320→1280, ratio 1.25). When restyling toward the design system, regenerate
+> it from the config above so it matches `domus-tokens.css`.
+
+## How a step is computed
+
+For a step that goes from `minPx` (at `minVw` = 320) to `maxPx` (at `maxVw` =
+1240):
+
+```
+slope = (maxPx - minPx) / (maxVw - minVw)
+vw = slope * 100 # the vw coefficient
+interceptPx = minPx - slope * minVw # the rem offset, in px
+clamp( minPx/16 rem , interceptPx/16 rem + vw·vw , maxPx/16 rem )
+```
+
+Worked example — body (step 0), 18→20px:
+
+```
+slope = (20 - 18) / (1240 - 320) = 0.0021739
+vw = 0.21739vw
+interceptPx = 18 - 0.0021739 * 320 = 17.30435px → 1.0815rem
+→ clamp(1.125rem, 1.0815rem + 0.2174vw, 1.25rem)
+```
+
+Type steps are derived by multiplying/dividing the body by the ratio
+(`step n = body * ratio^n`), using the **min ratio** for min sizes and the
+**max ratio** for max sizes. Space steps are the base size times the
+multiples above.
+
+## Generating tokens
+
+Prefer the helper script (no network, deterministic):
+
+```bash
+# Print the full :root token block (type + space) for the project config:
+python3 .claude/skills/utopia/scripts/utopia.py
+
+# Compute a single clamp() for an arbitrary min→max px pair:
+python3 .claude/skills/utopia/scripts/utopia.py 13.5 15
+# → clamp(0.8438rem, 0.8111rem + 0.163vw, 0.9375rem)
+
+# Override the viewport range or rem base:
+python3 .claude/skills/utopia/scripts/utopia.py --min-vw 320 --max-vw 1240 25.92 31.25
+```
+
+The interactive generator at <https://utopia.fyi/type/calculator> and
+<https://utopia.fyi/space/calculator> produces the same values if you'd
+rather use the UI — feed it the config above.
+
+## Using the tokens
+
+Reference the custom properties; never hard-code sizes.
+
+```css
+.h1 { font-size: var(--step-3); }
+.body { font-size: var(--step-0); }
+.card { padding: var(--space-m); gap: var(--space-s); }
+```
+
+Type steps: `--step--2` (catalog labels) → `--step-5` (cover display).
+Space steps: `--space-3xs` (hairline gaps) → `--space-3xl` (cover whitespace).
+See `docs/design/design-system.md` for the role each step plays.
diff --git a/.claude/skills/utopia/scripts/utopia.py b/.claude/skills/utopia/scripts/utopia.py
new file mode 100755
index 0000000..c9b3a39
--- /dev/null
+++ b/.claude/skills/utopia/scripts/utopia.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+"""Generate Utopia fluid clamp() tokens for Domus.
+
+Utopia (utopia.fyi) interpolates a value between a min size (at the min
+viewport) and a max size (at the max viewport) with a single clamp(). This
+script reproduces that math so type/space tokens can be regenerated without
+the website.
+
+Usage
+-----
+ utopia.py # print the full :root token block
+ utopia.py 13.5 15 # one clamp() for a min->max px pair
+ utopia.py --min-vw 320 --max-vw 1240 18 20
+"""
+import argparse
+from decimal import Decimal, ROUND_HALF_UP
+
+REM = 16.0 # 1rem assumed = 16px (browser default)
+
+
+def round_(x, n=4):
+ # Half-up rounding (matches Utopia), then trim trailing zeros.
+ q = Decimal(10) ** -n
+ d = Decimal(repr(x)).quantize(q, rounding=ROUND_HALF_UP)
+ return format(d.normalize(), "f")
+
+
+def clamp(min_px, max_px, min_vw, max_vw):
+ """Return a Utopia-style clamp() string for min_px -> max_px."""
+ slope = (max_px - min_px) / (max_vw - min_vw)
+ vw = slope * 100
+ intercept_rem = (min_px - slope * min_vw) / REM
+ return (
+ f"clamp({round_(min_px / REM)}rem, "
+ f"{round_(intercept_rem)}rem + {round_(vw)}vw, "
+ f"{round_(max_px / REM)}rem)"
+ )
+
+
+def type_scale(body_min, body_max, ratio_min, ratio_max):
+ """Steps -2..5: body * ratio**n (min uses min ratio, max uses max ratio)."""
+ steps = {}
+ for n in range(-2, 6):
+ name = f"--step-{n}" if n >= 0 else f"--step-{n}"
+ steps[name] = (body_min * ratio_min**n, body_max * ratio_max**n)
+ return steps
+
+
+# Space multiples off the body base, in Utopia's naming.
+SPACE_MULTIPLES = {
+ "--space-3xs": 0.25,
+ "--space-2xs": 0.5,
+ "--space-xs": 0.75,
+ "--space-s": 1.0,
+ "--space-m": 1.5,
+ "--space-l": 2.0,
+ "--space-xl": 3.0,
+ "--space-2xl": 4.0,
+ "--space-3xl": 6.0,
+}
+
+
+def main():
+ p = argparse.ArgumentParser(description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ p.add_argument("pair", nargs="*", type=float,
+ help="min_px max_px for a single clamp()")
+ p.add_argument("--min-vw", type=float, default=320)
+ p.add_argument("--max-vw", type=float, default=1240)
+ p.add_argument("--body-min", type=float, default=18)
+ p.add_argument("--body-max", type=float, default=20)
+ p.add_argument("--ratio-min", type=float, default=1.20)
+ p.add_argument("--ratio-max", type=float, default=1.25)
+ args = p.parse_args()
+
+ if args.pair:
+ if len(args.pair) != 2:
+ p.error("provide exactly two numbers: min_px max_px")
+ print(clamp(args.pair[0], args.pair[1], args.min_vw, args.max_vw))
+ return
+
+ print(":root {")
+ print(" /* fluid type scale */")
+ for name, (mn, mx) in type_scale(args.body_min, args.body_max,
+ args.ratio_min, args.ratio_max).items():
+ print(f" {name}: {clamp(mn, mx, args.min_vw, args.max_vw)}; "
+ f"/* {round_(mn, 2)} -> {round_(mx, 2)} */")
+ print()
+ print(" /* fluid space scale */")
+ for name, mult in SPACE_MULTIPLES.items():
+ mn, mx = args.body_min * mult, args.body_max * mult
+ print(f" {name}: {clamp(mn, mx, args.min_vw, args.max_vw)}; "
+ f"/* {round_(mn, 2)} -> {round_(mx, 2)} */")
+ print("}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/AGENTS.md b/AGENTS.md
index 697b9e7..24f90fa 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -8,3 +8,30 @@ a backlog entry before you start it.
The default backlog for this project is `domus`. The `RANGER_DEFAULT_BACKLOG`
environment variable is set to `domus`, so you can omit `--backlog` from all
ranger commands.
+
+## Design system
+
+Domus follows the **Calm Archive** design system — warm paper, deep warm
+ink, one muted accent (Clay), web-safe type, and minimal motion. The full
+spec lives in [`docs/design/design-system.md`](docs/design/design-system.md),
+with the fluid type/space and color tokens in
+[`docs/design/domus-tokens.css`](docs/design/domus-tokens.css).
+
+When building or changing any UI:
+
+- Read the spec before touching styles, and match its visual output. These
+ are guidelines for real implementation (Phlex views, `public/app.css`),
+ not files to copy verbatim.
+- Reference the design tokens (`--w-*`, `--step-*`, `--space-*`) rather than
+ hard-coding colors, font sizes, or spacing. Use the fluid `--step-*` /
+ `--space-*` scales for type and spacing instead of fixed pixels.
+- Keep it calm: flat surfaces, hairline rules, one accent per view, no
+ gradients or bounce. Mono uppercase labels for field/meta/catalog text.
+- Write copy in the archival voice — plain and unhurried, never marketing.
+
+## Scaling — Utopia
+
+Type and space are fluid, generated with [Utopia](https://utopia.fyi). When
+you need to add, regenerate, or reason about a `clamp()` step on the scale,
+use the **utopia** skill (`.claude/skills/utopia/`) — it documents the
+project's Utopia config and how to derive the tokens.
diff --git a/docs/design/design-system.md b/docs/design/design-system.md
new file mode 100644
index 0000000..90b1df2
--- /dev/null
+++ b/docs/design/design-system.md
@@ -0,0 +1,320 @@
+# Domus — Design System
+
+**Calm Archive** — the quiet system for keeping a home's documents.
+
+Domus treats the screen like good paper and labels things like a library
+catalog. Surfaces are warm, ink is deep, motion is minimal. The interface
+recedes so the documents — receipts, warranties, manuals, deeds — are what
+you notice.
+
+| | |
+|---|---|
+| **Version** | 1.0 — June 2026 |
+| **Voice** | Plain, archival, unhurried |
+| **Surface** | Warm paper & deep warm ink |
+| **Accent** | Clay (swappable) |
+| **Type** | Georgia · Helvetica · system mono (no custom fonts) |
+| **Scaling** | [Utopia](https://utopia.fyi) fluid type & space |
+
+Tokens live in [`domus-tokens.css`](domus-tokens.css) — import it once and
+reference the custom properties below.
+
+---
+
+## Principles
+
+1. **Calm over clever.** No performing buttons, no gradients, no bounce.
+ Components sit flat and quiet. Emphasis comes from one calm accent and
+ generous space — not from shadow and shine.
+2. **Paper, not screen.** Warm stock, hairline rules, a faint 45° grain on
+ placeholders. The feel of a well-kept filing cabinet, rendered crisply —
+ never skeuomorphic.
+3. **Label like a catalog.** Monospace, uppercase, letter-spaced labels mark
+ every field and meta value — the way an archivist tags a folder.
+4. **The tool recedes.** Capture is one tap; naming is one line; everything
+ else waits. Structure (tags, details) arrives only when the moment calls
+ for it.
+
+---
+
+## Foundations
+
+### Scaling — Utopia
+
+Type and space are **fluid**: a single `clamp()` per step interpolates
+between a min and max as the viewport moves, so there are no breakpoints to
+manage. The scale is generated with [Utopia](https://utopia.fyi).
+
+| Setting | Min | Max |
+|---|---|---|
+| Viewport | 320px | 1240px |
+| Body (step 0) | 18px | 20px |
+| Type ratio | 1.20 — Minor Third | 1.25 — Major Third |
+
+Below 320px the value pins to the min; above 1240px it pins to the max.
+Everything is expressed in `rem`, so it also respects the user's browser
+font-size preference.
+
+#### Type scale
+
+| Token | Min → Max | Role |
+|---|---|---|
+| `--step-5` | 44.8 → 61.0px | Cover display |
+| `--step-4` | 37.3 → 48.8px | Section display |
+| `--step-3` | 31.1 → 39.1px | H1 / page title |
+| `--step-2` | 25.9 → 31.3px | H2 / section title |
+| `--step-1` | 21.6 → 25.0px | Lead · H3 |
+| `--step-0` | 18.0 → 20.0px | Body |
+| `--step--1` | 15.0 → 16.0px | Small · chips · captions |
+| `--step--2` | 12.5 → 12.8px | Eyebrows · catalog labels |
+
+```css
+:root {
+ --step--2: clamp(0.7813rem, 0.7747rem + 0.0326vw, 0.8rem);
+ --step--1: clamp(0.9375rem, 0.9158rem + 0.1087vw, 1rem);
+ --step-0: clamp(1.125rem, 1.0815rem + 0.2174vw, 1.25rem);
+ --step-1: clamp(1.35rem, 1.2761rem + 0.3696vw, 1.5625rem);
+ --step-2: clamp(1.62rem, 1.5041rem + 0.5793vw, 1.9531rem);
+ --step-3: clamp(1.944rem, 1.771rem + 0.8651vw, 2.4414rem);
+ --step-4: clamp(2.3328rem, 2.0827rem + 1.2504vw, 3.0518rem);
+ --step-5: clamp(2.7994rem, 2.4462rem + 1.7658vw, 3.8147rem);
+}
+```
+
+#### Space scale
+
+Built from the body size, so spacing breathes in step with type. Use these
+for padding, gap, and margins instead of fixed pixels.
+
+| Token | Min → Max | Typical use |
+|---|---|---|
+| `--space-3xs` | 4.5 → 5px | Hairline gaps, icon insets |
+| `--space-2xs` | 9 → 10px | Chip padding, tight stacks |
+| `--space-xs` | 13.5 → 15px | Label-to-field, button padding-y |
+| `--space-s` | 18 → 20px | Default gap inside a card |
+| `--space-m` | 27 → 30px | Card padding |
+| `--space-l` | 36 → 40px | Section gap |
+| `--space-xl` | 54 → 60px | Between sections |
+| `--space-2xl` | 72 → 80px | Page padding |
+| `--space-3xl` | 108 → 120px | Cover whitespace |
+
+```css
+:root {
+ --space-3xs: clamp(0.2813rem, 0.2704rem + 0.0543vw, 0.3125rem);
+ --space-2xs: clamp(0.5625rem, 0.5408rem + 0.1087vw, 0.625rem);
+ --space-xs: clamp(0.8438rem, 0.8111rem + 0.163vw, 0.9375rem);
+ --space-s: clamp(1.125rem, 1.0815rem + 0.2174vw, 1.25rem);
+ --space-m: clamp(1.6875rem, 1.6223rem + 0.3261vw, 1.875rem);
+ --space-l: clamp(2.25rem, 2.163rem + 0.4348vw, 2.5rem);
+ --space-xl: clamp(3.375rem, 3.2446rem + 0.6522vw, 3.75rem);
+ --space-2xl: clamp(4.5rem, 4.3261rem + 0.8696vw, 5rem);
+ --space-3xl: clamp(6.75rem, 6.4891rem + 1.3043vw, 7.5rem);
+}
+```
+
+> Utopia also generates **one-up pairs** (`--space-s-m`, `--space-m-l`, …) for
+> asymmetric rhythm — a few are included in `domus-tokens.css`.
+
+---
+
+### Color
+
+A warm, low-chroma neutral scale (whites and blacks tinted toward paper) plus
+**one** muted accent. Never introduce a second accent in the same view.
+
+#### Paper & ink
+
+| Token | Hex | Role |
+|---|---|---|
+| `--w-desk` | `#E9E3D6` | Page background / desk |
+| `--w-bg` | `#F3EFE6` | Warm paper |
+| `--w-surface` | `#FDFBF6` | Card stock |
+| `--w-fill` | `#EFE9DC` | Recessed paper |
+| `--w-fill-2` | `#E9E2D2` | Recessed, deeper |
+| `--w-line` | `#E4DDCF` | Hairline |
+| `--w-line-2` | `#D4CBB8` | Rule |
+| `--w-ink-3` | `#9B9384` | Faint catalog label |
+| `--w-ink-2` | `#6B6458` | Muted text |
+| `--w-ink` | `#2A261F` | Warm near-black — body, the mark |
+
+#### Accent — Clay (shipping default)
+
+| Token | Value | Role |
+|---|---|---|
+| `--w-accent` | `#9A5A3C` | Primary fill, the dot, FAB, active tab |
+| `--w-accent-ink` | `color-mix(… 80%, black)` | Hover, links, primary-tile label |
+| `--w-accent-soft` | `color-mix(… 12%, white)` | Recommended-tile tint, selection |
+
+`-ink` and `-soft` are **derived** from `--w-accent` via `color-mix`, so
+swapping the accent updates the whole family.
+
+#### Sanctioned accent palette
+
+The accent is chosen at the brand level and held constant. Swap by changing
+`--w-accent` only.
+
+| Name | Hex |
+|---|---|
+| **Clay** *(default)* | `#9A5A3C` |
+| Sage | `#3F6B53` |
+| Ink blue | `#2B4A78` |
+| Slate | `#5B6470` |
+| Tobacco | `#7A5A3A` |
+
+---
+
+### Typography
+
+Three web-safe families, each with a clear job. No custom font loading.
+
+| Role | Family | Stack |
+|---|---|---|
+| Display / Serif | **Georgia** | `Georgia, "Times New Roman", "Iowan Old Style", serif` |
+| Interface / Sans | **Helvetica** | `"Helvetica Neue", Helvetica, Arial, system-ui, sans-serif` |
+| Catalog / Mono | **System mono** | `ui-monospace, Menlo, Consolas, "Courier New", monospace` |
+
+**Conventions**
+
+- Headings & the wordmark: `--font-serif`, slight negative tracking
+ (`letter-spacing: -0.012em`).
+- Body, controls, leads: `--font-ui`.
+- Eyebrows, field labels, chips, shortcuts: `--font-mono`, uppercase,
+ `letter-spacing: 0.06–0.16em`.
+
+```css
+.h1 { font: var(--step-3)/1.06 var(--font-serif); letter-spacing: -0.018em; }
+.h2 { font: var(--step-2)/1.10 var(--font-serif); letter-spacing: -0.012em; }
+.lead { font-size: var(--step-1); color: var(--w-ink-2); line-height: 1.5; }
+.body { font-size: var(--step-0); }
+.eyebrow { font: 500 var(--step--2)/1 var(--font-mono);
+ text-transform: uppercase; letter-spacing: 0.16em; color: var(--w-ink-3); }
+.label { font: 500 var(--step--2) var(--font-mono);
+ text-transform: uppercase; letter-spacing: 0.06em; color: var(--w-ink-2); }
+```
+
+---
+
+### Radius, elevation & grain
+
+- **Radius** — base `--radius: 14px` for cards. Nested controls subtract
+ (`calc(var(--radius) - 5px)` for buttons, `- 6px` for inputs) so corners
+ stay concentric. Inputs ≈ 8px, tiles ≈ 11px, cards 14px.
+- **Elevation** — two levels only: **flat** (default) and **float** (the one
+ shadow), via `--shadow-float`. No mid-tier shadows.
+- **Grain** — placeholders use a faint 45° repeating stripe over `--w-fill`:
+
+```css
+.shot {
+ background:
+ repeating-linear-gradient(45deg, transparent 0 8px,
+ rgba(60,52,36,.045) 8px 9px),
+ var(--w-fill);
+ border: 1px solid var(--w-line-2);
+ border-radius: calc(var(--radius) - 4px);
+}
+```
+
+---
+
+## Components
+
+### Buttons
+
+Flat and quiet — they recede, they don't perform. Primary is a calm accent
+fill; default is paper with a rule; ghost is text-only; dark is reserved for a
+single committing action (e.g. *Save*).
+
+```css
+.btn {
+ display: inline-flex; align-items: center; gap: var(--space-2xs);
+ padding: var(--space-xs) var(--space-s);
+ border: 1px solid var(--w-line-2);
+ border-radius: calc(var(--radius) - 5px);
+ background: var(--w-surface);
+ font: 550 var(--step--1) var(--font-ui); letter-spacing: -0.01em;
+ color: var(--w-ink); cursor: pointer;
+ transition: background .14s ease, border-color .14s ease;
+}
+.btn:hover { background: var(--w-fill); }
+.btn.primary { background: var(--w-accent); border-color: var(--w-accent); color: #fff; }
+.btn.primary:hover { background: var(--w-accent-ink); border-color: var(--w-accent-ink); }
+.btn.dark { background: var(--w-ink); border-color: var(--w-ink); color: var(--w-bg); }
+.btn.ghost { background: transparent; border-color: transparent; color: var(--w-ink-2); }
+```
+
+- **Sizes** — `sm` / default / `lg` adjust padding only; font stays on the
+ scale.
+- **Split button** — primary action + alternate share one accent-outlined
+ group (e.g. *Capture | Browse*).
+
+### Fields
+
+```css
+.field { display: flex; flex-direction: column; gap: var(--space-2xs); }
+.label { /* mono, uppercase — see Typography */ }
+.input {
+ border: 1px solid var(--w-line-2);
+ border-radius: calc(var(--radius) - 6px);
+ background: var(--w-surface);
+ padding: var(--space-xs) var(--space-s);
+ font-size: var(--step--1); color: var(--w-ink);
+}
+.caret { width: 1.5px; height: 1em; background: var(--w-accent);
+ animation: blink 1.05s steps(2) infinite; }
+@keyframes blink { 50% { opacity: 0; } }
+```
+
+The caret is tinted with the accent — the only animated element in a field.
+
+### Chips & meta
+
+Monospace pills for file metadata (name, size, type). `--w-fill` background,
+`--w-line` border, `--step--2` mono text, 6px radius.
+
+### Segmented control
+
+Pill track (`--w-fill`), the active segment lifts to `--w-surface` with a
+1px shadow. Use for filters (*All · Receipts · Warranties*), never for primary
+navigation.
+
+### Cards
+
+Warm `--w-surface`, 1px `--w-line`, `--radius`. Apply `--shadow-float` only
+for the focused/primary card (e.g. the capture card or a document preview).
+
+### Capture methods
+
+The capture chooser ships in three sanctioned treatments — pick one per
+surface:
+
+| Variant | When |
+|---|---|
+| **Tiles** | Two equal options; the recommended path gets the `accent-soft` tint. |
+| **Keys** | Desktop, power users — full-width rows with mono shortcut hints (`⌘⇧N`). |
+| **Minimal** | Mobile / focused capture — one primary button + a quiet "or browse" link. |
+
+### Icons
+
+Line icons, `1.6` stroke, round caps & joins, drawn on a 24px grid. Set:
+`camera · upload · image · doc · folder · search · home · plus · check · x ·
+chev · swap · sparkle · lock`. Match stroke to text color; size by context
+(13–22px).
+
+---
+
+## Voice & labels
+
+Plain words, archival labels. Write like a calm archivist, not a marketer.
+
+**Catalog labels** (uppercase mono): `NAME` · `SAVED` · `EDITABLE` ·
+`RECOMMENDED`.
+
+| Write like this | Not like this |
+|---|---|
+| "Take a photo or pick a file to keep." | "Effortlessly supercharge your workflow!" |
+| "Tags & details come later — for now, just keep it." | "Oops! Something went wrong 😬" |
+| "or drop an image onto this card" | "UPLOAD NOW — it's FREE" |
+
+---
+
+*Domus · Calm Archive · Design System v1.0 · June 2026*
diff --git a/docs/design/domus-tokens.css b/docs/design/domus-tokens.css
new file mode 100644
index 0000000..abbe400
--- /dev/null
+++ b/docs/design/domus-tokens.css
@@ -0,0 +1,73 @@
+/* ════════════════════════════════════════════════════════════════════════
+ Domus — design tokens (Calm Archive)
+ Fluid type & space generated with the Utopia scale (utopia.fyi).
+
+ Config
+ ──────
+ Viewport 320px → 1240px
+ Body 18px → 20px
+ Type ratio 1.20 (Minor Third) → 1.25 (Major Third)
+ Space base = body, multiples 0.25 / 0.5 / 0.75 / 1 / 1.5 / 2 / 3 / 4 / 6
+ ════════════════════════════════════════════════════════════════════════ */
+
+:root {
+
+ /* ── fluid type scale ───────────────────────────────────────────────── */
+ --step--2: clamp(0.7813rem, 0.7747rem + 0.0326vw, 0.8rem); /* 12.50 → 12.80 */
+ --step--1: clamp(0.9375rem, 0.9158rem + 0.1087vw, 1rem); /* 15.00 → 16.00 */
+ --step-0: clamp(1.125rem, 1.0815rem + 0.2174vw, 1.25rem); /* 18.00 → 20.00 */
+ --step-1: clamp(1.35rem, 1.2761rem + 0.3696vw, 1.5625rem); /* 21.60 → 25.00 */
+ --step-2: clamp(1.62rem, 1.5041rem + 0.5793vw, 1.9531rem); /* 25.92 → 31.25 */
+ --step-3: clamp(1.944rem, 1.771rem + 0.8651vw, 2.4414rem); /* 31.10 → 39.06 */
+ --step-4: clamp(2.3328rem, 2.0827rem + 1.2504vw, 3.0518rem); /* 37.32 → 48.83 */
+ --step-5: clamp(2.7994rem, 2.4462rem + 1.7658vw, 3.8147rem); /* 44.79 → 61.04 */
+
+ /* ── fluid space scale ──────────────────────────────────────────────── */
+ --space-3xs: clamp(0.2813rem, 0.2704rem + 0.0543vw, 0.3125rem); /* 4.5 → 5 */
+ --space-2xs: clamp(0.5625rem, 0.5408rem + 0.1087vw, 0.625rem); /* 9 → 10 */
+ --space-xs: clamp(0.8438rem, 0.8111rem + 0.163vw, 0.9375rem); /* 13.5 → 15 */
+ --space-s: clamp(1.125rem, 1.0815rem + 0.2174vw, 1.25rem); /* 18 → 20 */
+ --space-m: clamp(1.6875rem, 1.6223rem + 0.3261vw, 1.875rem); /* 27 → 30 */
+ --space-l: clamp(2.25rem, 2.163rem + 0.4348vw, 2.5rem); /* 36 → 40 */
+ --space-xl: clamp(3.375rem, 3.2446rem + 0.6522vw, 3.75rem); /* 54 → 60 */
+ --space-2xl: clamp(4.5rem, 4.3261rem + 0.8696vw, 5rem); /* 72 → 80 */
+ --space-3xl: clamp(6.75rem, 6.4891rem + 1.3043vw, 7.5rem); /* 108 → 120 */
+
+ /* one-up pairs (useful for asymmetric rhythm) */
+ --space-s-m: clamp(1.125rem, 0.8641rem + 1.3043vw, 1.875rem); /* 18 → 30 */
+ --space-m-l: clamp(1.6875rem, 1.4049rem + 1.413vw, 2.5rem); /* 27 → 40 */
+ --space-l-xl: clamp(2.25rem, 1.7283rem + 2.6087vw, 3.75rem); /* 36 → 60 */
+
+ /* ── paper & ink (warm, low chroma) ─────────────────────────────────── */
+ --w-desk: #e9e3d6; /* page background / desk */
+ --w-bg: #f3efe6; /* warm paper */
+ --w-surface: #fdfbf6; /* card stock */
+ --w-fill: #efe9dc; /* recessed paper */
+ --w-fill-2: #e9e2d2;
+ --w-line: #e4ddcf; /* hairline */
+ --w-line-2: #d4cbb8; /* rule */
+ --w-ink-3: #9b9384; /* faint catalog-label */
+ --w-ink-2: #6b6458; /* muted */
+ --w-ink: #2a261f; /* warm near-black */
+
+ /* ── accent (Clay — swappable at the brand level) ───────────────────── */
+ --w-accent: #9a5a3c;
+ --w-accent-ink: color-mix(in oklch, var(--w-accent) 80%, black);
+ --w-accent-soft: color-mix(in oklch, var(--w-accent) 12%, white);
+
+ /* ── radius & elevation ─────────────────────────────────────────────── */
+ --radius: 14px; /* cards; controls subtract 5–6px */
+ --shadow-float:
+ 0 1px 2px rgba(60,52,36,.04),
+ 0 4px 10px -4px rgba(60,52,36,.10),
+ 0 24px 48px -28px rgba(60,52,36,.30);
+
+ /* ── families (web-safe, no custom fonts) ───────────────────────────── */
+ --font-serif: Georgia, "Times New Roman", "Iowan Old Style", serif;
+ --font-ui: "Helvetica Neue", Helvetica, Arial, system-ui, sans-serif;
+ --font-mono: ui-monospace, Menlo, Consolas, "Courier New", monospace;
+}
+
+/* Set the document's base font from step 0 so rem-based tokens scale with it. */
+html { font-size: 100%; } /* respect user default (≈16px) */
+body { font-size: var(--step-0); font-family: var(--font-ui); line-height: 1.55; color: var(--w-ink); }