From f74f2f58f6c6701d77aaf29df1eb995638e8c615 Mon Sep 17 00:00:00 2001 From: highperfocused Date: Sat, 5 Sep 2026 21:15:29 +0200 Subject: [PATCH] Redesign visual identity: layer-stack design system + styleguide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reworks the site around a signature "layer stack" motif — the relay is literally one layer between a client and the wider Nostr network, so the brand mark is that stack rather than a logo standing in for it. - New color/type/spacing tokens (src/index.css, tailwind.config.ts): cool "Paper" light surface, deep "Ink" dark surface, relay-amber accent, navy/cyan structural colors; JetBrains Mono as the display face for headlines and data, Inter for body copy (both self-hosted via fontsource to satisfy the existing font-src 'self' CSP). - New brand components: LayerStack (hero signature), LayerDivider, Wordmark, SiteHeader/SiteFooter, shared across the landing page, legal pages, and the new styleguide. - Reworked the landing page hero, feature grid, and connection steps with benefit-first, plain-voice copy. - Added docs/DESIGN_STYLEGUIDE.md and a live /styleguide route documenting the system. - Fixed structural gaps: /messages had no route, and Explore/Messages had no sidebar navigation entry despite being real pages; both now sit in the same app shell as Dashboard. Closes #8 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01FvaqCkYtksTvsEKGzjGcde --- AGENTS.md | 2 + docs/DESIGN_STYLEGUIDE.md | 127 +++++++++ package-lock.json | 30 +- package.json | 1 + src/AppRouter.tsx | 4 + src/components/brand/LayerDivider.tsx | 16 ++ src/components/brand/LayerStack.tsx | 93 +++++++ src/components/brand/SiteFooter.tsx | 36 +++ src/components/brand/SiteHeader.tsx | 30 ++ src/components/brand/Wordmark.tsx | 26 ++ src/components/navigation/AppSidebar.tsx | 21 +- src/index.css | 248 ++++++++++------- src/main.tsx | 4 + src/pages/Dashboard.tsx | 2 +- src/pages/DashboardEvents.tsx | 2 +- src/pages/DashboardExport.tsx | 2 +- src/pages/Explore.tsx | 36 ++- src/pages/Index.tsx | 337 ++++++++++------------- src/pages/Messages.tsx | 27 +- src/pages/NotFound.tsx | 28 +- src/pages/Privacy.tsx | 34 +-- src/pages/Styleguide.tsx | 215 +++++++++++++++ src/pages/Terms.tsx | 33 +-- tailwind.config.ts | 46 +++- 24 files changed, 1003 insertions(+), 397 deletions(-) create mode 100644 docs/DESIGN_STYLEGUIDE.md create mode 100644 src/components/brand/LayerDivider.tsx create mode 100644 src/components/brand/LayerStack.tsx create mode 100644 src/components/brand/SiteFooter.tsx create mode 100644 src/components/brand/SiteHeader.tsx create mode 100644 src/components/brand/Wordmark.tsx create mode 100644 src/pages/Styleguide.tsx diff --git a/AGENTS.md b/AGENTS.md index 4ec9e5c..9e29365 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -116,6 +116,8 @@ The project includes a **`docs/`** directory containing specialized documentatio - **`docs/NOSTR_DIRECT_MESSAGES.md`**: Read when implementing direct messaging features, building chat interfaces, or working with encrypted peer-to-peer communication (NIP-04 and NIP-17). +- **`docs/DESIGN_STYLEGUIDE.md`**: Read before making any visual change — color, type, spacing, or component styling. Covers the design tokens in `src/index.css`, the "layer stack" signature motif, and the rationale behind them. The live version is the `/styleguide` route (`src/pages/Styleguide.tsx`). + ## System Prompt Management The AI assistant's behavior and knowledge is defined by the AGENTS.md file, which serves as the system prompt. To modify the assistant's instructions or add new project-specific guidelines: diff --git a/docs/DESIGN_STYLEGUIDE.md b/docs/DESIGN_STYLEGUIDE.md new file mode 100644 index 0000000..46f964c --- /dev/null +++ b/docs/DESIGN_STYLEGUIDE.md @@ -0,0 +1,127 @@ +# LAYER.systems Design Styleguide + +Read this before making visual changes anywhere in the app. The live version of +everything below is at [`/styleguide`](../src/pages/Styleguide.tsx). + +## The idea + +LAYER.systems is a public Nostr relay: one layer in the path between a +person's client and the rest of the network. The brand doesn't use a logo to +*represent* that — the signature visual **is** the stack (`Client → Relay → +Network`, see `src/components/brand/LayerStack.tsx`), lit up at the relay +layer, because that's literally what this product is. + +Everything else in the system follows from taking that stack seriously: +layered, hairline-bordered surfaces instead of soft drop shadows; a +monospace face used for headlines and data, not tucked away as a caption +font; and a dark "Ink" hero that reads like an oscilloscope panel, not a +marketing gradient. + +## Color + +Named tokens live in `src/index.css` as HSL CSS variables, consumed through +Tailwind (`tailwind.config.ts`) and shadcn/ui components. Don't hardcode hex +values in components — reference the token (`bg-primary`, `text-brand-cyan`, +`hsl(var(--brand-navy))`) so light/dark mode and future palette tweaks stay +centralized. + +| Name | Hex | Token | Usage | +|---|---|---|---| +| Paper | `#F5F6F5` | `--background` (light) | Default light-mode surface — cool, not cream | +| Ink | `#0E1116` | `--brand-ink`, `--background` (dark) | Hero panel + dark-mode base surface | +| Relay Amber | `#F5A623` | `--primary` | The signature signal color: primary actions, the "live" layer, focus rings | +| Deep Signal Navy | `#1C2B45` | `--brand-navy` | Structural color for layer bands and dark-mode accents | +| Circuit Cyan | `#3FC1CE` | `--brand-cyan`, `--chart-2` | Secondary data color — charts, hover accents | +| Slate Ink | `#2B3140` | `--foreground` (light) | Body text on light surfaces | + +Both light and dark modes share the same amber accent so the brand reads +consistently either way — only the surface (paper vs. ink) flips. + +**Why not the obvious defaults:** we deliberately avoided a warm-cream + +serif look (doesn't fit infrastructure) and a pure near-black + single +neon accent (too generic for what should feel like real hardware — a relay +lamp, not a startup gradient). The dark hero pairs a warm accent (amber) +with a cool structural color (navy) and a cool data color (cyan), which is +richer than the single-accent cliché, and only the *hero* is dark — the app +itself is light-first. + +## Type + +Two faces, doing different jobs: + +- **Display / data — JetBrains Mono** (`font-display` / `font-mono`, + `@fontsource-variable/jetbrains-mono`). Used for headlines, the eyebrow + labels (`.eyebrow` utility class), stat numbers, relay URLs, hex ids, and + timestamps. This is a wire-protocol product — a monospace headline is a + deliberate choice that matches the subject, not a caption font promoted + by accident. +- **Body — Inter** (`font-sans`, `@fontsource-variable/inter`). Everything a + person reads at length: paragraphs, descriptions, terms & privacy copy. + +Both are self-hosted via `@fontsource-variable/*` and imported once in +`src/main.tsx` — the CSP (`font-src 'self'`) blocks third-party font CDNs, +so don't add a Google Fonts ``. + +Eyebrow labels use the `.eyebrow` utility (`src/index.css`): mono, uppercase, +`0.14em` tracking, small size. Use them to label a section's role +("Public Nostr relay", "Getting connected"), not as decoration. + +## Spacing & radius + +Standard Tailwind 4px scale (`--spacing: 0.25rem`). `--radius` is `0.5rem` +site-wide — soft enough to feel modern, restrained enough to stay +technical. Don't mix radius values ad hoc; use the `rounded-lg` / +`rounded-md` / `rounded-sm` scale derived from the token. + +## Signature components + +- **`LayerStack`** (`src/components/brand/LayerStack.tsx`) — the hero mark. + Three cascading bands (Client / Relay / Network), the relay band lit + amber and gently pulsing. Respects `prefers-reduced-motion` via Tailwind's + `motion-safe:` variant — the settle-in animation and pulse simply don't + run when reduced motion is requested; the stack still renders in its + final state. +- **`LayerDivider`** — a quiet three-line echo of the stack for breaking up + sections without repeating the hero verbatim. +- **`.layer-card`** utility class — the default card treatment across the + marketing site: two hairline borders offset by 6px instead of a drop + shadow, widening slightly on hover. Use plain `border + shadow-sm` cards + instead inside dense data views (dashboard, explorer) where the offset + border would add noise against a grid of cards. + +## Components + +Built on shadcn/ui (`src/components/ui/*`) — Button, Card, Badge, Input, +Alert, Sidebar, etc. — restyled entirely through the token layer, not by +forking the components. See `/styleguide` for live specimens of every +variant. When adding new UI, prefer an existing shadcn primitive over a +bespoke one. + +## Motion + +- Page-load: the `LayerStack` bands settle in with a short staggered + animation (`animate-layer-settle`); the relay band pulses continuously + but subtly (`animate-signal-pulse`). +- Hover: `.layer-card` widens its offset border; buttons and links use the + existing shadcn transition tokens. No scroll-triggered effects — this is + a technical, read-once page, not a scrollytelling site. +- Everything animated is wrapped in Tailwind's `motion-safe:` variant so + `prefers-reduced-motion: reduce` gets the static end state. + +## Accessibility + +- Color pairs (background/foreground, primary/primary-foreground, etc.) + are chosen for contrast in both themes — check new token pairs with a + contrast checker before shipping. +- Interactive elements rely on shadcn's built-in focus rings (`--ring`, + amber) — don't remove `focus-visible` outlines. +- The `LayerStack` diagram carries a text `aria-label` describing the same + information conveyed visually (client → this relay → the network). + +## Voice + +Plain, direct, written from the person's side of the screen: "add the +relay," not "leverage relay connectivity." Say what things do, not why +they're impressive. Errors and empty states say what happened and what to +do next — see `NotFound.tsx` for the tone ("There's no layer at +`/whatever`") instead of a generic apology. diff --git a/package-lock.json b/package-lock.json index 2b607ec..bcffb7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@fontsource-variable/inter": "^5.2.6", + "@fontsource-variable/jetbrains-mono": "^5.3.0", "@getalby/sdk": "^5.1.1", "@hookform/resolvers": "^3.9.0", "@nostrify/nostrify": "^0.48.2", @@ -955,6 +956,15 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@fontsource-variable/jetbrains-mono": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.3.0.tgz", + "integrity": "sha512-F32xpS2NsGYoQi2ADSkKTgpJj7ozajsGgDJ8woTnqjmIB+dxDIqImjl4pXZVEExu8UFZ2ndhmX18EBS/hdz3Lw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@getalby/lightning-tools": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@getalby/lightning-tools/-/lightning-tools-5.2.0.tgz", @@ -3260,7 +3270,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3274,7 +3283,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3288,7 +3296,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3302,7 +3309,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3316,7 +3322,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3330,7 +3335,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3344,7 +3348,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3358,7 +3361,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3372,7 +3374,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3386,7 +3387,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3400,7 +3400,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3414,7 +3413,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3428,7 +3426,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3442,7 +3439,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3456,7 +3452,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3470,7 +3465,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3484,7 +3478,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3498,7 +3491,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3512,7 +3504,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3526,7 +3517,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ diff --git a/package.json b/package.json index 5dc7498..3264e12 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@fontsource-variable/inter": "^5.2.6", + "@fontsource-variable/jetbrains-mono": "^5.3.0", "@getalby/sdk": "^5.1.1", "@hookform/resolvers": "^3.9.0", "@nostrify/nostrify": "^0.48.2", diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index e3bb12e..1d423c6 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -6,6 +6,8 @@ import { Explore } from "./pages/Explore"; import { Dashboard } from "./pages/Dashboard"; import { DashboardEvents } from "./pages/DashboardEvents"; import { DashboardExport } from "./pages/DashboardExport"; +import Messages from "./pages/Messages"; +import { Styleguide } from "./pages/Styleguide"; import { NIP19Page } from "./pages/NIP19Page"; import { Terms } from "./pages/Terms"; import { Privacy } from "./pages/Privacy"; @@ -21,6 +23,8 @@ export function AppRouter() { } /> } /> } /> + } /> + } /> } /> } /> {/* NIP-19 route for npub1, note1, naddr1, nevent1, nprofile1 */} diff --git a/src/components/brand/LayerDivider.tsx b/src/components/brand/LayerDivider.tsx new file mode 100644 index 0000000..6d4ead2 --- /dev/null +++ b/src/components/brand/LayerDivider.tsx @@ -0,0 +1,16 @@ +import { cn } from '@/lib/utils'; + +/** + * A quiet echo of the layer-stack device used to break up sections — + * three hairlines of shrinking width, same cascade logic as the hero mark, + * without repeating it verbatim. + */ +export function LayerDivider({ className }: { className?: string }) { + return ( +