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

16 KiB
Raw Permalink Blame History

name description colors typography rounded spacing components
Nostr Keynctr Local-first Nostr identity atelier — keys stay in Rust, the UI stays calm and warm.
warm-ivory soft-stone stone-hover paper ink ink-muted line line-strong antique-brass antique-brass-hover antique-brass-soft on-brass signal-coral signal-coral-soft antique-gold antique-gold-soft muted-sage muted-sage-soft clear-ink clear-ink-soft focus
#f6f4f0 #f1ede7 #faf8f5 #ffffff #201b15 #6d655a #e6e0d6 #d8d0c3 #6d5ae6 #5c49d4 #efecfd #ffffff #c0392b #fdecec #9a6b13 #fcf3e0 #22713c #e7f5ec #2868a4 #e9f2fb #6d5ae6
display headline body label mono
fontFamily fontSize fontWeight lineHeight letterSpacing
system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif 24px 650 1.25 normal
fontFamily fontSize fontWeight lineHeight letterSpacing
system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif 16px 650 1.25 normal
fontFamily fontSize fontWeight lineHeight letterSpacing
system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif 15px 400 1.5 normal
fontFamily fontSize fontWeight lineHeight letterSpacing
system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif 13px 600 1.5 normal
fontFamily fontSize fontWeight lineHeight letterSpacing
'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace 13.8px 400 1.5 normal
sm md lg pill
9px 14px 16px 999px
xs sm md lg xl
8px 12px 16px 20px 32px
button-primary button-primary-hover button-secondary button-ghost button-danger card input
backgroundColor textColor rounded padding
{colors.antique-brass} {colors.on-brass} {rounded.sm} 10px 18px
backgroundColor textColor rounded padding
{colors.antique-brass-hover} {colors.on-brass} {rounded.sm} 10px 18px
backgroundColor textColor rounded padding
{colors.paper} {colors.ink} {rounded.sm} 10px 18px
backgroundColor textColor rounded padding
transparent {colors.ink-muted} {rounded.sm} 7px 12px
backgroundColor textColor rounded padding
{colors.signal-coral-soft} {colors.signal-coral} {rounded.sm} 7px 12px
backgroundColor textColor rounded padding
{colors.paper} {colors.ink} {rounded.md} 18px
backgroundColor textColor rounded padding
{colors.paper} {colors.ink} {rounded.sm} 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.