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.
263 lines
16 KiB
Markdown
263 lines
16 KiB
Markdown
---
|
||
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.
|