Add design/ contract; archive prior design system as provenance

Establish keysat's durable, vendor-neutral design contract (the standards
/design backfill, document-as-is):

- design/DESIGN.md — nine-section brand brief distilled from the prior
  Claude Design system (navy-on-cream-paper identity, sovereignty-first
  voice, component + motion rules, do's/don'ts). Manrope is canonical
  display (the README's "Archivo" was a stale placeholder).
- design/tokens.tokens.json — W3C DTCG tokens from colors_and_type.css.
- design/brand/ — canonical palette.css + logo/mark assets.
- design/_imports/2026-06-16-claude-design-system/ — the original system,
  relocated as dated provenance (nothing imported it).
- AGENTS.md — add the Design line (read the contract before UI work);
  repoint the layout entry.
- ROADMAP.md — design-checker cleanup backlog (gold-as-fill + pill-radius
  blockers, the inline-token-copy consolidation, token gaps).
This commit is contained in:
Keysat
2026-06-16 11:29:28 -05:00
parent ce5edaed29
commit 532229d488
51 changed files with 700 additions and 1 deletions
+2 -1
View File
@@ -25,6 +25,7 @@ its guide** — see the index below.
- Before building, bumping the version, or editing the StartOS wrapper, read `docs/guides/startos-packaging.md`. - Before building, bumping the version, or editing the StartOS wrapper, read `docs/guides/startos-packaging.md`.
- Before editing the admin SPA (`web/index.html`), read `docs/guides/admin-ui.md`. - Before editing the admin SPA (`web/index.html`), read `docs/guides/admin-ui.md`.
- Before editing public site/docs copy, read `docs/guides/website-copy.md`. - Before editing public site/docs copy, read `docs/guides/website-copy.md`.
- **Before building or changing any user-facing UI (landing, docs, admin SPA), read `design/DESIGN.md` and `design/tokens.tokens.json` and conform to them** — the brand contract; pull colors/type/space/radii/shadows from the tokens, never hardcode off-scale values.
- Before adding/altering tests or relying on lint/CI, read `docs/guides/testing.md`. - Before adding/altering tests or relying on lint/CI, read `docs/guides/testing.md`.
## Build / test / run (quick ref) ## Build / test / run (quick ref)
@@ -50,7 +51,7 @@ licensing-service-startos/ daemon + StartOS wrapper (s9pk package source)
keysat-xyz-landing/ keysat-docs/ keysat-registry-landing/ public sites → guides/website-copy.md keysat-xyz-landing/ keysat-docs/ keysat-registry-landing/ public sites → guides/website-copy.md
licensing-client-{rust,ts,python,go}/ the four SDK source repos licensing-client-{rust,ts,python,go}/ the four SDK source repos
activate-license-template/ Tauri desktop template for license activation activate-license-template/ Tauri desktop template for license activation
keysat-design-system/ design tokens / brand assets design/ design contract (DESIGN.md + tokens.tokens.json) + brand/ assets; original Claude Design system archived in design/_imports/
plans/ design specs (multi-provider-payment-model.md, keysat-smtp-emails.md) plans/ design specs (multi-provider-payment-model.md, keysat-smtp-emails.md)
tests/crosscheck/ cross-language LIC1 verifier → guides/crypto-wire-format.md tests/crosscheck/ cross-language LIC1 verifier → guides/crypto-wire-format.md
``` ```
+33
View File
@@ -24,3 +24,36 @@ Longer-term backlog. Near-term state lives in `AGENTS.md` → Current state.
- Re-test `KEYSAT_INTEGRATION.md` against a fresh downstream app to confirm a clean one-shot SDK integration. - Re-test `KEYSAT_INTEGRATION.md` against a fresh downstream app to confirm a clean one-shot SDK integration.
- End-to-end Zaprite sandbox pass on the multi-merchant-profile webhook routing before relying on it in production. - End-to-end Zaprite sandbox pass on the multi-merchant-profile webhook routing before relying on it in production.
## Design (contract conformance)
The brand contract now lives in `design/DESIGN.md` + `design/tokens.tokens.json` (distilled
2026-06-16 from the prior Claude Design system, now archived in `design/_imports/`). A
`design-checker` audit (2026-06-16) found high fidelity overall, with these items where the
**code contradicts the contract's stated rules** or bypasses the token scale:
**Blockers (code violates a named "never" rule):**
- Gold used as an actionable *fill* (contract: gold is accent/border only, never a fill).
(a) admin SPA `.featured-pill-toggle.on``web/index.html:418`; (b) admin sidebar
upgrade CTA `#tier-banner-cta``web/index.html:537-542`. Fix to navy-fill or
gold-border/text.
- Primary buy CTA uses pill radius `999px` (contract: buttons are `r-md` 8px; pill is
badges-only) — `keysat-xyz-landing/index.html:384-385`. Set to 8px.
**Structural (headline):**
- All four surfaces inline their own copy of the CSS variables instead of importing the
canonical `design/brand/palette.css` (landing :33-56, registry :11-22, docs.css :7-21,
admin :9-25). Copies are currently exact but one edit from drift. Consolidate onto
`palette.css`.
**Token gaps / drift (decide: tokenize the as-built value, or snap to an existing token):**
- `14px` card radius used throughout the marketing landing — not a token (between `r-lg` 12
and `r-xl` 18). Snap to a token or add one.
- Wordmark letter-spacing is `0.30em` (landing) vs `0.28em` (docs/admin) and has no token —
pick one value, add a `letterSpacing.wordmark` token.
- Semantic badge *text* colors (`#205c47`/`#7a5814`/`#8a2828`) are darker one-offs with no
token — add `semantic.*-text` tokens or reference existing ones.
- Syntax-highlight colors hardcoded as hex (`#d4b985`=gold-400, `#a6b7cf`=navy-300) — switch
to `var()`. One admin hex `#f6f1e7` isn't a token (closest cream-50/100) — reconcile.
- Sticky-header backdrop on docs/admin (`blur(10px)`/`blur(8px)`) diverges from the contract's
`blur(12px)` — align if a single header treatment is wanted.
+142
View File
@@ -0,0 +1,142 @@
---
project: keysat
tagline: Software licensing for Bitcoin creators
source: design/_imports/2026-06-16-claude-design-system/ (Claude Design output, May 2025)
tokens: design/tokens.tokens.json
canonical_css: design/brand/palette.css
last_distilled: 2026-06-16
---
# Keysat — design contract
> The durable, vendor-neutral brand brief. Any agent building or changing a user-facing
> surface (the marketing landing, the docs site, the creator admin SPA) reads this and
> `design/tokens.tokens.json` first and conforms to them. Token *values* live in the tokens
> file; this document is the *intent* and the rules. Distilled from a prior Claude Design
> system (now in `design/_imports/`).
## 1. Visual theme
Navy ink on cream paper, with a gold accent that whispers. The reference objects are a
**certificate of authenticity, a vault deed, a hand-numbered print** — classical trust
signaling crossed with modern indie-software practicality. Restrained, archival, precise;
**modern in interaction, classical in composition.** The identity is anchored by the logo: a
deep-navy key crossing a Bitcoin "B" bow, on cream paper with a gold inner border. The brand
reads as **printed, not liquid** — solid surfaces over glass and gradients.
## 2. Color palette
Values in `tokens.tokens.json`. Roles:
- **Navy is the primary brand color.** `navy-800` (`#1E3A5F`) is the wordmark color and
dominant ink — primary buttons, headings, key chrome. Full scale `navy-950 → navy-50`.
- **Cream is the page background.** `cream-100` (`#F5F1E8`) default; `cream-50` (`#FBF9F2`)
for elevated paper. **Pure white (`#FFFFFF`)** is reserved for forms, tables, and code
blocks where contrast matters.
- **Gold (`gold-500`, `#BFA068`) is the accent, used sparingly** — eyebrow labels, dividers,
the inner stroke of premium cards, a verified-badge highlight. **Never a primary button
color.**
- **Ink scale (`ink-900 → ink-300`)** handles body text, secondary copy, disabled states.
- **Semantic:** success `#2D7A5F`, warning `#B8861F`, danger `#B23A3A`, info = `navy-700`,
each with a tinted `-bg`.
- Borders are **alpha navy**, not solid lines: `border-1` 12%, `border-2` 20%, `border-3` 35%.
## 3. Typography
- **Display — Manrope** (`font-display`): geometric sans, mirrors the wordmark. h1h4, large
numerals, the wordmark. Weights 400700 (500 for h1/h2, 600 for h3/h4). *Note: the original
design-system README named "Archivo" as a placeholder substitution, but the shipped CSS and
every live surface use Manrope — Manrope is canonical. If a licensed display face is ever
supplied, swap `font.display` in the tokens and remove the Google Fonts import.*
- **Body — Inter** (`font-body`): humanist sans for prose, UI labels, form fields. Stylistic
sets `ss01` + `cv11`.
- **Mono — JetBrains Mono** (`font-mono`): license keys, code samples, API responses, tx IDs.
- **Type scale** is the `fs-*` tokens (`display-xl` clamp 5688px down to `meta` 12px).
Headings track tight (`-0.02em`); eyebrows track wide (`0.18em`), uppercase.
- **Casing:** sentence case for buttons/menus/headings ("Create a license"); ALL CAPS + wide
tracking only for eyebrow labels above sections, sparingly. Proper nouns capitalized
(Keysat, Start9, StartOS, BTCPay, Bitcoin, Lightning, Ed25519).
- **Identifiers:** license keys monospace, hyphen-grouped (`KS-9F2A-7C41-XK22-6D8E`); keys/
hashes ellipsized middle (`mz7q8…h3k2p`); amounts default to **sats** under 0.01 BTC.
## 4. Component styling
- **Buttons** — radius `r-md` (8px). Primary: `navy-800` bg, `cream-50` text; hover `navy-900`,
press `navy-950` + 1px translate-down (no scale). Secondary: `cream-50` bg, alpha-navy
border; hover `cream-200`. Ghost: transparent; hover `rgba(14,31,51,0.05)`. **Gold is a
text/border treatment only, never a primary button fill.** Danger: red text + faint red
border.
- **Cards** — radius `r-lg` (12px), on cream with a hairline `border-1` and `shadow-sm`.
Premium/featured cards get a **1px gold inner stroke** (`gold-500`) + `shadow-md`.
- **Badges** — pill (`r-pill`), tinted semantic bg + matching text; gold/neutral = transparent
with a gold border.
- **Forms** — white bg, alpha-navy border, radius ~7px; focus = navy border + `ring-focus`
halo. Monospace variant for keys.
- **Code blocks** — `navy-950` bg, `cream-50` text, `r-lg`, JetBrains Mono; gold/cream syntax.
- **Icons — Lucide**, stroke 1.75px, 16/20/24px. **No emoji in product UI. No PNG icons** (the
logo mark is SVG; only the source thumbnail is PNG). The logo mark is never recolored —
navy, or cream on dark surfaces.
## 5. Layout
- **4px base grid**, spacing tokens `sp-1` (4px) → `sp-12` (128px). Use the scale; don't mint
magic numbers.
- Marketing pages **breathe** — sections often `sp-11` (96px) apart. Dashboard density is
moderate: table rows ~52px, card padding `sp-6` (24px).
- Max content width on marketing ~1200px; reading width for prose/docs ~680px.
- **Backgrounds are cream with a subtle grain** (the `paper-texture` utility — two radial-dot
grids at ~2.5% opacity), never flat color. Section bands alternate cream → cream-200 →
cream-50 → navy-950 (dark CTAs/footers).
## 6. Depth / elevation
A **paper-shadow** system, not a glassy one. `shadow-xs`/`sm` for resting cards; `shadow-md`
for elevated/premium cards; `shadow-lg` for popovers/menus; `shadow-xl` for modals/command
palettes. `shadow-inset` gives buttons subtle paper relief. Elevation comes from **shadow,
not heavy borders**. Transparency/blur is rare — acceptable only for the sticky marketing
header (`blur(12px)` over `rgba(245,241,232,0.85)`) and the modal scrim (`rgba(14,31,51,0.55)`).
## 7. Do's and don'ts
**Do**
- Lead copy with *what the creator owns* — the signing key, the customer list, the payment
rails. Sovereignty-first is the brand's center of gravity.
- Keep gold rare and accenting; keep motion quiet (200ms standard, 120ms hover).
- Use solid surfaces, restrained radii, hairline alpha borders, the paper grain.
- Link a one-sentence definition the first time a term appears (Ed25519, BTCPay webhook,
`.s9pk`).
**Don't**
- **No hype words:** revolutionary, seamless, unlock, supercharge, leverage, ecosystem,
journey, paradigm, game-changing.
- **No emoji** in product UI; **no PNG icons**; **no Bitcoin orange** in the UI (navy/cream
stands alone); no blue/purple gradients, no glassmorphism.
- **Gold is never a primary button fill.** Radii never exceed ~18px (`r-xl`) for surfaces —
no 24px+ rounding (reads as a consumer fintech app, which Keysat is not).
- No spring physics, no scale-up on hover (cards move at most 1px; buttons darken, not grow).
- Never remove focus rings.
## 8. Responsive behavior
Breakpoints in use: **980px** (grids collapse), **720px** (sidebar → slide-in drawer),
**640px** (phone: tighter chrome, single column). Container padding steps 32px desktop → 20px
tablet → 14px phone. Marketing is full-width hero + stacked sections; docs is a 3-column
(sidebar | prose | TOC) collapsing to single column at 980px; admin is a 240px sticky sidebar
+ main, sidebar drawering at 720px.
## 9. Agent prompt guide
When building or changing a Keysat UI:
- **Pull every color, size, space, radius, and shadow from `design/tokens.tokens.json`** (or a
CSS custom property derived from it — see `design/brand/palette.css`). Do not hardcode hex or
px values that bypass the scale.
- Match the **existing surface** you're editing (landing / docs / admin) — they share the token
set; keep them consistent rather than introducing a new look.
- Default to **navy primary buttons, cream pages, gold-as-accent-only**, paper shadows, Lucide
icons, sentence-case labels, no emoji.
- If a needed value genuinely isn't in the tokens, **add it to the tokens file** (and ideally
`palette.css`) rather than inlining a one-off — keep the contract the single source of truth.
- Known debt (see ROADMAP): the three surfaces currently inline their own copy of the
variables. Prefer importing `design/brand/palette.css`; when you touch a surface, move it
toward that shared source rather than perpetuating a private copy.

Before

Width:  |  Height:  |  Size: 226 B

After

Width:  |  Height:  |  Size: 226 B

Before

Width:  |  Height:  |  Size: 618 B

After

Width:  |  Height:  |  Size: 618 B

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 828 B

After

Width:  |  Height:  |  Size: 828 B

Before

Width:  |  Height:  |  Size: 831 B

After

Width:  |  Height:  |  Size: 831 B

Before

Width:  |  Height:  |  Size: 843 B

After

Width:  |  Height:  |  Size: 843 B

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
<text x="16" y="24" text-anchor="middle" font-family="Archivo, Helvetica, sans-serif" font-weight="900" font-size="28" fill="#1E3A5F"></text>
</svg>

After

Width:  |  Height:  |  Size: 226 B

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
<rect width="32" height="32" rx="6" fill="#F5F1E8"></rect>
<ellipse cx="16" cy="9" rx="9" ry="1.6" fill="#1E3A5F"></ellipse>
<rect x="7" y="9" width="18" height="16" fill="#FBF9F2" stroke="#1E3A5F" stroke-width="1.4"></rect>
<ellipse cx="16" cy="25" rx="9" ry="1.6" fill="#1E3A5F"></ellipse>
<circle cx="13" cy="17" r="2.6" fill="none" stroke="#BFA068" stroke-width="1.4"></circle>
<rect x="15.6" y="16.4" width="6" height="1.5" fill="#BFA068"></rect>
<rect x="20" y="17.9" width="0.9" height="1.8" fill="#BFA068"></rect>
</svg>

After

Width:  |  Height:  |  Size: 618 B

+14
View File
@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 280 80" fill="none">
<g transform="translate(0,0)">
<ellipse cx="40" cy="20" rx="26" ry="4.5" fill="#1E3A5F"></ellipse>
<rect x="14" y="20" width="52" height="52" fill="#FBF9F2" stroke="#1E3A5F" stroke-width="2.8"></rect>
<ellipse cx="40" cy="72" rx="26" ry="4.5" fill="#1E3A5F"></ellipse>
<line x1="23" y1="33" x2="57" y2="33" stroke="#1E3A5F" stroke-width="1.4" stroke-linecap="round"></line>
<line x1="23" y1="40" x2="52" y2="40" stroke="#1E3A5F" stroke-width="1.4" stroke-linecap="round"></line>
<circle cx="32" cy="55" r="5.5" fill="none" stroke="#BFA068" stroke-width="2.3"></circle>
<rect x="38" y="53.7" width="13" height="2.7" fill="#BFA068"></rect>
<rect x="47" y="56.4" width="1.8" height="3.6" fill="#BFA068"></rect>
<rect x="51" y="56.4" width="1.8" height="2.7" fill="#BFA068"></rect>
</g>
<text x="92" y="52" font-family="Manrope, system-ui, sans-serif" font-weight="500" font-size="32" letter-spacing="9" fill="#1E3A5F">KEYSAT</text>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
<ellipse cx="50" cy="22" rx="28" ry="5" fill="#1E3A5F"></ellipse>
<rect x="22" y="22" width="56" height="56" fill="none" stroke="#1E3A5F" stroke-width="3"></rect>
<ellipse cx="50" cy="78" rx="28" ry="5" fill="#1E3A5F"></ellipse>
<line x1="32" y1="36" x2="68" y2="36" stroke="#1E3A5F" stroke-width="1.5" stroke-linecap="round"></line>
<line x1="32" y1="44" x2="62" y2="44" stroke="#1E3A5F" stroke-width="1.5" stroke-linecap="round"></line>
<circle cx="42" cy="60" r="6" fill="none" stroke="#1E3A5F" stroke-width="2.5"></circle>
<rect x="48" y="58.5" width="14" height="3" fill="#1E3A5F"></rect>
<rect x="58" y="61.5" width="2" height="4" fill="#1E3A5F"></rect>
<rect x="62" y="61.5" width="2" height="3" fill="#1E3A5F"></rect>
</svg>

After

Width:  |  Height:  |  Size: 828 B

+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
<ellipse cx="50" cy="22" rx="28" ry="5" fill="#FBF9F2"></ellipse>
<rect x="22" y="22" width="56" height="56" fill="#0E1F33" stroke="#FBF9F2" stroke-width="3"></rect>
<ellipse cx="50" cy="78" rx="28" ry="5" fill="#FBF9F2"></ellipse>
<line x1="32" y1="36" x2="68" y2="36" stroke="#FBF9F2" stroke-width="1.5" stroke-linecap="round"></line>
<line x1="32" y1="44" x2="62" y2="44" stroke="#FBF9F2" stroke-width="1.5" stroke-linecap="round"></line>
<circle cx="42" cy="60" r="6" fill="none" stroke="#BFA068" stroke-width="2.5"></circle>
<rect x="48" y="58.5" width="14" height="3" fill="#BFA068"></rect>
<rect x="58" y="61.5" width="2" height="4" fill="#BFA068"></rect>
<rect x="62" y="61.5" width="2" height="3" fill="#BFA068"></rect>
</svg>

After

Width:  |  Height:  |  Size: 831 B

+16
View File
@@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
<ellipse cx="50" cy="22" rx="28" ry="5" fill="#1E3A5F"></ellipse>
<rect x="22" y="22" width="56" height="56" fill="#FBF9F2" stroke="#1E3A5F" stroke-width="3"></rect>
<ellipse cx="50" cy="78" rx="28" ry="5" fill="#1E3A5F"></ellipse>
<line x1="32" y1="36" x2="68" y2="36" stroke="#1E3A5F" stroke-width="1.5" stroke-linecap="round"></line>
<line x1="32" y1="44" x2="62" y2="44" stroke="#1E3A5F" stroke-width="1.5" stroke-linecap="round"></line>
<circle cx="42" cy="60" r="6" fill="none" stroke="#BFA068" stroke-width="2.5"></circle>
<rect x="48" y="58.5" width="14" height="3" fill="#BFA068"></rect>
<rect x="58" y="61.5" width="2" height="4" fill="#BFA068"></rect>
<rect x="62" y="61.5" width="2" height="3" fill="#BFA068"></rect>
</svg>

After

Width:  |  Height:  |  Size: 843 B

+285
View File
@@ -0,0 +1,285 @@
/* ============================================================
Keysat — palette.css (CANONICAL design tokens as CSS custom properties)
"Software Licensing for Bitcoin Creators"
Navy + cream, paper texture, classical type.
Single canonical stylesheet, derived from design/tokens.tokens.json.
Surfaces (landing, docs, admin SPA) should @import or inline THIS file
rather than keep private copies — see the "shared token source" item in
ROADMAP.md. Keep this and the tokens JSON in sync; never fork a one-off copy.
============================================================ */
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
:root {
/* ---------- Brand Colors ---------- */
/* Primary navy — pulled from the wordmark */
--navy-950: #0E1F33;
--navy-900: #142A47;
--navy-800: #1E3A5F; /* core brand navy */
--navy-700: #2A4A75;
--navy-600: #3A5C8A;
--navy-500: #5074A1;
--navy-400: #7892B8;
--navy-300: #A6B7CF;
--navy-200: #CBD5E2;
--navy-100: #E4EAF1;
--navy-50: #F2F5F9;
/* Cream / paper — the background tone of the logo card */
--cream-50: #FBF9F2;
--cream-100: #F5F1E8; /* core cream */
--cream-200: #EDE7D7;
--cream-300: #E1D8C0;
--cream-400: #C9BC9A;
/* Gold / tan — the inner key border */
--gold-700: #8A6F3D;
--gold-600: #A88652;
--gold-500: #BFA068; /* core gold accent */
--gold-400: #D4B985;
--gold-300: #E5CFA5;
--gold-200: #F0E2C5;
/* Ink — dark text */
--ink-900: #0E1F33;
--ink-700: #2C3E54;
--ink-500: #5A6B7F;
--ink-400: #7E8C9D;
--ink-300: #A4AEBB;
/* Semantic */
--success: #2D7A5F;
--success-bg: #E3F0EA;
--warning: #B8861F;
--warning-bg: #F7EFD7;
--danger: #B23A3A;
--danger-bg: #F4E0E0;
--info: var(--navy-700);
--info-bg: var(--navy-100);
/* ---------- Semantic surface tokens ---------- */
--bg-page: var(--cream-100); /* default page bg */
--bg-paper: var(--cream-50); /* lighter paper */
--bg-elev: #FFFFFF; /* elevated surface (cards on cream) */
--bg-inverse: var(--navy-900); /* dark surface */
--bg-tint: var(--cream-200); /* tinted band/section */
--fg-1: var(--ink-900); /* primary text */
--fg-2: var(--ink-700); /* secondary text */
--fg-3: var(--ink-500); /* tertiary / meta */
--fg-4: var(--ink-400); /* disabled / hint */
--fg-on-navy: var(--cream-50);
--fg-on-gold: var(--navy-900);
--border-1: rgba(14, 31, 51, 0.12); /* hairline on cream */
--border-2: rgba(14, 31, 51, 0.20); /* card border */
--border-3: rgba(14, 31, 51, 0.35); /* focus / strong */
--border-on-navy: rgba(245, 241, 232, 0.18);
--accent: var(--navy-800);
--accent-hover: var(--navy-900);
--accent-press: var(--navy-950);
--accent-soft: var(--navy-100);
--gold: var(--gold-500);
--gold-hover: var(--gold-600);
/* ---------- Type families ---------- */
--font-display: 'Manrope', 'Helvetica Neue', Arial, sans-serif;
--font-body: 'Inter', 'Helvetica Neue', Arial, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
/* ---------- Type scale ---------- */
--fs-display-xl: clamp(56px, 6vw, 88px);
--fs-display: clamp(40px, 4.5vw, 64px);
--fs-h1: 44px;
--fs-h2: 32px;
--fs-h3: 24px;
--fs-h4: 20px;
--fs-h5: 17px;
--fs-body-lg: 18px;
--fs-body: 15px;
--fs-body-sm: 13.5px;
--fs-meta: 12px;
--fs-mono: 13px;
/* ---------- Line heights ---------- */
--lh-display: 1.02;
--lh-heading: 1.15;
--lh-body: 1.55;
--lh-tight: 1.25;
/* ---------- Letter spacing ---------- */
--tracking-tight: -0.02em;
--tracking-normal: 0;
--tracking-wide: 0.04em;
--tracking-eyebrow: 0.18em;
/* ---------- Spacing (4px base) ---------- */
--sp-1: 4px;
--sp-2: 8px;
--sp-3: 12px;
--sp-4: 16px;
--sp-5: 20px;
--sp-6: 24px;
--sp-7: 32px;
--sp-8: 40px;
--sp-9: 56px;
--sp-10: 72px;
--sp-11: 96px;
--sp-12: 128px;
/* ---------- Radii ---------- */
--r-xs: 3px;
--r-sm: 5px;
--r-md: 8px;
--r-lg: 12px;
--r-xl: 18px;
--r-2xl: 24px;
--r-pill: 999px;
/* ---------- Shadows ---------- */
/* Quiet, layered shadows — paper, not glassy */
--shadow-xs: 0 1px 1px rgba(14,31,51,0.04);
--shadow-sm: 0 1px 2px rgba(14,31,51,0.06), 0 1px 1px rgba(14,31,51,0.03);
--shadow-md: 0 2px 4px rgba(14,31,51,0.06), 0 4px 12px rgba(14,31,51,0.06);
--shadow-lg: 0 4px 8px rgba(14,31,51,0.07), 0 12px 32px rgba(14,31,51,0.10);
--shadow-xl: 0 8px 16px rgba(14,31,51,0.10), 0 24px 64px rgba(14,31,51,0.14);
--shadow-inset: inset 0 1px 0 rgba(255,255,255,0.6), inset 0 -1px 0 rgba(14,31,51,0.05);
--ring-focus: 0 0 0 3px rgba(30,58,95,0.25);
/* ---------- Motion ---------- */
--ease-standard: cubic-bezier(0.2, 0.7, 0.2, 1);
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in: cubic-bezier(0.7, 0, 0.84, 0);
--dur-fast: 120ms;
--dur-base: 200ms;
--dur-slow: 360ms;
}
/* ============================================================
Paper texture — subtle grain on cream surfaces
============================================================ */
.paper-texture {
background-color: var(--bg-page);
background-image:
radial-gradient(rgba(14,31,51,0.025) 1px, transparent 1px),
radial-gradient(rgba(138,111,61,0.022) 1px, transparent 1px);
background-size: 3px 3px, 7px 7px;
background-position: 0 0, 1px 1px;
}
.paper-texture-strong {
background-color: var(--bg-page);
background-image:
radial-gradient(rgba(14,31,51,0.04) 1px, transparent 1.4px),
radial-gradient(rgba(138,111,61,0.035) 1px, transparent 1.2px);
background-size: 3px 3px, 7px 7px;
}
/* ============================================================
Element defaults — drop these into a body class .keysat
============================================================ */
.keysat {
font-family: var(--font-body);
font-size: var(--fs-body);
line-height: var(--lh-body);
color: var(--fg-1);
background: var(--bg-page);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-feature-settings: 'ss01', 'cv11';
}
.keysat h1, .keysat .h1 {
font-family: var(--font-display);
font-size: var(--fs-h1);
font-weight: 500;
line-height: var(--lh-heading);
letter-spacing: var(--tracking-tight);
color: var(--fg-1);
margin: 0;
}
.keysat h2, .keysat .h2 {
font-family: var(--font-display);
font-size: var(--fs-h2);
font-weight: 500;
line-height: var(--lh-heading);
letter-spacing: var(--tracking-tight);
color: var(--fg-1);
margin: 0;
}
.keysat h3, .keysat .h3 {
font-family: var(--font-display);
font-size: var(--fs-h3);
font-weight: 600;
line-height: var(--lh-tight);
letter-spacing: var(--tracking-tight);
margin: 0;
}
.keysat h4, .keysat .h4 {
font-family: var(--font-display);
font-size: var(--fs-h4);
font-weight: 600;
line-height: var(--lh-tight);
margin: 0;
}
.keysat h5, .keysat .h5 {
font-family: var(--font-body);
font-size: var(--fs-h5);
font-weight: 600;
line-height: var(--lh-tight);
margin: 0;
}
.keysat .display-xl {
font-family: var(--font-display);
font-size: var(--fs-display-xl);
font-weight: 500;
line-height: var(--lh-display);
letter-spacing: -0.022em;
}
.keysat .display {
font-family: var(--font-display);
font-size: var(--fs-display);
font-weight: 500;
line-height: var(--lh-display);
letter-spacing: -0.022em;
}
.keysat .wordmark {
font-family: var(--font-display);
font-weight: 500;
letter-spacing: 0.32em;
text-transform: uppercase;
color: var(--navy-800);
}
.keysat .eyebrow {
font-family: var(--font-body);
font-size: 11.5px;
font-weight: 600;
letter-spacing: var(--tracking-eyebrow);
text-transform: uppercase;
color: var(--gold-700);
}
.keysat p { margin: 0 0 1em 0; color: var(--fg-2); }
.keysat .lead {
font-size: var(--fs-body-lg);
line-height: 1.5;
color: var(--fg-2);
}
.keysat .meta {
font-size: var(--fs-meta);
color: var(--fg-3);
letter-spacing: 0.02em;
}
.keysat code, .keysat .mono {
font-family: var(--font-mono);
font-size: var(--fs-mono);
font-feature-settings: 'ss02';
}
.keysat a {
color: var(--accent);
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 2px;
}
.keysat a:hover { color: var(--accent-hover); }
+171
View File
@@ -0,0 +1,171 @@
{
"$description": "Keysat design tokens — W3C DTCG-shaped, distilled from design/_imports/2026-06-16-claude-design-system/colors_and_type.css (as-built values). Group-level $type is set where it maps cleanly to a DTCG primitive; a few groups (fontSize, letterSpacing, shadow) carry as-built CSS expressions (clamp(), em, multi-layer shadows) as strings and are intentionally left untyped pending a Style Dictionary build. See design/DESIGN.md for intent and rules.",
"color": {
"$type": "color",
"navy": {
"950": { "$value": "#0E1F33" },
"900": { "$value": "#142A47" },
"800": { "$value": "#1E3A5F", "$description": "core brand navy — wordmark, primary buttons, dominant ink" },
"700": { "$value": "#2A4A75" },
"600": { "$value": "#3A5C8A" },
"500": { "$value": "#5074A1" },
"400": { "$value": "#7892B8" },
"300": { "$value": "#A6B7CF" },
"200": { "$value": "#CBD5E2" },
"100": { "$value": "#E4EAF1" },
"50": { "$value": "#F2F5F9" }
},
"cream": {
"50": { "$value": "#FBF9F2", "$description": "elevated paper / card surface" },
"100": { "$value": "#F5F1E8", "$description": "core cream — default page background" },
"200": { "$value": "#EDE7D7" },
"300": { "$value": "#E1D8C0" },
"400": { "$value": "#C9BC9A" }
},
"gold": {
"700": { "$value": "#8A6F3D", "$description": "eyebrow labels / dark gold accent" },
"600": { "$value": "#A88652" },
"500": { "$value": "#BFA068", "$description": "core gold accent — sparing use only, never a primary button fill" },
"400": { "$value": "#D4B985" },
"300": { "$value": "#E5CFA5" },
"200": { "$value": "#F0E2C5" }
},
"ink": {
"900": { "$value": "#0E1F33", "$description": "primary text" },
"700": { "$value": "#2C3E54", "$description": "secondary text" },
"500": { "$value": "#5A6B7F", "$description": "tertiary / meta" },
"400": { "$value": "#7E8C9D", "$description": "disabled / hint" },
"300": { "$value": "#A4AEBB" }
},
"semantic": {
"success": { "$value": "#2D7A5F" },
"success-bg": { "$value": "#E3F0EA" },
"warning": { "$value": "#B8861F" },
"warning-bg": { "$value": "#F7EFD7" },
"danger": { "$value": "#B23A3A" },
"danger-bg": { "$value": "#F4E0E0" },
"info": { "$value": "{color.navy.700}" },
"info-bg": { "$value": "{color.navy.100}" }
},
"surface": {
"page": { "$value": "{color.cream.100}", "$description": "default page bg (apply with paper-texture grain)" },
"paper": { "$value": "{color.cream.50}" },
"elevated":{ "$value": "#FFFFFF", "$description": "forms, tables, code blocks — reserved white" },
"inverse": { "$value": "{color.navy.900}" },
"tint": { "$value": "{color.cream.200}" }
},
"fg": {
"primary": { "$value": "{color.ink.900}" },
"secondary": { "$value": "{color.ink.700}" },
"tertiary": { "$value": "{color.ink.500}" },
"hint": { "$value": "{color.ink.400}" },
"on-navy": { "$value": "{color.cream.50}" },
"on-gold": { "$value": "{color.navy.900}" }
},
"accent": {
"default": { "$value": "{color.navy.800}" },
"hover": { "$value": "{color.navy.900}" },
"press": { "$value": "{color.navy.950}" },
"soft": { "$value": "{color.navy.100}" },
"gold": { "$value": "{color.gold.500}" }
},
"border": {
"1": { "$value": "rgba(14, 31, 51, 0.12)", "$description": "hairline on cream" },
"2": { "$value": "rgba(14, 31, 51, 0.20)", "$description": "card border" },
"3": { "$value": "rgba(14, 31, 51, 0.35)", "$description": "focus / strong" },
"on-navy": { "$value": "rgba(245, 241, 232, 0.18)" }
}
},
"font": {
"$type": "fontFamily",
"display": { "$value": ["Manrope", "Helvetica Neue", "Arial", "sans-serif"], "$description": "canonical display face (README's 'Archivo' was a stale placeholder)" },
"body": { "$value": ["Inter", "Helvetica Neue", "Arial", "sans-serif"] },
"mono": { "$value": ["JetBrains Mono", "ui-monospace", "SF Mono", "Menlo", "monospace"] }
},
"fontSize": {
"$description": "as-built type scale; display sizes use CSS clamp() and are kept as strings",
"display-xl": { "$value": "clamp(56px, 6vw, 88px)" },
"display": { "$value": "clamp(40px, 4.5vw, 64px)" },
"h1": { "$value": "44px" },
"h2": { "$value": "32px" },
"h3": { "$value": "24px" },
"h4": { "$value": "20px" },
"h5": { "$value": "17px" },
"body-lg": { "$value": "18px" },
"body": { "$value": "15px" },
"body-sm": { "$value": "13.5px" },
"meta": { "$value": "12px" },
"mono": { "$value": "13px" }
},
"lineHeight": {
"$type": "number",
"display": { "$value": 1.02 },
"heading": { "$value": 1.15 },
"body": { "$value": 1.55 },
"tight": { "$value": 1.25 }
},
"letterSpacing": {
"$description": "em values kept as strings",
"tight": { "$value": "-0.02em" },
"normal": { "$value": "0" },
"wide": { "$value": "0.04em" },
"eyebrow": { "$value": "0.18em" }
},
"space": {
"$type": "dimension",
"1": { "$value": "4px" },
"2": { "$value": "8px" },
"3": { "$value": "12px" },
"4": { "$value": "16px" },
"5": { "$value": "20px" },
"6": { "$value": "24px" },
"7": { "$value": "32px" },
"8": { "$value": "40px" },
"9": { "$value": "56px" },
"10": { "$value": "72px" },
"11": { "$value": "96px" },
"12": { "$value": "128px" }
},
"radius": {
"$type": "dimension",
"xs": { "$value": "3px" },
"sm": { "$value": "5px" },
"md": { "$value": "8px", "$description": "buttons" },
"lg": { "$value": "12px", "$description": "cards" },
"xl": { "$value": "18px", "$description": "max surface rounding — never exceed" },
"2xl": { "$value": "24px", "$description": "reserved; avoid in product UI" },
"pill": { "$value": "999px", "$description": "tags/badges only" }
},
"shadow": {
"$description": "paper-shadow system — multi-layer CSS strings, not glassy",
"xs": { "$value": "0 1px 1px rgba(14,31,51,0.04)" },
"sm": { "$value": "0 1px 2px rgba(14,31,51,0.06), 0 1px 1px rgba(14,31,51,0.03)" },
"md": { "$value": "0 2px 4px rgba(14,31,51,0.06), 0 4px 12px rgba(14,31,51,0.06)" },
"lg": { "$value": "0 4px 8px rgba(14,31,51,0.07), 0 12px 32px rgba(14,31,51,0.10)" },
"xl": { "$value": "0 8px 16px rgba(14,31,51,0.10), 0 24px 64px rgba(14,31,51,0.14)" },
"inset": { "$value": "inset 0 1px 0 rgba(255,255,255,0.6), inset 0 -1px 0 rgba(14,31,51,0.05)" },
"ring-focus": { "$value": "0 0 0 3px rgba(30,58,95,0.25)" }
},
"duration": {
"$type": "duration",
"fast": { "$value": "120ms", "$description": "hover transitions" },
"base": { "$value": "200ms", "$description": "default" },
"slow": { "$value": "360ms" }
},
"easing": {
"$type": "cubicBezier",
"standard": { "$value": [0.2, 0.7, 0.2, 1] },
"out": { "$value": [0.16, 1, 0.3, 1] },
"in": { "$value": [0.7, 0, 0.84, 0] }
}
}