Keynctr/DESIGN.md
Avi d4d87b85b6 docs: replace blanket 'keys never leave the machine' claim with per-mode truth
External NIP-46 signer mode is a stronger posture, not a caveat: the key
never arrives on this machine, so a compromised desktop cannot extract it.
The old claim only holds for embedded/bunker modes and undersold the
external-signer option. UI already ranked modes correctly; no code change.
2026-09-22 17:46:17 -05:00

263 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: Nostr Keynctr
description: Local-first Nostr identity atelier — keys stay in Rust, the UI stays calm and warm.
colors:
warm-ivory: "#f6f4f0"
soft-stone: "#f1ede7"
stone-hover: "#faf8f5"
paper: "#ffffff"
ink: "#201b15"
ink-muted: "#6d655a"
line: "#e6e0d6"
line-strong: "#d8d0c3"
antique-brass: "#6d5ae6"
antique-brass-hover: "#5c49d4"
antique-brass-soft: "#efecfd"
on-brass: "#ffffff"
signal-coral: "#c0392b"
signal-coral-soft: "#fdecec"
antique-gold: "#9a6b13"
antique-gold-soft: "#fcf3e0"
muted-sage: "#22713c"
muted-sage-soft: "#e7f5ec"
clear-ink: "#2868a4"
clear-ink-soft: "#e9f2fb"
focus: "#6d5ae6"
typography:
display:
fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif"
fontSize: "24px"
fontWeight: 650
lineHeight: 1.25
letterSpacing: "normal"
headline:
fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif"
fontSize: "16px"
fontWeight: 650
lineHeight: 1.25
letterSpacing: "normal"
body:
fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif"
fontSize: "15px"
fontWeight: 400
lineHeight: 1.5
letterSpacing: "normal"
label:
fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif"
fontSize: "13px"
fontWeight: 600
lineHeight: 1.5
letterSpacing: "normal"
mono:
fontFamily: "'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace"
fontSize: "13.8px"
fontWeight: 400
lineHeight: 1.5
letterSpacing: "normal"
rounded:
sm: "9px"
md: "14px"
lg: "16px"
pill: "999px"
spacing:
xs: "8px"
sm: "12px"
md: "16px"
lg: "20px"
xl: "32px"
components:
button-primary:
backgroundColor: "{colors.antique-brass}"
textColor: "{colors.on-brass}"
rounded: "{rounded.sm}"
padding: "10px 18px"
button-primary-hover:
backgroundColor: "{colors.antique-brass-hover}"
textColor: "{colors.on-brass}"
rounded: "{rounded.sm}"
padding: "10px 18px"
button-secondary:
backgroundColor: "{colors.paper}"
textColor: "{colors.ink}"
rounded: "{rounded.sm}"
padding: "10px 18px"
button-ghost:
backgroundColor: "transparent"
textColor: "{colors.ink-muted}"
rounded: "{rounded.sm}"
padding: "7px 12px"
button-danger:
backgroundColor: "{colors.signal-coral-soft}"
textColor: "{colors.signal-coral}"
rounded: "{rounded.sm}"
padding: "7px 12px"
card:
backgroundColor: "{colors.paper}"
textColor: "{colors.ink}"
rounded: "{rounded.md}"
padding: "18px"
input:
backgroundColor: "{colors.paper}"
textColor: "{colors.ink}"
rounded: "{rounded.sm}"
padding: "10px 12px"
---
# Design System: Nostr Keynctr
## Overview
**Creative North Star: "Vault & Atelier"**
Nostr Keynctr is an atelier, not a dashboard — a warm, quiet workshop where identity work is done with care. The space feels like heavy paper and soft stone, with ink that is near-black, not pure black. Instruments are laid out plainly; nothing shouts for attention. Trust is built through precision: consistent edges, settled type, and state that is always legible. The product truth — in local modes keys never leave Rust, and in external-signer mode they never arrive on this machine at all — is mirrored visually: the UI is restrained, the material is honest, and every destructive or security-relevant moment is given deliberate weight.
The aesthetic is *warm and human*, not technical or bold. Density is Operate: scannable lists, clear hierarchies, and generous but not loose spacing (8/12/16/20/32). The four themes (light, dark, glass/Aurora, neon) share the same semantic roles; only the material values shift. Neon and glass are gated expressions, never the default.
**Key Characteristics:**
- Warm and human, precise and calm — humanity first, then exactness.
- Paper and stone material language — tonal layering over heavy shadows.
- Semantic tokens only — every color has a job (public data vs. private operation vs. relay health vs. security).
- Operate density — tasks complete faster because hierarchy is consistent.
- Vault logic made visible — locked/muted states are distinct at a glance.
## Colors
Four themes share the same semantic roles; values below are the light (paper) baseline extracted from `frontend/src/styles.css` (`:root` / `:root[data-theme='light']`). Dark, neon, and glass override the same tokens (see Elevation notes for glass treatment).
### Primary
- **Antique Brass — restrained identity accent** (`{colors.antique-brass}` #6d5ae6): Used for primary actions, active nav (`is-active`), focus rings, and the themed undo bar link ("Undo and restore profile"). Restrained — ≤10% of any screen. Hover is `{colors.antique-brass-hover}` #5c49d4; wash is `{colors.antique-brass-soft}` #efecfd.
- **Focus Violet** (`{colors.focus}` #6d5ae6): Same value as primary; outlines via `outline: 2px solid var(--focus)`.
### Neutral
- **Warm Ivory — canvas** (`{colors.warm-ivory}` #f6f4f0): `var(--bg)` — page background.
- **Paper — surface** (`{colors.paper}` #ffffff): `var(--surface)` — cards, sidebar, modals.
- **Soft Stone — raised/tinted surface** (`{colors.soft-stone}` #f1ede7): `var(--surface-2)` — inline code, secondary surfaces, segmented control track, `create-explainer`.
- **Stone Hover — hover wash** (`{colors.stone-hover}` #faf8f5): `var(--surface-hover)`.
- **Ink — primary text** (`{colors.ink}` #201b15): `var(--text)` — near-black, never pure black.
- **Ink Muted — secondary text** (`{colors.ink-muted}` #6d655a): `var(--text-muted)` — subtitles, hints, `.muted`, dates.
- **Line — hairline border** (`{colors.line}` #e6e0d6): `var(--border)`.
- **Line Strong — emphasized border** (`{colors.line-strong}` #d8d0c3): `var(--border-strong)` — input strokes, secondary buttons.
### Semantic Feedback
- **Muted Sage — secure/success** (`{colors.muted-sage}` #22713c / wash #e7f5ec): active profile border, success badges/alerts. Not decorative — only for successful security-state confirmation.
- **Signal Coral — destructive/warning/error** (`{colors.signal-coral}` #c0392b / wash #fdecec): danger buttons, error alerts, relay bad dots. Destructive actions live here alone.
- **Antique Gold — caution** (`{colors.antique-gold}` #9a6b13 / wash #fcf3e0): warning badges/alerts.
- **Clear Ink — information/relay** (`{colors.clear-ink}` #2868a4 / wash #e9f2fb): info badges/alerts, relay neutral states.
- **On Brass — text on accent** (`{colors.on-brass}` #ffffff): label inside primary buttons.
**Theme overrides (same roles, different material):** Dark inverts to ink surfaces (`#211d29`, `#2a2534`) with primary `#8b7cff`; Neon uses pure black panels (`#000000`) and Cyber Pink primary `#ff1493` + Aurora Purple/Yellow highlights; Glass (Aurora) uses translucent `rgba(23,29,41,0.58)` surfaces with `backdrop-filter: blur(18px) saturate(160%)` and cyan primary `#7fdbff`. Tokens stay semantically identical, so components do not branch per theme.
### Named Rules
**The One Accent Rule.** Antique Brass is the only saturated accent in the restrained palette. Its rarity is the point — use it for primary actions and active state only.
**The Vault Separation Rule.** Secret-dependent UI never shares color with public data: private operations are flagged via `muted-sage`/`signal-coral` and explicit Approve/Reject, never via primary alone.
## Typography
**Display Font:** System UI stack (`system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif`) — no custom display face; trust is in weight and rhythm, not ornament.
**Body Font:** Same System UI stack — continuity over contrast.
**Label/Mono Font:** `SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace` — code and `npub` only.
**Character:** Humanist, quiet, and precise. Weight 650 for headings is the only emphasis; body is regular 15/1.5 for long-form readability without dense compaction.
### Hierarchy
- **Display** (650, 24px, 1.25): `h1` page titles only — one per screen.
- **Headline** (650, 16px, 1.25): `h2` card heads, modal titles.
- **Title** (650, 16px, 1.25): Profile/cluster names (`.profile-name`, `.profile-card-meta h3`).
- **Body** (400, 15px, 1.5): All reading text, `p`, `li`, `.page-subtitle` (14px muted variant). Max width ~65–75ch in cards.
- **Label** (600, 13px): Field labels, `.hint`, `.char-count`, `.field > label`.
- **Mono** (400, 13.8px / 0.92em): `code`, `.mono`, npub displays — always on `Soft Stone` pill.
### Named Rules
**The Weight Restraint Rule.** Only headings are 650. Do not add semibold to body or emphasize muted text with weight — color shift (`ink` → `ink-muted`) is the emphasis.
## Layout
**Canvas:** Centered column (`max-width: 920px`, `margin: 0 auto`) with screen padding `32px 40px 56px` (collapses proportionally on narrow viewports). Inner stack via `.screen-inner` (`gap: 20px`, `flex-direction: column`).
**Shell:** Fixed sidebar `248px` + flexible `.main` (`overflow-y: auto`). Sidebar is paper on light, translucent blurred pane on glass. App is `height: 100vh` locked; only main scrolls.
**Grids:** Home is single-column (`gap: 20px`). Profiles use responsive `repeat(auto-fill, minmax(280px, 1fr))` (`gap: 16px`). Relays are vertical list (`gap: 12px`). Compose card keeps tabs above editor; preview stacks below.
**Spacing rhythm:** 8/12/16/20/32. Section gaps 20px; card internal padding 18–20px; button padding `10px 18px` (md) / `7px 12px` (sm); input padding `10px 12px`. No arbitrary values — step on the scale.
**Responsive:** Flex-wrap at page heads and action rows; segmented controls wrap; profile grid collapses to single column; modal max-width `520px`, max-height `88vh`.
## Elevation & Depth
System is **flat-by-default, tonal before lifted.** Depth is conveyed first by material contrast (Warm Ivory → Paper → Soft Stone) and 1px `Line` borders. Shadows are whisper-low and never the primary separation.
### Shadow Vocabulary
- **Paper Lift** (`box-shadow: 0 1px 2px rgba(30,25,18,0.05), 0 8px 24px rgba(30,25,18,0.06)`): Cards, profile cards — the default lift for paper surfaces on ivory.
- **Modal Lift** (`box-shadow: 0 12px 40px rgba(20,15,10,0.22)`): Modals — only overlay that earns a large, soft shadow. On dark: `0 12px 40px rgba(0,0,0,0.6)`.
- **Neon Glow (gated)** (`0 0 32px rgba(255,20,147,0.12)` paired with dark shadows): Only in `data-theme='neon'` modals — never on light/paper.
**Glass/Aurora exception:** Panels become translucent (`rgba(23,29,41,0.58)`, `rgba(255,255,255,0.05)`) with `backdrop-filter: blur(18px) saturate(160%)`; body carries three radial gradients (cyan/purple/aqua washes) to create depth through blur, not shadow.
### Named Rules
**The Flat-By-Default Rule.** Surfaces are flat at rest. Shadows appear only as a response to layer (card on canvas, modal over app). Never add shadow to inline controls or navigation.
## Shapes
Form language is *softly squared with a pill exception.* Corners are gentle, never sharp, never fully rounded except for tags and avatars.
- **Soft square** (`border-radius: var(--radius)` 14px): Cards, modals (16px), empty states — the workhorse.
- **Compact square** (`border-radius: var(--radius-sm)` 9px): Buttons, inputs, alerts, segmented control, nav items.
- **Pill** (`border-radius: 999px`): Badges, `npub-chip`, toggles, compose tabs, segmented buttons.
- **Circle** (`50%`): Avatars (42px / 52px lg), toggles thumb.
- **Empty-state dash** (`border: 1.5px dashed var(--border-strong)`): Deliberately unfinished — signals "bring your own."
- **Glass blur** is shape-adjacent: panels keep the same radii but gain `backdrop-filter` to soften edges without changing geometry.
## Components
### Buttons
- **Shape:** Compact square (9px), `font-weight: 600`, `gap: 8px`.
- **Primary (Antique Brass on Paper):** `background: var(--primary)` / `color: var(--on-primary)` / `padding: 10px 18px` (md) or `7px 12px` (sm). Hover → `var(--primary-hover)`. Loading shows spinner in `rgba(255,255,255,0.4)` track.
- **Secondary:** `background: var(--surface)` / `border: 1px solid var(--border-strong)` / `color: var(--text)` / Hover → `var(--surface-hover)`.
- **Danger:** `background: var(--danger-soft)` / `color: var(--danger)` / Hover → `var(--danger)` on white.
- **Ghost:** `background: transparent` / `color: var(--text-muted)` / Hover → `var(--surface-2)` + `var(--text)`.
- Focus everywhere: `outline: 2px solid var(--focus)` `offset: 2px`.
### Chips / Badges
- **Style:** Pill (999px), `padding: 3px 10px`, `font-size: 12px` / `weight: 650`, neutral uses `var(--surface-2)` + `var(--border)`, semantic variants use `<role>-soft` washes.
### Cards / Containers
- **Corner Style:** Soft square (14px).
- **Background:** `var(--surface)` (or translucent on glass).
- **Shadow Strategy:** Paper Lift by default; see Elevation.
- **Border:** `1px solid var(--border)` (active profile card upgrades to `var(--success)`).
- **Internal Padding:** `18px` (profile card) / `20px` (card-body).
### Inputs / Fields
- **Style:** `background: var(--surface)` / `border: 1px solid var(--border-strong)` / `radius: 9px` / `padding: 10px 12px` / `font: inherit`.
- **Focus:** `border-color: var(--focus)` + `box-shadow: 0 0 0 3px color-mix(in srgb, var(--focus) 22%, transparent)`.
- **Error:** `.field-error` in `var(--danger)` 13px / 500.
- **Hint:** `.hint` in `var(--text-muted)` 13px.
### Navigation (Sidebar)
- **Style:** Sidebar `248px`, `background: var(--surface)`, `border-right: 1px solid var(--border)`. Brand block with `38px` white tile (inverted `filter: invert(1)` on dark/neon/glass).
- **Nav item:** `padding: 11px 12px` / `radius: 9px` / `font-weight: 550` / `color: var(--text-muted)` → hover `var(--surface-2)` + `var(--text)` → active `var(--primary-soft)` + `var(--primary)`.
- **Footer:** Profile row + top border `1px solid var(--border)`.
### Undo Bar (Signature Component)
- **Shape:** Compact square (`8px`), `padding: 10px 14px`, `border: 1px solid var(--border)`, `background: var(--primary-soft)`.
- **Character:** Warm, not alarming — confirms deletion without shouting. Label in `var(--text)` 500, dash in `var(--text-muted)`, action as themed link.
- **Action:** Inline `<button>` styled as link (`background: none` / `color: var(--primary)` / `font-weight: 600` / `underline` `2px` offset). Auto-dismisses after 5s; restores via `undoDelete`. Same token treatment on all themes.
## Do's and Don'ts
### Do:
- **Do** keep Warm Ivory (`#f6f4f0`) as the only page canvas on light; let Paper and Soft Stone carry the hierarchy.
- **Do** use Muted Sage wash only for successful security confirmation (active badge, save success).
- **Do** keep one primary per screen — Antique Brass is rare by design; secondary actions stay ghost or secondary.
- **Do** use `var(--focus)` (same as primary) for every focus ring; keep the 2px + 2px offset invariant.
- **Do** preserve the side-by-side rate: cards on Paper with 1px Line borders before adding any shadow.
- **Do** let glass blur inherit radii; never change corner size just for translucency.
### Don't:
- **Don't** use Signal Coral outside error/destructive states — never for decorative accents or neutral badges.
- **Don't** introduce a new accent hue outside the four semantic washes (primary/success/warning/danger/info); add a new feature with existing tokens.
- **Don't** bump heading weight beyond 650 or add bold to muted text — shift color to Ink Muted instead.
- **Don't** apply neon glow (pink `0 0 32px rgba(255,20,147,0.12)`) outside `data-theme='neon'` modals.
- **Don't** invent a new radius — step on `9px` / `14px` / `16px` / `999px` only.
- **Don't** replace the 5s auto-dismiss undo bar with a permanent modal; the atelier stays calm after a task.