diff --git a/civiccore-ui/README.md b/civiccore-ui/README.md
new file mode 100644
index 0000000..8ace8bd
--- /dev/null
+++ b/civiccore-ui/README.md
@@ -0,0 +1,39 @@
+# civiccore-ui — the CivicSuite design system
+
+The shared, versioned UI layer every CivicSuite module builds on. Three layers,
+each the single source of truth for its concern:
+
+| Layer | File | Owns |
+|---|---|---|
+| **Tokens** | [`tokens/tokens.css`](tokens/tokens.css) | Values — color, ink ramp, status palette, type families, density, radii. WCAG-AA-validated ([`tokens/tokens-reference.html`](tokens/tokens-reference.html)). |
+| **Components** | [`components/components.css`](components/components.css) | Structure — buttons, badges, status ribbon, tables, forms, alerts, first-run steps, empty states, audit drawer ([`components/components-reference.html`](components/components-reference.html)). |
+| **Shell** | [`shell/shell.css`](shell/shell.css) | The app frame — brand block, topbar, surface switcher, nav, workspace, page header, letterhead, search overlay. |
+
+## Dependency direction
+
+```
+tokens.css ─┐
+ ├─► components.css ─► shell.css
+ ┘
+```
+
+Components consume tokens; the shell consumes both. Load in that order. No layer
+redefines a `:root` custom property that `tokens.css` already owns.
+
+## Consumption (offline binaries cannot `@import` at runtime)
+
+Consumers — the Tauri/WebView2 desktop app, the umbrella prototype — **vendor a
+generated copy** of each file, pinned to a `civiccore` version, with a CI
+`--check` drift gate that fails the build if a vendored copy drifts from the
+source here. Any JS/JSON mirror is generated, never hand-maintained. This is the
+same idiom as the `source_commit` pins and the generated topology block. See the
+umbrella repo's `docs/design/windows-desktop-design-control.md` (Token Authority).
+
+## Provenance
+
+`components.css` and `shell.css` were extracted faithfully from the canonical
+prototype (`docs/design/ui-ux-prototype/styles.css` in the umbrella repo) — same
+class names, same behavior — with one accessibility correction: `.badge.gold`
+uses `--gold-strong` (5.43:1 on `--gold-soft`) instead of `--gold-2` (4.31:1, an
+AA fail). Migrating the prototype and desktop app to consume these files (rather
+than their own copies) is the incremental follow-on, one surface at a time.
diff --git a/civiccore-ui/components/.gitkeep b/civiccore-ui/components/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/civiccore-ui/components/components-reference.html b/civiccore-ui/components/components-reference.html
new file mode 100644
index 0000000..13679d4
--- /dev/null
+++ b/civiccore-ui/components/components-reference.html
@@ -0,0 +1,145 @@
+
+
+
+
+CivicSuite — Component Reference
+
+
+
+
+
+
+
+
+
+ CivicSuite · Design System
+ Component Reference
+ The canonical component primitives, rendered from components.css on the token authority. Every color, type, and density value comes from tokens.css . This is the structure source the way tokens.css is the value source.
+
+
+
+ Buttons
+ .btn · .primary .ghost .danger · sizes .sm .lg . No gold button: white-on-gold fails AA; use .primary for a solid CTA.
+
+ Primary
+ Default
+ Ghost
+ Delete review
+ Small
+ Large
+
+
+
+
+ Badges & status
+ .badge · .ok .warn .err .info .gold · .dot . Gold uses --gold-strong (5.43:1, AA).
+
+ Neutral
+ Released
+ Needs attention
+ Failed
+ Platform
+ Queued
+
+
+
+
+ Status ribbon (lifecycle)
+ .ribbon > .ribbon-step (.done .current ). Status conveyed by shape + label, never color alone.
+
+
+
1 Drafted
+
2 Reviewed
+
3 On agenda
+
4 Posted
+
5 Archived
+
+
+
+
+
+ Table
+ .tbl with .id .ttl .meta cells; honors [data-density] .
+
+
+ ID Notice Status Updated
+
+ N-1042 Public hearing — rezoning Posted 2026-07-01
+ N-1043 Budget adoption notice Queued 2026-07-02
+ N-1044 Special meeting Draft 2026-07-03
+
+
+
+
+
+
+ Form fields
+ .field > label (.req ) / .hint + .input .select .textarea . Focus ring is the AA navy ring.
+
+
City name *
+
Records contact Used on public-records responses.
+
Profile City Core (all six modules) Custom
+
Notice body
+
+
+
+
+ Alerts
+ .alert · .ok .warn .err .info . Used for the "AI engine not ready" fallback state and inline validation.
+
+
▲ AI engine not ready Every tool still works in labeled sample mode.
Open model setup
+
i This is a draft. A human reviews and decides before anything is published.
+
✓ Review saved. Records-ready export is available.
+
+
+
+
+ First-run setup step
+ .setup-step (.current .done ) — the canonical three-state wizard step.
+
+
+
+
+ Empty state
+ ▢
No saved reviews yet
Run an accessibility review and save it to see it listed here with a records-ready export.
New review
+
+
+
+ Audit drawer
+ .audit-drawer — the hash-chained, cross-module audit trail (shown inline here; it is a right-docked drawer in the app).
+
+
+
Audit trail
Hash-chained · this record
+
This record All modules
+
+
Review created
clerk@city.gov · 2026-07-02 14:22
Accessibility review opened for notice N-1043.
a1b2…9f4c
+
AI analysis attached
gemma-4-12b-it-qat-q4_0 · 2026-07-02 14:23
Advisory remediation analysis stored (draft; status unchanged).
c3d4…1a77
+
Review saved
clerk@city.gov · 2026-07-02 14:25
e5f6…8b22
+
+
+
+
+
+
+
diff --git a/civiccore-ui/components/components.css b/civiccore-ui/components/components.css
new file mode 100644
index 0000000..2e83c62
--- /dev/null
+++ b/civiccore-ui/components/components.css
@@ -0,0 +1,352 @@
+/* =============================================================================
+ CivicSuite component primitives — CANONICAL COMPONENT LAYER
+ -----------------------------------------------------------------------------
+ Buttons, badges, status ribbon, tables, forms, alerts, first-run setup steps,
+ empty states, and the audit drawer. Extracted faithfully from the canonical
+ prototype (docs/design/ui-ux-prototype/styles.css in the CivicSuite umbrella
+ repo) — same class names, same behavior — so this file becomes the single
+ source for component STRUCTURE the way tokens.css is the source for VALUES.
+
+ CONSUMPTION: consumes civiccore-ui/tokens/tokens.css (all colors/type/density
+ come from those custom properties). Load tokens.css first, then this file.
+ Offline consumers vendor a generated copy pinned to a civiccore version, with
+ a CI --check drift gate (same idiom as tokens.css).
+
+ AA: one correction vs the prototype at extraction time — .badge.gold uses
+ --gold-strong (5.43:1 on --gold-soft) instead of --gold-2 (4.31:1, a fail).
+ ========================================================================== */
+
+/* Card */
+.card {
+ background: var(--card);
+ border: 1px solid var(--rule);
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+}
+.card-h {
+ padding: 14px 16px 12px;
+ border-bottom: 1px solid var(--rule);
+ display: flex; align-items: center; gap: 10px;
+}
+.card-h .ttl {
+ font-size: 13px;
+ font-weight: 600;
+ letter-spacing: 0.01em;
+ color: var(--ink);
+}
+.card-h .ttl small {
+ font-weight: 400;
+ color: var(--ink-3);
+ margin-left: 6px;
+ letter-spacing: 0;
+}
+.card-h .right { margin-left: auto; display: flex; gap: 6px; }
+.card-b { padding: 12px 16px; }
+.card-b.flush { padding: 0; }
+[data-density="compact"] .card-h { padding: 10px 12px 10px; }
+[data-density="compact"] .card-b { padding: 10px 12px; }
+
+
+/* Buttons */
+.btn {
+ appearance: none;
+ display: inline-flex; align-items: center; gap: 6px;
+ height: 32px;
+ padding: 0 12px;
+ border-radius: 7px;
+ border: 1px solid var(--rule);
+ background: var(--card);
+ color: var(--ink);
+ font-size: 12.5px;
+ font-weight: 500;
+ letter-spacing: 0.005em;
+}
+.btn:hover { background: var(--paper-2); }
+.btn.primary { background: var(--navy); color: #fff; border-color: var(--navy-2); }
+.btn.primary:hover { background: var(--navy-2); }
+/* .btn.gold removed: white-on-gold is 3.22:1, fails WCAG AA. Gold is accent/border
+ only; use .btn.primary for a solid CTA, or --gold-2 for text-bearing gold. */
+.btn.ghost { background: transparent; border-color: transparent; }
+.btn.ghost:hover { background: var(--paper-2); border-color: var(--rule); }
+.btn.danger { color: var(--err); border-color: var(--err-soft); }
+.btn.sm { height: 26px; padding: 0 8px; font-size: 11.5px; }
+.btn.lg { height: 38px; padding: 0 16px; font-size: 13.5px; }
+
+/* Badges / Status */
+.badge {
+ display: inline-flex; align-items: center; gap: 4px;
+ font-size: 10.5px;
+ font-weight: 500;
+ letter-spacing: 0.02em;
+ padding: 2px 7px;
+ border-radius: 100px;
+ background: var(--neutral-soft);
+ color: var(--ink-2);
+ white-space: nowrap;
+ border: 1px solid transparent;
+}
+.badge.ok { background: var(--ok-soft); color: var(--ok); }
+.badge.warn { background: var(--warn-soft); color: var(--warn); }
+.badge.err { background: var(--err-soft); color: var(--err); }
+.badge.info { background: var(--info-soft); color: var(--info); }
+.badge.gold { background: var(--gold-soft); color: var(--gold-strong); } /* AA: gold-strong 5.43:1 (gold-2 was 4.31:1) */
+.badge.dot::before {
+ content: "";
+ width: 6px; height: 6px;
+ border-radius: 50%;
+ background: currentColor;
+ display: inline-block;
+}
+
+/* Status ribbon (lifecycle) */
+.ribbon {
+ display: flex;
+ align-items: center;
+ gap: 0;
+ padding: 0;
+ background: var(--paper-2);
+ border: 1px solid var(--rule);
+ border-radius: 8px;
+ overflow: hidden;
+ font-size: 11.5px;
+}
+.ribbon-step {
+ flex: 1;
+ padding: 10px 14px 10px 22px;
+ position: relative;
+ color: var(--ink-3);
+ background: transparent;
+ letter-spacing: 0.005em;
+ white-space: nowrap;
+}
+.ribbon-step:first-child { padding-left: 14px; }
+.ribbon-step::after {
+ content: "";
+ position: absolute;
+ right: -10px; top: 0; bottom: 0;
+ width: 20px;
+ background: inherit;
+ clip-path: polygon(0 0, 60% 50%, 0 100%);
+ z-index: 2;
+ border-right: 1px solid var(--rule);
+}
+.ribbon-step:last-child::after { display: none; }
+.ribbon-step.done {
+ background: var(--ok-soft);
+ color: var(--ok);
+}
+.ribbon-step.current {
+ background: var(--navy);
+ color: #fff;
+ font-weight: 500;
+}
+.ribbon-step.current .num { color: rgba(255,255,255,0.6); }
+.ribbon-step .num {
+ font-family: var(--font-mono);
+ font-size: 10px;
+ color: var(--ink-4);
+ margin-right: 6px;
+}
+.ribbon-step.done .num { color: var(--ok); opacity: 0.6; }
+
+/* Tables */
+.tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
+.tbl thead th {
+ text-align: left;
+ font-size: 10.5px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: var(--ink-3);
+ padding: 8px 12px;
+ border-bottom: 1px solid var(--rule);
+ background: var(--paper-2);
+ position: sticky; top: 0;
+}
+.tbl tbody td {
+ padding: 10px 12px;
+ border-bottom: 1px solid var(--rule);
+ vertical-align: middle;
+}
+[data-density="compact"] .tbl tbody td { padding: 6px 12px; }
+.tbl tbody tr:hover td { background: var(--paper-2); }
+.tbl tbody tr.selected td { background: var(--navy-soft); }
+.tbl .id { font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-3); }
+.tbl .ttl { color: var(--ink); font-weight: 500; }
+.tbl .meta { color: var(--ink-3); font-size: 12px; }
+.tbl tr { cursor: default; }
+
+/* Forms */
+.field { display: flex; flex-direction: column; gap: 4px; }
+.field label {
+ font-size: 11px; font-weight: 500; letter-spacing: 0.02em;
+ color: var(--ink-2);
+}
+.field label .req { color: var(--seal); }
+.field .hint { font-size: 11px; color: var(--ink-3); }
+.input, .select, .textarea {
+ appearance: none;
+ border: 1px solid var(--rule);
+ background: var(--card);
+ border-radius: 7px;
+ padding: 8px 10px;
+ font: inherit;
+ font-size: 13px;
+ color: var(--ink);
+ outline: none;
+ transition: border-color 120ms, box-shadow 120ms;
+}
+.input:focus, .select:focus, .textarea:focus {
+ border-color: var(--navy);
+ box-shadow: 0 0 0 3px rgba(26,58,82,0.12);
+}
+.textarea { resize: vertical; min-height: 80px; }
+
+/* Alerts */
+.alert {
+ display: flex; align-items: flex-start; gap: 10px;
+ padding: 10px 14px;
+ border-radius: 8px;
+ border: 1px solid var(--rule);
+ background: var(--paper-2);
+ font-size: 12.5px;
+ color: var(--ink-2);
+}
+.alert.warn { background: var(--warn-soft); border-color: rgba(138,90,20,0.2); color: var(--warn); }
+.alert.err { background: var(--err-soft); border-color: rgba(138,42,42,0.2); color: var(--err); }
+.alert.ok { background: var(--ok-soft); border-color: rgba(47,107,58,0.2); color: var(--ok); }
+.alert.info { background: var(--info-soft); border-color: rgba(31,74,107,0.2); color: var(--info); }
+.alert .ic { flex-shrink: 0; margin-top: 1px; }
+.alert .body { flex: 1; }
+.alert .body b { display: block; font-weight: 600; margin-bottom: 2px; }
+.alert .actions { display: flex; gap: 6px; margin-top: 8px; }
+
+/* First-run setup checklist (canonical three-state step list). */
+.setup-step {
+ display: grid;
+ grid-template-columns: 30px minmax(0,1fr);
+ gap: var(--gap);
+ padding: 14px;
+ border: 1px solid var(--rule);
+ border-radius: var(--radius-lg);
+ background: var(--card);
+}
+.setup-step.current { border-color: var(--gold); box-shadow: 0 0 0 1px var(--gold); }
+.setup-step > .num {
+ width: 30px; height: 30px; border-radius: 50%;
+ display: grid; place-items: center; font-weight: 600;
+ background: var(--paper-2); color: var(--ink-3);
+}
+.setup-step.current > .num { background: var(--navy); color: #fff; }
+.setup-step.done > .num { background: var(--ok-soft); color: var(--ok); }
+.setup-step-h { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
+.setup-step-h h3 { margin: 0; font-family: var(--font-serif); font-size: 15px; }
+.setup-action-needed { margin: 10px 0; }
+.setup-fields { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 10px; margin-top: 6px; }
+/* .input:focus (navy ring, L612) already provides the AA focus appearance. */
+
+/* Empty */
+.empty {
+ text-align: center;
+ padding: 40px 24px;
+ color: var(--ink-3);
+}
+.empty .ic { font-size: 24px; opacity: 0.4; margin-bottom: 8px; }
+.empty .ttl { font-family: var(--font-serif); font-size: 16px; color: var(--ink); margin-bottom: 4px; }
+.empty .body { font-size: 13px; max-width: 320px; margin: 0 auto 12px; }
+
+/* Audit drawer */
+.audit-drawer {
+ position: fixed;
+ top: 52px; right: 0; bottom: 0;
+ width: 380px;
+ background: var(--card);
+ border-left: 1px solid var(--rule);
+ box-shadow: -8px 0 24px rgba(0,0,0,0.04);
+ z-index: 50;
+ display: flex; flex-direction: column;
+ transform: translateX(100%);
+ transition: transform 240ms cubic-bezier(0.16,1,0.3,1);
+}
+.audit-drawer.open { transform: translateX(0); }
+.audit-h {
+ padding: 14px 18px;
+ border-bottom: 1px solid var(--rule);
+ background: var(--paper-2);
+ display: flex; align-items: center; gap: 10px;
+}
+.audit-h .ttl {
+ font-family: var(--font-serif);
+ font-weight: 600;
+ font-size: 14px;
+}
+.audit-h .sub { font-size: 11px; color: var(--ink-3); margin-top: 2px; }
+.audit-tabs {
+ display: flex; gap: 0;
+ border-bottom: 1px solid var(--rule);
+ padding: 0 12px;
+ background: var(--paper-2);
+}
+.audit-tabs button {
+ appearance: none; border: 0; background: transparent;
+ padding: 8px 10px;
+ font-size: 12px;
+ color: var(--ink-3);
+ border-bottom: 2px solid transparent;
+ margin-bottom: -1px;
+}
+.audit-tabs button.on { color: var(--navy); border-bottom-color: var(--navy); font-weight: 500; }
+.audit-b {
+ flex: 1; overflow-y: auto;
+ padding: 16px 18px;
+}
+.audit-event {
+ display: grid;
+ grid-template-columns: 14px 1fr;
+ gap: 10px;
+ padding-bottom: 16px;
+ position: relative;
+}
+.audit-event:not(:last-child)::after {
+ content: "";
+ position: absolute;
+ left: 6px; top: 14px; bottom: 0;
+ width: 1px; background: var(--rule);
+}
+.audit-event .marker {
+ width: 13px; height: 13px;
+ border-radius: 50%;
+ background: var(--card);
+ border: 2px solid var(--navy);
+ margin-top: 2px;
+ z-index: 2;
+}
+.audit-event.publish .marker { border-color: var(--gold); }
+.audit-event.error .marker { border-color: var(--err); }
+.audit-event .ev-h {
+ font-size: 12.5px;
+ font-weight: 500;
+}
+.audit-event .ev-meta {
+ font-size: 11px;
+ color: var(--ink-3);
+ font-family: var(--font-mono);
+ margin-top: 2px;
+}
+.audit-event .ev-body {
+ font-size: 12px;
+ color: var(--ink-2);
+ margin-top: 4px;
+ line-height: 1.5;
+}
+.audit-event .ev-hash {
+ display: inline-block;
+ font: 500 10.5px/1 var(--font-mono);
+ background: var(--paper-2);
+ border: 1px solid var(--rule);
+ padding: 3px 6px;
+ border-radius: 4px;
+ margin-top: 6px;
+ color: var(--ink-3);
+}
+
diff --git a/civiccore-ui/shell/.gitkeep b/civiccore-ui/shell/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/civiccore-ui/shell/shell.css b/civiccore-ui/shell/shell.css
new file mode 100644
index 0000000..0699bc1
--- /dev/null
+++ b/civiccore-ui/shell/shell.css
@@ -0,0 +1,446 @@
+/* =============================================================================
+ CivicSuite desktop shell chrome — CANONICAL SHELL LAYER
+ -----------------------------------------------------------------------------
+ The app frame the six workflow modules render inside: brand block, topbar,
+ surface switcher (staff / resident / admin), primary nav, main workspace,
+ page header, letterhead, and the global search overlay. Extracted faithfully
+ from the canonical prototype (docs/design/ui-ux-prototype/styles.css).
+
+ CONSUMPTION: consumes civiccore-ui/tokens/tokens.css and pairs with
+ civiccore-ui/components/components.css. Load tokens.css, then components.css,
+ then this file. Offline consumers vendor a generated copy, drift-gated in CI.
+ ========================================================================== */
+
+/* ─────────── App shell ─────────── */
+.app {
+ display: grid;
+ grid-template-columns: 232px 1fr;
+ grid-template-rows: 52px 1fr;
+ grid-template-areas:
+ "brand topbar"
+ "nav main";
+ height: 100vh;
+ background: var(--paper);
+}
+
+.app[data-surface="resident"] {
+ grid-template-columns: 1fr;
+ grid-template-rows: 64px 1fr;
+ grid-template-areas:
+ "topbar"
+ "main";
+}
+
+/* Brand block */
+.brand {
+ grid-area: brand;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 0 16px;
+ border-right: 1px solid var(--rule);
+ border-bottom: 1px solid var(--rule);
+ background: var(--paper-2);
+}
+.seal {
+ width: 28px; height: 28px;
+ border-radius: 50%;
+ background: var(--navy);
+ display: grid; place-items: center;
+ color: var(--gold-soft);
+ font-family: var(--font-serif);
+ font-weight: 600;
+ font-size: 13px;
+ letter-spacing: 0.02em;
+ position: relative;
+ flex-shrink: 0;
+}
+.seal::before {
+ content: "";
+ position: absolute; inset: 2px;
+ border-radius: 50%;
+ border: 1px solid var(--gold);
+ opacity: 0.55;
+}
+.brand-name {
+ font-family: var(--font-serif);
+ font-weight: 600;
+ font-size: 15px;
+ letter-spacing: -0.005em;
+ color: var(--ink);
+}
+.brand-sub {
+ font-size: 11px;
+ color: var(--ink-3);
+ letter-spacing: 0.02em;
+}
+
+/* Topbar */
+.topbar {
+ grid-area: topbar;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 0 16px;
+ border-bottom: 1px solid var(--rule);
+ background: var(--card);
+}
+.topbar .search {
+ flex: 1;
+ max-width: 640px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 0 12px;
+ height: 34px;
+ border: 1px solid var(--rule);
+ border-radius: 8px;
+ background: var(--paper);
+ color: var(--ink-3);
+ cursor: text;
+ transition: border-color 120ms, background 120ms;
+}
+.topbar .search:hover { background: var(--paper-2); }
+.topbar .search.active {
+ background: var(--card);
+ border-color: var(--navy);
+ box-shadow: 0 0 0 3px rgba(26,58,82,0.12);
+ color: var(--ink);
+}
+.topbar .search kbd {
+ margin-left: auto;
+ font: 500 10px/1 var(--font-mono);
+ padding: 3px 6px;
+ border: 1px solid var(--rule);
+ border-radius: 4px;
+ background: var(--paper-2);
+ color: var(--ink-3);
+}
+.topbar-spacer { flex: 1; }
+.topbar-right { display: flex; align-items: center; gap: 6px; }
+
+.iconbtn {
+ width: 32px; height: 32px;
+ border: 1px solid transparent;
+ background: transparent;
+ border-radius: 7px;
+ display: grid; place-items: center;
+ color: var(--ink-2);
+ position: relative;
+}
+.iconbtn:hover { background: var(--paper-2); border-color: var(--rule); }
+.iconbtn .dot {
+ position: absolute;
+ top: 6px; right: 7px;
+ width: 6px; height: 6px;
+ border-radius: 50%;
+ background: var(--seal);
+ border: 1.5px solid var(--card);
+}
+
+.user-chip {
+ display: flex; align-items: center; gap: 8px;
+ padding: 4px 10px 4px 4px;
+ border: 1px solid var(--rule);
+ border-radius: 100px;
+ background: var(--paper);
+}
+.avatar {
+ width: 24px; height: 24px;
+ border-radius: 50%;
+ background: var(--navy);
+ color: var(--gold-soft);
+ display: grid; place-items: center;
+ font-size: 10px; font-weight: 600;
+ letter-spacing: 0.02em;
+}
+.user-chip .nm { font-size: 12px; font-weight: 500; }
+.user-chip .role { font-size: 10px; color: var(--ink-3); letter-spacing: 0.02em; }
+
+/* Surface switcher */
+.surface-switch {
+ display: flex;
+ border: 1px solid var(--rule);
+ border-radius: 100px;
+ padding: 2px;
+ background: var(--paper);
+ gap: 0;
+}
+.surface-switch button {
+ appearance: none;
+ border: 0;
+ background: transparent;
+ padding: 4px 10px;
+ border-radius: 100px;
+ font-size: 11px;
+ font-weight: 500;
+ color: var(--ink-3);
+ letter-spacing: 0.01em;
+}
+.surface-switch button.on {
+ background: var(--navy);
+ color: #fff;
+}
+
+/* Nav */
+.nav {
+ grid-area: nav;
+ border-right: 1px solid var(--rule);
+ background: var(--paper-2);
+ padding: 12px 0;
+ overflow-y: auto;
+ display: flex; flex-direction: column;
+ gap: 2px;
+}
+.nav-section {
+ font-size: 10px;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ink-4);
+ padding: 14px 18px 6px;
+}
+.nav-section:first-child { padding-top: 4px; }
+.nav-item {
+ display: flex; align-items: center; gap: 10px;
+ padding: 7px 14px 7px 18px;
+ margin: 0 8px;
+ border-radius: 6px;
+ font-size: 13px;
+ color: var(--ink-2);
+ position: relative;
+}
+.nav-item:hover { background: var(--paper-3); color: var(--ink); }
+.nav-item.active {
+ background: var(--card);
+ color: var(--ink);
+ font-weight: 500;
+ box-shadow: inset 0 0 0 1px var(--rule);
+}
+.nav-item.active::before {
+ content: "";
+ position: absolute; left: -8px; top: 6px; bottom: 6px;
+ width: 3px;
+ background: var(--navy);
+ border-radius: 0 2px 2px 0;
+}
+.nav-item .ic {
+ width: 16px; height: 16px;
+ display: grid; place-items: center;
+ color: var(--ink-3);
+ flex-shrink: 0;
+}
+.nav-item.active .ic { color: var(--navy); }
+.nav-item .count {
+ margin-left: auto;
+ font-size: 10px;
+ font-variant-numeric: tabular-nums;
+ color: var(--ink-3);
+ background: var(--paper);
+ padding: 2px 6px;
+ border-radius: 10px;
+ border: 1px solid var(--rule);
+}
+.nav-item.active .count { background: var(--gold-soft); border-color: var(--gold); color: var(--gold-2); }
+
+.nav-recent {
+ margin: 8px;
+ padding: 8px 10px;
+ border-top: 1px solid var(--rule);
+}
+.nav-recent .lbl {
+ font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
+ color: var(--ink-4); margin-bottom: 6px;
+}
+.nav-recent a {
+ display: block;
+ font-size: 12px;
+ color: var(--ink-2);
+ padding: 3px 4px;
+ border-radius: 4px;
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+ text-decoration: none;
+}
+.nav-recent a:hover { background: var(--paper-3); }
+
+/* Main */
+.main {
+ grid-area: main;
+ overflow-y: auto;
+ position: relative;
+}
+
+/* ─────────── Page header ─────────── */
+.page-head {
+ padding: 24px 32px 16px;
+ border-bottom: 1px solid var(--rule);
+ background: var(--card);
+}
+[data-density="compact"] .page-head { padding: 16px 24px 12px; }
+.crumbs {
+ font-size: 11px;
+ color: var(--ink-3);
+ letter-spacing: 0.02em;
+ margin-bottom: 6px;
+ display: flex; gap: 6px; align-items: center;
+}
+.crumbs .sep { color: var(--ink-4); }
+.crumbs a { color: var(--ink-3); text-decoration: none; }
+.crumbs a:hover { color: var(--navy); }
+.page-title {
+ font-family: var(--font-serif);
+ font-size: 26px;
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ color: var(--ink);
+ margin: 0 0 4px;
+}
+[data-density="compact"] .page-title { font-size: 22px; }
+.page-sub {
+ font-size: 13px;
+ color: var(--ink-2);
+ margin: 0;
+}
+.page-head-row {
+ display: flex; align-items: flex-end; justify-content: space-between; gap: 16px;
+}
+
+.page-tabs {
+ display: flex; gap: 4px;
+ margin-top: 16px;
+ margin-bottom: -16px;
+ border-bottom: 1px solid transparent;
+}
+.page-tabs button {
+ appearance: none;
+ border: 0;
+ background: transparent;
+ padding: 8px 12px;
+ font-size: 13px;
+ color: var(--ink-3);
+ border-bottom: 2px solid transparent;
+ margin-bottom: -1px;
+ font-weight: 500;
+}
+.page-tabs button.on { color: var(--navy); border-bottom-color: var(--navy); }
+.page-tabs button:hover:not(.on) { color: var(--ink); }
+.page-tabs button .pill {
+ display: inline-block;
+ font-size: 10px;
+ font-weight: 500;
+ background: var(--paper-2);
+ color: var(--ink-3);
+ padding: 1px 6px;
+ margin-left: 4px;
+ border-radius: 8px;
+}
+
+/* ─────────── Body ─────────── */
+.body { padding: 24px 32px; }
+[data-density="compact"] .body { padding: 16px 24px; }
+
+.row { display: flex; gap: var(--gap); }
+.col { display: flex; flex-direction: column; gap: var(--gap); }
+
+.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
+.grid-3 { display: grid; grid-template-columns: repeat(3,1fr); gap: 16px; }
+.grid-4 { display: grid; grid-template-columns: repeat(4,1fr); gap: 16px; }
+.grid-cards { display: grid; grid-template-columns: 2fr 1fr; gap: 16px; }
+
+
+/* Letterhead — used at top of detail pages */
+.letterhead {
+ background: linear-gradient(180deg, var(--paper-2), var(--card));
+ border-bottom: 1px solid var(--rule);
+ padding: 20px 32px 24px;
+ position: relative;
+}
+.letterhead::after {
+ content: ""; position: absolute; left: 32px; right: 32px; bottom: 0;
+ height: 1px; background: var(--gold); opacity: 0.4;
+}
+.letterhead .seal-row {
+ display: flex; align-items: center; gap: 10px;
+ font-family: var(--font-serif);
+ font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
+ color: var(--gold-2);
+ margin-bottom: 8px;
+}
+.letterhead .seal-row::before, .letterhead .seal-row::after {
+ content: ""; height: 1px; background: var(--gold); opacity: 0.4; flex: 0 0 32px;
+}
+.letterhead .seal-row::after { flex: 1; }
+
+
+/* Search overlay */
+.search-overlay {
+ position: fixed; inset: 0;
+ background: rgba(20,30,42,0.32);
+ backdrop-filter: blur(2px);
+ display: grid;
+ place-items: start center;
+ padding-top: 12vh;
+ z-index: 100;
+}
+.search-modal {
+ width: min(680px, 92vw);
+ background: var(--card);
+ border: 1px solid var(--rule);
+ border-radius: 12px;
+ box-shadow: 0 24px 60px rgba(20,30,42,0.24);
+ overflow: hidden;
+ display: flex; flex-direction: column;
+ max-height: 70vh;
+}
+.search-modal .input-wrap {
+ display: flex; align-items: center; gap: 10px;
+ padding: 14px 18px;
+ border-bottom: 1px solid var(--rule);
+}
+.search-modal input {
+ flex: 1;
+ border: 0; outline: 0;
+ background: transparent;
+ font-size: 16px;
+}
+.search-modal .scope {
+ font-size: 11px; color: var(--ink-3);
+ font-family: var(--font-mono);
+}
+.search-modal .results {
+ overflow-y: auto;
+ flex: 1;
+}
+.search-group {
+ padding: 8px 0;
+}
+.search-group .lbl {
+ font-size: 10px;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--ink-4);
+ font-weight: 600;
+ padding: 6px 18px 4px;
+}
+.search-result {
+ display: grid;
+ grid-template-columns: 28px 1fr auto;
+ gap: 10px;
+ padding: 8px 18px;
+ align-items: center;
+}
+.search-result:hover, .search-result.active {
+ background: var(--paper-2);
+}
+.search-result .ic {
+ width: 28px; height: 28px;
+ background: var(--paper-2);
+ border-radius: 6px;
+ display: grid; place-items: center;
+ color: var(--ink-3);
+ border: 1px solid var(--rule);
+}
+.search-result .ttl { font-size: 13px; font-weight: 500; color: var(--ink); }
+.search-result .meta { font-size: 11px; color: var(--ink-3); margin-top: 2px; }
+.search-result .right { font-size: 10.5px; color: var(--ink-3); font-family: var(--font-mono); }
+