From a8a8148cd903127c93425649464b7698fed6761c Mon Sep 17 00:00:00 2001 From: holke <103380539+holkexyz@users.noreply.github.com> Date: Sun, 10 May 2026 12:35:46 +0000 Subject: [PATCH 01/15] docs(design): add PRODUCT.md and DESIGN.md design context PRODUCT.md captures the strategic brief: register (product-led mixed), primary user, brand personality (confident, principled, plain), the "visibly not-a-wallet" anti-reference set, and design principles. DESIGN.md captures the visual system in the Stitch six-section format with YAML frontmatter (13 colors, 7 typography roles, components). North Star: "The Notary's Ledger". Civic palette names. Flat-by-default elevation. Nine named rules across colors, typography, and elevation. .impeccable/design.json sidecar carries tonal ramps, the single overlay shadow, motion tokens, breakpoints, and 8 ready-to-render component snippets for the impeccable live panel. AGENTS.md gets a section 0 Design Context pointer at the top so future agent sessions surface PRODUCT.md and DESIGN.md without prompting. The pointer also flags that tailwind.config.ts holds stale tokens from a prior visual system; canonical source is --color-* in globals.css. Co-Authored-By: Claude Opus 4.7 (1M context) --- .impeccable/design.json | 405 ++++++++++++++++++++++++++++++++++++++++ AGENTS.md | 7 + DESIGN.md | 302 ++++++++++++++++++++++++++++++ PRODUCT.md | 69 +++++++ 4 files changed, 783 insertions(+) create mode 100644 .impeccable/design.json create mode 100644 DESIGN.md create mode 100644 PRODUCT.md diff --git a/.impeccable/design.json b/.impeccable/design.json new file mode 100644 index 00000000..6368c064 --- /dev/null +++ b/.impeccable/design.json @@ -0,0 +1,405 @@ +{ + "schemaVersion": 2, + "generatedAt": "2026-05-10T00:00:00.000Z", + "title": "Design System: Certified", + "extensions": { + "colorMeta": { + "ink-black": { + "role": "primary", + "displayName": "Ink Black", + "canonical": "#111111", + "tonalRamp": [ + "#000000", + "#0a0a0a", + "#111111", + "#1f1f1f", + "#2e2e2e", + "#454547", + "#4c4546", + "#7e7576" + ] + }, + "archive-gray": { + "role": "neutral", + "displayName": "Archive Gray", + "canonical": "#4c4546", + "tonalRamp": [ + "#1d1a1b", + "#2c2a2b", + "#3b393a", + "#4c4546", + "#5e5757", + "#7e7576", + "#a39c9d", + "#cfc4c5" + ] + }, + "bureau-iron": { + "role": "neutral", + "displayName": "Bureau Iron", + "canonical": "#7e7576", + "tonalRamp": [ + "#3b3636", + "#534b4c", + "#695e5f", + "#7e7576", + "#988f8f", + "#b3aaa9", + "#cdc6c5", + "#e2dddc" + ] + }, + "ledger-rule": { + "role": "neutral", + "displayName": "Ledger Rule Gray", + "canonical": "#cfc4c5", + "tonalRamp": [ + "#7a7273", + "#928a8a", + "#a8a09f", + "#cfc4c5", + "#dcd2d2", + "#e7dedd", + "#efe9e8", + "#f7f3f3" + ] + }, + "light-divider": { + "role": "neutral", + "displayName": "Light Divider", + "canonical": "#e2e2e2", + "tonalRamp": [ + "#9c9c9c", + "#b4b4b4", + "#cccccc", + "#dadada", + "#e2e2e2", + "#ebebeb", + "#f1f1f1", + "#f7f7f7" + ] + }, + "public-stone": { + "role": "neutral", + "displayName": "Public Stone", + "canonical": "#eeeeee", + "tonalRamp": [ + "#a8a8a8", + "#bcbcbc", + "#d0d0d0", + "#e0e0e0", + "#eeeeee", + "#f3f3f3", + "#f7f7f7", + "#fbfbfb" + ] + }, + "vellum": { + "role": "neutral", + "displayName": "Vellum", + "canonical": "#f3f3f3", + "tonalRamp": [ + "#b3b3b3", + "#c5c5c5", + "#d6d6d6", + "#e4e4e4", + "#ededed", + "#f3f3f3", + "#f7f7f7", + "#fbfbfb" + ] + }, + "notice-paper-white": { + "role": "neutral", + "displayName": "Notice-Paper White", + "canonical": "#f9f9f9", + "tonalRamp": [ + "#bababa", + "#cccccc", + "#dddddd", + "#e9e9e9", + "#f1f1f1", + "#f6f6f6", + "#f9f9f9", + "#fcfcfc" + ] + }, + "pure-white": { + "role": "neutral", + "displayName": "Pure White", + "canonical": "#ffffff", + "tonalRamp": [ + "#c8c8c8", + "#d8d8d8", + "#e6e6e6", + "#efefef", + "#f6f6f6", + "#fafafa", + "#fdfdfd", + "#ffffff" + ] + }, + "annotation-green": { + "role": "tertiary", + "displayName": "Annotation Green", + "canonical": "#94bb51", + "tonalRamp": [ + "#3f5621", + "#577a2f", + "#6f9c3e", + "#84b048", + "#94bb51", + "#abc97a", + "#c5d8a1", + "#e1ebcf" + ] + }, + "success-leaf": { + "role": "semantic", + "displayName": "Success Leaf", + "canonical": "#2ecc71" + }, + "caution-amber": { + "role": "semantic", + "displayName": "Caution Amber", + "canonical": "#f5a623" + }, + "error-vermilion": { + "role": "semantic", + "displayName": "Error Vermilion", + "canonical": "#ba1a1a" + } + }, + "typographyMeta": { + "display": { + "displayName": "Display", + "purpose": "Hero title on /welcome only. Noto Serif at clamp(5rem, 7vw + 1rem, 7rem). One per page." + }, + "display-italic": { + "displayName": "Display Italic Accent", + "purpose": "Instrument Serif italic accent words inside the hero title. The single typographic flourish in the system." + }, + "headline": { + "displayName": "Headline", + "purpose": "Section headlines on landing surface and authenticated app pages. Noto Serif at clamp(2rem, 3vw + 0.5rem, 3rem)." + }, + "body": { + "displayName": "Body", + "purpose": "Default running copy. Inter 400 1rem, line-height 1.6, color archive-gray, max 65-75ch." + }, + "body-sm": { + "displayName": "Body Small", + "purpose": "Meta copy, helper text, captions inside cards." + }, + "label": { + "displayName": "Label", + "purpose": "Eyebrows above section headlines and inside cards. Inter 500 0.6875rem, ls 0.2em, uppercase, color bureau-iron." + }, + "nav-label": { + "displayName": "Nav Label", + "purpose": "Authenticated top-nav links. Inter 500 0.75rem, ls 0.15em, uppercase, color bureau-iron with active state in ink-black." + } + }, + "shadows": [ + { + "name": "overlay-ambient", + "value": "0 8px 32px rgba(0, 0, 0, 0.12)", + "purpose": "The single sanctioned shadow. Reserved for modals, dropdowns, and the floating feedback sheet. Surfaces that scroll with the page are flat." + } + ], + "motion": [ + { + "name": "transition-fast", + "value": "150ms ease-out", + "purpose": "Color, border, and opacity state changes (hover, focus)." + }, + { + "name": "transition-base", + "value": "250ms ease-out", + "purpose": "Default for transform and layout-adjacent transitions that are not on layout properties themselves." + }, + { + "name": "transition-slow", + "value": "400ms cubic-bezier(0.16, 1, 0.3, 1)", + "purpose": "Fade-up reveals on hero load. Exponential ease-out, never bounce." + } + ], + "breakpoints": [ + { + "name": "sm", + "value": "640px" + }, + { + "name": "md", + "value": "768px" + }, + { + "name": "lg", + "value": "1024px" + }, + { + "name": "xl", + "value": "1280px" + }, + { + "name": "2xl", + "value": "1536px" + } + ] + }, + "components": [ + { + "name": "Primary Button", + "kind": "button", + "refersTo": "button-primary", + "description": "Solid ink-black primary action. Hero CTAs use 18px 40px padding; in-app CTAs use 16px 24px. Hover drops opacity to 0.9; active scales to 0.97.", + "html": "", + "css": ".ds-btn-primary { display: inline-flex; align-items: center; gap: 12px; padding: 18px 40px; font-size: 1.125rem; font-weight: 500; font-family: Inter, system-ui, -apple-system, sans-serif; color: #ffffff; background: #111111; border: none; border-radius: 4px; cursor: pointer; transition: opacity 200ms ease-out, transform 200ms ease-out; text-decoration: none; } .ds-btn-primary:hover { opacity: 0.9; } .ds-btn-primary:active { transform: scale(0.97); } .ds-btn-primary:focus-visible { outline: 2px solid #111111; outline-offset: 2px; } .ds-btn-primary__arrow { font-size: 1.25rem; line-height: 1; }" + }, + { + "name": "Secondary Button", + "kind": "button", + "refersTo": "button-secondary", + "description": "Text-link with hairline underline on hover. Never fills. Used as the alternate sign-in path next to a primary action.", + "html": "", + "css": ".ds-btn-secondary { display: inline-flex; align-items: center; gap: 8px; padding: 18px 40px; font-size: 1.125rem; font-weight: 500; font-family: Inter, system-ui, -apple-system, sans-serif; color: #111111; background: transparent; border: none; border-bottom: 2px solid transparent; cursor: pointer; transition: border-color 150ms ease-out; text-decoration: none; } .ds-btn-secondary:hover { border-bottom-color: #111111; } .ds-btn-secondary:focus-visible { outline: 2px solid #111111; outline-offset: 2px; }" + }, + { + "name": "Text Input", + "kind": "input", + "refersTo": "input-default", + "description": "Form field used in sign-in modal and feedback modal. The 1.5px border (slightly thicker than page hairlines) signals a write-here surface; focus turns the border annotation-green with a soft ring.", + "html": "", + "css": ".ds-input-wrap { display: flex; flex-direction: column; gap: 8px; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-input-label { font-size: 0.875rem; font-weight: 500; color: #4c4546; } .ds-input { width: 100%; height: 48px; padding: 0 16px; border: 1.5px solid #e2e2e2; border-radius: 2px; font-size: 1rem; font-family: inherit; color: #111111; background: #ffffff; transition: border-color 150ms ease-out, box-shadow 150ms ease-out; outline: none; } .ds-input::placeholder { color: #7e7576; } .ds-input:focus { border-color: #94bb51; box-shadow: 0 0 0 3px rgba(148, 187, 81, 0.15); } .ds-input:disabled { opacity: 0.6; cursor: not-allowed; }" + }, + { + "name": "App Card", + "kind": "card", + "refersTo": "card-default", + "description": "The default container in authenticated app pages. Pure white lifted onto the notice-paper page, hairline border, no shadow. Hover only deepens the border tone slightly. Internal padding 24px.", + "html": "
Account

Two-factor authentication

Add an extra layer of protection to your sign-in.

", + "css": ".ds-card { background: #ffffff; border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 2px; padding: 24px; transition: border-color 150ms ease-out; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-card:hover { border-color: rgba(0, 0, 0, 0.12); } .ds-card__label { font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.08em; color: #7e7576; margin-bottom: 8px; text-transform: uppercase; } .ds-card__title { font-family: 'Noto Serif', Georgia, serif; font-size: 1.375rem; font-weight: 700; line-height: 1.3; color: #111111; margin: 0 0 8px 0; } .ds-card__body { font-size: 0.875rem; line-height: 1.5; color: #4c4546; margin: 0; }" + }, + { + "name": "Status Chip", + "kind": "chip", + "refersTo": "chip-default", + "description": "Small pill used for status (2FA on/off, wallet verified/unverified, group sync changed/removed). Default fill is notice-paper-white; tinted variants use 0.1-opacity semantic color with the matching semantic text color. No shadow, no border.", + "html": "Enabled", + "css": ".ds-chip { display: inline-flex; align-items: center; padding: 4px 12px; border-radius: 2px; font-size: 0.75rem; font-weight: 500; font-family: Inter, system-ui, -apple-system, sans-serif; background: #f9f9f9; color: #7e7576; } .ds-chip--on { background: rgba(46, 204, 113, 0.1); color: #047857; } .ds-chip--off { background: #f9f9f9; color: #7e7576; } .ds-chip--warn { background: #fef9e7; color: #7a6420; } .ds-chip--err { background: rgba(186, 26, 26, 0.08); color: #ba1a1a; }" + }, + { + "name": "App Nav Link", + "kind": "nav", + "refersTo": "nav-link", + "description": "Authenticated top-nav link. Inter 500 0.75rem, letter-spacing 0.15em, uppercase, bureau-iron at rest. Hover and active shift to ink-black; active also adds a 1.5px ink-black bottom border.", + "html": "", + "css": ".ds-nav { display: flex; align-items: center; gap: 32px; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-nav__link { font-size: 0.75rem; font-weight: 500; letter-spacing: 0.15em; text-transform: uppercase; color: #7e7576; text-decoration: none; transition: color 150ms ease-out; padding: 4px 0; border-bottom: 1.5px solid transparent; } .ds-nav__link:hover { color: #111111; } .ds-nav__link--active { color: #111111; border-bottom-color: #111111; }" + }, + { + "name": "Modal Overlay", + "kind": "card", + "refersTo": "card-default", + "description": "Sign-in modal and feedback modal share this shell. The single sanctioned shadow appears here because the surface is genuinely detached from the page. Backdrop dims to 0.7 alpha black.", + "html": "

Sign in

Enter the handle for any AT Protocol account.

", + "css": ".ds-modal-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.7); display: flex; align-items: center; justify-content: center; padding: 16px; } .ds-modal { width: 100%; max-width: 440px; background: #ffffff; border-radius: 4px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); padding: 32px; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-modal__header { margin-bottom: 16px; } .ds-modal__title { font-family: 'Noto Serif', Georgia, serif; font-size: 1.5rem; font-weight: 700; line-height: 1.2; letter-spacing: -0.01em; color: #111111; margin: 0; } .ds-modal__body { font-size: 0.9375rem; line-height: 1.6; color: #4c4546; }" + }, + { + "name": "Hero Title", + "kind": "custom", + "description": "The signature typographic moment on /welcome. Noto Serif heavy display with one Instrument Serif italic accent phrase set inline. The italic is the brand's only visual flourish.", + "html": "

Your identity, everywhere.

", + "css": ".ds-hero-title { font-family: 'Noto Serif', Georgia, serif; color: #111111; font-size: clamp(5rem, 7vw + 1rem, 7rem); font-weight: 700; line-height: 0.9; letter-spacing: -0.02em; margin: 0 0 32px 0; } .ds-hero-title__accent { font-family: 'Instrument Serif', Georgia, serif; font-style: italic; font-weight: 400; }" + } + ], + "narrative": { + "northStar": "The Notary's Ledger", + "overview": "Certified feels like a notary's ledger reimagined as a mobile app: austere, monochrome, and quietly authoritative. The near-absence of color forces attention onto the content: serif headlines anchor each card like a document title, while the surrounding chrome recedes into warm grays. The interface is deliberately under-decorated. No gradients, no brand accent hue, no playful illustrations. The restraint is the brand. The system is two-toned at the surface level: an off-white paper and a near-black ink, separated by a small library of warm-tinted neutrals that carry meta-text, dividers, and structural surfaces. Headlines are set in Noto Serif at heavy weight; signature accents lean on Instrument Serif italic, the closest thing the system has to a flourish. Body and UI labels are Inter, in restrained weights. Edges are nearly square (2px), borders are 1px and hairline, and shadows are absent except on overlay elements that genuinely need to detach from the page. This system explicitly rejects the crypto-wallet aesthetic, the SaaS-cream auth-as-a-service lane, the Bluesky / consumer-social look, and the generic foundation / NGO look. Where competitors perform, Certified records.", + "keyCharacteristics": [ + "Two-tone monochrome: warm-tinted neutrals, ink-black ink, no brand accent hue.", + "Serif-led typographic identity: Noto Serif for authority, Instrument Serif italic for the single accent voice, Inter for everything else.", + "Near-square edges (2px default; 4px reserved for primary action surfaces).", + "Flat by default. One ambient shadow vocabulary, reserved for overlays.", + "Borders are hairline (1px) and tonal: they organize, they don't decorate.", + "Restraint is the affordance. If a flourish exists only to impress, remove it." + ], + "rules": [ + { + "name": "The No-Brand-Hue Rule", + "body": "Certified has no brand color. Do not introduce one. If a screen needs visual interest, it needs better typography or better information architecture, not a hue.", + "section": "colors" + }, + { + "name": "The Warm-Neutral Rule", + "body": "Every gray is warm. Cool grays (chroma toward blue) are forbidden: they read as web2 SaaS. Pulling a neutral from outside this list requires changing the list, not the screen.", + "section": "colors" + }, + { + "name": "The One-Voice-of-Color Rule", + "body": "Annotation Green is the only non-semantic color in the interface. It appears on focus rings and confirmation icons, nowhere else. If you find yourself wanting a second non-semantic color, the answer is restraint, not addition.", + "section": "colors" + }, + { + "name": "The Serif-Authority Rule", + "body": "Headlines are always serif. Sans-serif headlines do not exist in this system. If a sans-serif headline feels needed, it is a label or a title in disguise: set it accordingly.", + "section": "typography" + }, + { + "name": "The One-Italic Rule", + "body": "Italic appears in exactly one place: as Instrument Serif accent words inside a serif headline. There is no italic body, no italic UI label, no italic emphasis. The italic is the brand's signature; spreading it weakens it.", + "section": "typography" + }, + { + "name": "The 65-75ch Rule", + "body": "Body copy is capped at 65 to 75 characters per line. Edge-to-edge prose is forbidden; long lines are uncomfortable on an instrument that already asks for trust.", + "section": "typography" + }, + { + "name": "The Flat-By-Default Rule", + "body": "Pages, sections, cards, navigation, and buttons are flat. Shadows do not signal hover, focus, or interactivity on these surfaces.", + "section": "elevation" + }, + { + "name": "The Overlay-Only-Shadow Rule", + "body": "A box-shadow value is allowed only on elements that are physically detached from the page (modals, dropdowns, floating sheets). If the element scrolls with the page, it is flat.", + "section": "elevation" + }, + { + "name": "The Hairline Rule", + "body": "Borders are 1px and use the existing border ramp. 2px or thicker accent borders, side stripes, and colored gutters are forbidden: they are the absolute ban from the impeccable design laws.", + "section": "elevation" + } + ], + "dos": [ + "Do organize hierarchy with serif headlines and tonal warm neutrals. Restraint is the affordance.", + "Do use ink-black (#111111) for primary text and primary actions. Never #000.", + "Do use notice-paper-white (#f9f9f9) for page surfaces and pure-white only for lifted cards and modals.", + "Do keep neutrals warm. If you need a new gray, it must sit on the warm side of neutral.", + "Do use Instrument Serif italic for one accent phrase per hero, and nowhere else.", + "Do cap body copy at 65 to 75 characters per line.", + "Do use the 1px hairline border ramp (--border-subtle through --border-strong) for every divider and card edge.", + "Do confine shadows to overlay surfaces (modals, dropdowns, feedback sheet).", + "Do treat annotation-green (#94bb51) as a focus-and-confirmation accent only.", + "Do explain DIDs, handles, and PDSes inline the first time they appear on a screen, in plain Inter body type.", + "Do respect prefers-reduced-motion; motion is decoration, never load-bearing." + ], + "donts": [ + "Don't introduce a brand accent hue. Certified has no brand color, by design.", + "Don't use neon accents on black, gradient meshes, glassmorphism, or Web3 depth tricks. The crypto-wallet aesthetic is the strongest anti-reference.", + "Don't use cream backgrounds with warm-orange accents, illustrated heroes, or developer-first framing. The SaaS-cream auth-as-a-service lane (Auth0, Clerk, WorkOS) is forbidden.", + "Don't use Bluesky-cousin treatments: rounded cards, friendly blue accents, app-store-y heroes. Certified is identity infrastructure, not a social product.", + "Don't use stock photography, navy-and-gold credibility palettes, or vague empowering-communities copy. The generic-foundation / NGO lane is forbidden.", + "Don't frame identity as ownership, your keys your X, or any other crypto-self-custody phrasing. Certified is about portability, not custody.", + "Don't use #000 or #fff as variable values. Both are reserved as render-only edge cases.", + "Don't introduce cool grays. Cool gray is web2 SaaS by reflex.", + "Don't use border-left or border-right greater than 1px as a colored accent. The side-stripe pattern is in the absolute-bans list; rewrite the element instead.", + "Don't use background-clip text with a gradient. Gradient text is forbidden; use weight or size for emphasis.", + "Don't stack a card inside a card. Use tonal layering or hairline borders for hierarchy.", + "Don't add a shadow to surfaces that scroll with the page. Shadows belong on overlays only.", + "Don't reach for the stale tokens in tailwind.config.ts (navy, accent, the elevation-1 through elevation-4 shadows). They are leftover from a prior visual system; the source of truth is --color-* in globals.css.", + "Don't use em dashes in copy. Use commas, colons, semicolons, periods, or parentheses.", + "Don't use exclamation marks or emojis in product copy.", + "Don't rely on color alone to signal status. Pair color with icon or label every time." + ] + } +} diff --git a/AGENTS.md b/AGENTS.md index 558ebc43..c1942d0d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,7 @@ This document is the canonical reference for coding agents working in this repos ## Table of Contents +0. [Design Context](#0-design-context) 1. [Project Overview](#1-project-overview) 2. [Tech Stack](#2-tech-stack) 3. [Quick Reference](#3-quick-reference) @@ -32,6 +33,12 @@ This document is the canonical reference for coding agents working in this repos --- +## 0. Design Context + +`PRODUCT.md` at the repo root is the strategic design brief: register, primary user, brand personality (confident, principled, plain), anti-references (anchored on "visibly not-a-wallet"), and design principles. Read it before any UI/UX work. The `/impeccable` skill loads it automatically; humans should open it for any design decision that goes beyond a one-line copy or token tweak. + +`DESIGN.md` is the visual companion: the "Notary's Ledger" North Star, the civic palette (Ink Black, Notice-Paper White, Bureau Iron, Annotation Green), Noto Serif and Instrument Serif italic typography, flat-by-default elevation, and named rules (No-Brand-Hue, Warm-Neutral, One-Italic, Flat-By-Default, Hairline). Machine-readable tokens live in the YAML frontmatter; the sidecar at `.impeccable/design.json` carries shadow, motion, breakpoints, tonal ramps, and ready-to-render component snippets. Note that `tailwind.config.ts` still holds stale tokens from a prior visual system (`navy: #0F2544`, `accent: #60A1E2`, the `elevation-1`..`elevation-4` shadows) and is scheduled for cleanup; the canonical source is `--color-*` in `src/app/globals.css`. + ## 1. Project Overview Certified is a passwordless identity platform built on **AT Protocol** (atproto), operated by the **Hypercerts Foundation**. It lets a user create one identity that travels across partner applications with full data portability and no vendor lock-in. diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 00000000..12c1a7b4 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,302 @@ +--- +name: Certified +description: A passwordless AT Protocol identity, designed like a notary's ledger. +colors: + ink-black: "#111111" + archive-gray: "#4c4546" + bureau-iron: "#7e7576" + ledger-rule: "#cfc4c5" + light-divider: "#e2e2e2" + public-stone: "#eeeeee" + vellum: "#f3f3f3" + notice-paper-white: "#f9f9f9" + pure-white: "#ffffff" + annotation-green: "#94bb51" + success-leaf: "#2ecc71" + caution-amber: "#f5a623" + error-vermilion: "#ba1a1a" +typography: + display: + fontFamily: "Noto Serif, Georgia, serif" + fontSize: "clamp(5rem, 7vw + 1rem, 7rem)" + fontWeight: 700 + lineHeight: 0.9 + letterSpacing: "-0.02em" + display-italic: + fontFamily: "Instrument Serif, Georgia, serif" + fontSize: "clamp(5rem, 7vw + 1rem, 7rem)" + fontWeight: 400 + lineHeight: 0.9 + letterSpacing: "-0.02em" + headline: + fontFamily: "Noto Serif, Georgia, serif" + fontSize: "clamp(2rem, 3vw + 0.5rem, 3rem)" + fontWeight: 700 + lineHeight: 1.1 + letterSpacing: "-0.02em" + body: + fontFamily: "Inter, system-ui, -apple-system, sans-serif" + fontSize: "1rem" + fontWeight: 400 + lineHeight: 1.6 + body-sm: + fontFamily: "Inter, system-ui, -apple-system, sans-serif" + fontSize: "0.875rem" + fontWeight: 400 + lineHeight: 1.5 + label: + fontFamily: "Inter, system-ui, -apple-system, sans-serif" + fontSize: "0.6875rem" + fontWeight: 500 + lineHeight: 1.4 + letterSpacing: "0.2em" + nav-label: + fontFamily: "Inter, system-ui, -apple-system, sans-serif" + fontSize: "0.75rem" + fontWeight: 500 + lineHeight: 1.4 + letterSpacing: "0.15em" +rounded: + xs: "2px" + sm: "4px" +spacing: + xs: "8px" + sm: "12px" + md: "16px" + lg: "24px" + xl: "32px" + 2xl: "48px" + 3xl: "96px" +components: + button-primary: + backgroundColor: "{colors.ink-black}" + textColor: "{colors.pure-white}" + typography: "{typography.body}" + rounded: "{rounded.sm}" + padding: "18px 40px" + height: "auto" + button-primary-hover: + backgroundColor: "{colors.ink-black}" + textColor: "{colors.pure-white}" + button-secondary: + backgroundColor: "transparent" + textColor: "{colors.ink-black}" + typography: "{typography.body}" + rounded: "{rounded.xs}" + padding: "18px 40px" + input-default: + backgroundColor: "{colors.pure-white}" + textColor: "{colors.ink-black}" + typography: "{typography.body}" + rounded: "{rounded.xs}" + padding: "0 16px" + height: "48px" + input-focus: + backgroundColor: "{colors.pure-white}" + textColor: "{colors.ink-black}" + card-default: + backgroundColor: "{colors.pure-white}" + textColor: "{colors.archive-gray}" + rounded: "{rounded.xs}" + padding: "24px" + card-hover: + backgroundColor: "{colors.pure-white}" + chip-default: + backgroundColor: "{colors.notice-paper-white}" + textColor: "{colors.bureau-iron}" + typography: "{typography.label}" + rounded: "{rounded.xs}" + padding: "4px 12px" + nav-link: + backgroundColor: "transparent" + textColor: "{colors.bureau-iron}" + typography: "{typography.nav-label}" + padding: "4px 0" +--- + +# Design System: Certified + +## 1. Overview + +**Creative North Star: "The Notary's Ledger"** + +Certified feels like a notary's ledger reimagined as a mobile app; austere, monochrome, and quietly authoritative. The near-absence of color forces attention onto the content: serif headlines anchor each card like a document title, while the surrounding chrome recedes into warm grays. The interface is deliberately under-decorated; no gradients, no brand accent hue, no playful illustrations. The restraint is the brand. + +The system is two-toned at the surface level: an off-white "paper" and a near-black "ink", separated by a small library of warm-tinted neutrals that carry meta-text, dividers, and structural surfaces. Headlines are set in Noto Serif at heavy weight; signature accents lean on Instrument Serif italic, the closest thing the system has to a flourish. Body and UI labels are Inter, in restrained weights. Edges are nearly square (2px), borders are 1px and hairline, and shadows are absent except on overlay elements that genuinely need to detach from the page. + +This system explicitly rejects the **crypto-wallet aesthetic** named in PRODUCT.md: neon accents, gradient meshes, glassmorphism, animated particle fields. It also rejects the **SaaS-cream auth-as-a-service** lane (cream and warm-orange, illustrated heroes), the **Bluesky / consumer-social** look (rounded cards, friendly blue, app-store hero), and the **generic foundation / NGO** look (navy-and-gold credibility palette, stock photography). Where competitors perform, Certified records. + +**Key Characteristics:** + +- Two-tone monochrome: warm-tinted neutrals, ink-black ink, no brand accent hue. +- Serif-led typographic identity: Noto Serif for authority, Instrument Serif italic for the single accent voice, Inter for everything else. +- Near-square edges (2px default; 4px reserved for primary action surfaces). +- Flat by default. One ambient shadow vocabulary, reserved for overlays. +- Borders are hairline (1px) and tonal; they organize, they don't decorate. +- Restraint is the affordance. If a flourish exists only to impress, remove it. + +## 2. Colors: The Civic Palette + +A monochrome system named in civic vocabulary. There is no primary brand hue; the "primary" color is ink. Color appears only in three places: focus state, semantic status, and partner-app logos at hover. Everywhere else, the system is grayscale with warm-tinted neutrals. + +### Primary + +- **Ink Black** (`#111111`): primary text, primary action surface, focus outline. The "ink" of the ledger. It is never `#000`; the warm tint matters. +- **Notice-Paper White** (`#f9f9f9`): the page surface. The "paper" of the ledger. It is never `#fff` for surfaces; pure white is reserved for cards lifted above the page. + +### Neutral + +- **Pure White** (`#ffffff`): card and modal interiors only. Pure white reads as a fresh page set on top of the older paper underneath. +- **Vellum** (`#f3f3f3`): the lowest container tier; subtle inset surfaces inside cards. +- **Public Stone** (`#eeeeee`): the standard container surface, grid backgrounds, the small pill behind the BETA label. +- **Ledger Cream** (`#e8e8e8`): the highest container tier; used as the dividing rail in the partner-apps grid (1px gap that reads as a stone wall). +- **Light Divider** (`#e2e2e2`): hairline borders on inputs, sectioning rules. +- **Ledger Rule Gray** (`#cfc4c5`): the empty / placeholder state in the partner-apps grid; partner names sit in this gray until the cell is hovered. +- **Bureau Iron** (`#7e7576`): meta text, secondary copy, navigation labels. The "official annotator" voice. +- **Archive Gray** (`#4c4546`): default body text. Sits one step softer than ink, so headlines stay dominant. + +All neutrals carry a slight warm undertone. They are never tuned cool; cool grays read as software, warm grays read as paper. + +### Tertiary (single deliberate accent) + +- **Annotation Green** (`#94bb51`): focus rings on form inputs and a small set of confirmation icons. It is a margin-note green; the color of someone's pen approving a line in a ledger. **It is the only chromatic color in the everyday interface.** + +### Semantic + +- **Success Leaf** (`#2ecc71`): success indicators only; never a fill on a page-level surface. Paired with text in `#047857` for AA contrast on light tints. +- **Caution Amber** (`#f5a623`): warning state, paired with `#7a6420` text on a `#fef9e7` tint. Reserved; never decorative. +- **Error Vermilion** (`#ba1a1a`): error state, validation copy, destructive-action confirmation only. + +### Named Rules + +**The No-Brand-Hue Rule.** Certified has no brand color. Do not introduce one. If a screen needs visual interest, it needs better typography or better information architecture, not a hue. + +**The Warm-Neutral Rule.** Every gray is warm. Cool grays (chroma toward blue) are forbidden; they read as web2 SaaS. Pulling a neutral from outside this list requires changing the list, not the screen. + +**The One-Voice-of-Color Rule.** Annotation Green is the only non-semantic color in the interface. It appears on focus rings and confirmation icons, nowhere else. If you find yourself wanting a "second" non-semantic color, the answer is restraint, not addition. + +## 3. Typography + +**Display Font:** Noto Serif (with Georgia, serif fallback) +**Display Accent:** Instrument Serif italic (with Georgia italic fallback) +**Body / UI Font:** Inter (with system-ui, -apple-system fallback) + +**Character:** Noto Serif anchors the system with foundation-letter authority; it carries the weight of a notarized document. Instrument Serif italic is the single flourish in the system, used for short accent phrases inside hero titles ("yours, everywhere") and editorial callouts. Inter handles every other surface in three weights: 400 for body, 500 for navigation and labels, 600/700 for emphasized UI text. + +### Hierarchy + +- **Display** (Noto Serif, 700, `clamp(5rem, 7vw + 1rem, 7rem)`, line-height 0.9, letter-spacing -0.02em): hero title on `/welcome`. One per page, never repeated. +- **Display Italic** (Instrument Serif, 400 italic): accent words inside the display title. Used inline as ``. Carries the only typographic flourish in the system. +- **Headline** (Noto Serif, 700, `clamp(2rem, 3vw + 0.5rem, 3rem)`, line-height 1.1, letter-spacing -0.02em): section headlines on the landing surface. Anchors each section like a document title. +- **Body** (Inter, 400, 1rem / 16px, line-height 1.6, max 65–75ch): all default running copy. Color is `archive-gray`, not ink-black, so headlines stay dominant. +- **Body Small** (Inter, 400, 0.875rem / 14px, line-height 1.5): meta copy, helper text, captions inside cards. +- **Label** (Inter, 500, 0.6875rem / 11px, letter-spacing 0.2em, uppercase): the eyebrow above section headlines and inside cards. The "category" voice in `bureau-iron`. +- **Nav Label** (Inter, 500, 0.75rem / 12px, letter-spacing 0.15em, uppercase): authenticated top-nav links. Active state adds a 1.5px bottom border in ink-black. + +### Named Rules + +**The Serif-Authority Rule.** Headlines are always serif. Sans-serif headlines do not exist in this system. If a sans-serif "headline" feels needed, it is a label or a title in disguise; set it accordingly. + +**The One-Italic Rule.** Italic appears in exactly one place: as Instrument Serif accent words inside a serif headline. There is no italic body, no italic UI label, no italic emphasis. The italic is the brand's signature; spreading it weakens it. + +**The 65–75ch Rule.** Body copy is capped at 65–75 characters per line. Edge-to-edge prose is forbidden; long lines are uncomfortable on an instrument that already asks for trust. + +## 4. Elevation + +The system is **flat by default**. Surfaces are organized by warm-tinted tonal layering (`notice-paper-white` → `vellum` → `public-stone` → `ledger-cream`) and 1px hairline borders. Cards do not float at rest. Buttons do not pop. Navigation does not drop a shadow. + +There is **one** sanctioned shadow in the system, and it lives on overlays only (modals, dropdowns, and the floating feedback surface), where the page genuinely needs a layer above it. Hover states change opacity, scale (very slightly: `scale(0.97)` on active), or border color, never elevation. + +### Shadow Vocabulary + +- **Overlay Ambient** (`box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12)`): the only sanctioned shadow. Reserved for modal containers, dropdown panels, and the feedback bottom sheet. Diffuse, low-opacity, no inner shadow. + +### Named Rules + +**The Flat-By-Default Rule.** Pages, sections, cards, navigation, and buttons are flat. Shadows do not signal hover, focus, or interactivity on these surfaces. + +**The Overlay-Only-Shadow Rule.** A `box-shadow` value is allowed only on elements that are physically detached from the page (modals, dropdowns, floating sheets). If the element scrolls with the page, it is flat. + +**The Hairline Rule.** Borders are 1px and use the existing `--border-*` ramp (`subtle`, `light`, `default`, `medium`, `hover`, `strong`). 2px+ accent borders, side stripes, and colored gutters are forbidden; they are the absolute ban from the impeccable design laws. + +## 5. Components + +### Buttons + +- **Shape:** Near-square. Primary actions use 4px (`rounded.sm`); destructive and ghost actions use 2px (`rounded.xs`). Pill / fully-rounded buttons are forbidden. +- **Primary** (solid): `ink-black` background, `pure-white` text, Inter 500, `18px 40px` padding on hero CTAs (`16px 24px` on app-page CTAs). No shadow at rest. Hover: opacity drops to 0.9. Active: `transform: scale(0.97)`. Focus: `outline: 2px solid ink-black; outline-offset: 2px`. +- **Secondary** (text-link): transparent background, `ink-black` text, transparent 2px bottom border. Hover: bottom border becomes `ink-black`. No fill, ever. +- **Ghost / icon** (rare): transparent background, `archive-gray` icon, `2px` radius. Hover: background shifts to `notice-paper-white`. +- **Destructive:** `error-vermilion` text on transparent at rest; only fills when confirming an irreversible action inside a modal. + +### Inputs / Fields + +- **Style:** `pure-white` background, **1.5px** border in `light-divider`, 2px radius, 48px height, Inter 1rem text in `ink-black`. Placeholder in `bureau-iron`. The 1.5px border is deliberate; slightly thicker than the page hairlines so the field reads as a write-here surface. +- **Focus:** border color shifts to `annotation-green`; a 3px `rgba(148, 187, 81, 0.15)` ring appears around it. This is the only place the green appears at this prominence. +- **Error:** border shifts to `error-vermilion`; helper text in `error-vermilion` Inter 0.8125rem appears immediately below. +- **Disabled:** opacity 0.6, `cursor: not-allowed`. No tonal change to the surface. + +### Cards / Containers + +- **Corner Style:** 2px radius (`rounded.xs`). Cards never round above 4px. +- **Background:** `pure-white` lifted onto the `notice-paper-white` page. Inset / lower-tier surfaces use `vellum` or `public-stone`. +- **Shadow Strategy:** none at rest; see Elevation section. +- **Border:** 1px `border-default` (`rgba(0, 0, 0, 0.08)`). Hover: `border-hover-soft` (`rgba(0, 0, 0, 0.12)`). The border is the affordance. +- **Internal Padding:** 24px default; 32px for spacious settings cards; 16px for dense list rows. +- **No nested cards.** A card inside a card is forbidden. Use tonal layering or borders instead. + +### Chips / Badges + +- **Style:** 4px 12px padding, 2px radius, Inter 500 0.75rem in `bureau-iron`. Default fill is `notice-paper-white`. Status variants tint the fill at 0.1 opacity (e.g. `rgba(46, 204, 113, 0.1)` for success badges) and use the matching semantic text color. +- **No drop shadow, no border, no gradient.** + +### Navigation + +- **Top nav:** 64px tall, fixed. Default state is `rgba(255, 255, 255, 0.8)` with a 12px backdrop-blur and a 1px hairline below. The transparent variant on `/welcome` reverts to opaque on scroll. +- **Authenticated top-nav links:** Inter 500 0.75rem, letter-spacing 0.15em, uppercase, `bureau-iron` text. Hover: text shifts to `ink-black`. Active: text shifts to `ink-black` and a 1.5px bottom border in `ink-black` appears. +- **Mobile:** hamburger trigger only; no off-canvas; the dropdown opens from below the navbar with a 1px hairline and a 0.97 alpha background. + +### Hero on `/welcome` (signature surface) + +The hero is the one place the design speaks at full volume. Display Noto Serif at clamp(5rem, 7vw + 1rem, 7rem) carries the headline; an Instrument Serif italic span carries one or two accent words inline. Subtitle is Inter at clamp(1.125rem, 1.5vw + 0.5rem, 1.5rem) in `archive-gray`, capped at 640px. Actions are stacked vertically on small screens, horizontal above 768px. A subtle staggered fade-up animation on initial load (`fadeUp` keyframes, 500ms cubic-bezier(0.16, 1, 0.3, 1), 0/50/150ms delays); disabled under `prefers-reduced-motion`. + +### Partner Network Grid (signature component) + +A 4-column grid with 1px gaps in `ledger-cream`, each cell padded 40px 24px on a `notice-paper-white` background. Logo is grayscale-100% at rest, opacity 0.7. Partner name is Noto Serif 1.125rem in `ledger-rule` gray. On hover: logo loses grayscale, name shifts to `ink-black`, an Inter 0.75rem description fades in. The grid itself is the metaphor; the ledger's columns made literal. + +## 6. Do's and Don'ts + +These guardrails enforce the strategic line in PRODUCT.md. The anti-references named there appear here verbatim. + +### Do: + +- **Do** organize hierarchy with serif headlines and tonal warm neutrals. Restraint is the affordance. +- **Do** use `ink-black` (`#111111`) for primary text and primary actions. Never `#000`. +- **Do** use `notice-paper-white` (`#f9f9f9`) for page surfaces and `pure-white` only for lifted cards and modals. +- **Do** keep neutrals warm. If you need a new gray, it must sit on the warm side of neutral. +- **Do** use Instrument Serif italic for one accent phrase per hero, and nowhere else. +- **Do** cap body copy at 65–75 characters per line. +- **Do** use the 1px hairline border ramp (`--border-subtle` → `--border-strong`) for every divider and card edge. +- **Do** confine shadows to overlay surfaces (modals, dropdowns, feedback sheet). +- **Do** treat `annotation-green` (`#94bb51`) as a focus-and-confirmation accent only. +- **Do** explain DIDs, handles, and PDSes inline the first time they appear on a screen, in plain Inter body type. +- **Do** respect `prefers-reduced-motion`; motion is decoration, never load-bearing. + +### Don't: + +- **Don't** introduce a brand accent hue. Certified has no brand color, by design. +- **Don't** use neon accents on black, gradient meshes, glassmorphism, or "Web3" depth tricks. The crypto-wallet aesthetic is the strongest anti-reference. +- **Don't** use cream backgrounds with warm-orange accents, illustrated heroes, or "developer-first" framing. The SaaS-cream auth-as-a-service lane (Auth0, Clerk, WorkOS) is forbidden. +- **Don't** use Bluesky-cousin treatments: rounded cards, friendly blue accents, app-store-y heroes. Certified is identity infrastructure, not a social product. +- **Don't** use stock photography, navy-and-gold credibility palettes, or vague "empowering communities" copy. The generic-foundation / NGO lane is forbidden. +- **Don't** frame identity as "ownership", "your keys your X", or any other crypto-self-custody phrasing. Certified is about portability, not custody. +- **Don't** use `#000` or `#fff` as variable values. Both are reserved as render-only edge cases (the `::selection` background is `var(--color-primary)` for a reason). +- **Don't** introduce cool grays. Cool gray is web2 SaaS by reflex. +- **Don't** use `border-left` or `border-right` greater than 1px as a colored accent. The side-stripe pattern is in the absolute-bans list; rewrite the element instead. +- **Don't** use `background-clip: text` with a gradient. Gradient text is forbidden; use weight or size for emphasis. +- **Don't** stack a card inside a card. Use tonal layering or hairline borders for hierarchy. +- **Don't** add a shadow to surfaces that scroll with the page. Shadows belong on overlays only. +- **Don't** reach for the stale tokens in `tailwind.config.ts` (`navy: #0F2544`, `accent: #60A1E2`, the `elevation-1`..`elevation-4` shadows). They are leftover from a prior visual system; the source of truth is `--color-*` in `globals.css`. +- **Don't** use em dashes (`—`) in copy or `--`. Use commas, colons, semicolons, periods, or parentheses. +- **Don't** use exclamation marks or emojis in product copy. +- **Don't** rely on color alone to signal status. Pair color with icon or label every time. diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 00000000..34ab26bf --- /dev/null +++ b/PRODUCT.md @@ -0,0 +1,69 @@ +# Product + +## Register + +product + +> Certified is a mixed surface, product-led. The gated app (`/`, `/settings`, `/groups`, `/profile/[did]`, `/connected-apps`) is the primary register and PRODUCT.md is written for it. The brand register applies on `/welcome` and `/about`, where design is allowed to lead — long-form content, hero typography, identity-forward sections. Anywhere a task is genuinely ambiguous, default to product. + +## Users + +The primary user is an **end user signing in to a partner application via Certified**. They arrive on `/welcome` from a partner app's sign-in screen, often without prior knowledge of AT Protocol, and need to create an identity, complete sign-in, and return to the partner app with minimal friction. After that first session they may rarely revisit, except to manage settings, connected apps, or groups. + +A secondary user is the **atproto-fluent power user or group admin** — someone managing organization membership, linking a wallet, or curating profile data. They tolerate (and reward) more depth, but the design must not optimize for them at the expense of the primary user. + +The shared context: people are using Certified at a moment of trust transfer — they are about to hand an identity to a partner app, or they are managing where their identity already travels. The interface is the visible surface of that trust contract, so it cannot feel improvised, branded-over, or decorative-first. + +## Product Purpose + +Certified is a passwordless identity platform built on AT Protocol, operated by the Hypercerts Foundation. It exists so that one identity can travel across partner applications with full data portability and no vendor lock-in. The user's records live on a Personal Data Server (`certified.one` by default, or any external atproto host) and the app itself is a thin OAuth client and BFF — it never holds tokens in the browser, never owns the user's data, and proxies everything through the user's PDS. + +Success looks like: a user lands on `/welcome` from a partner app, creates an identity in under a minute, and returns to the partner app feeling that the foundation behind this is serious and durable. Later, when they revisit `/settings` or `/groups`, the app rewards them with quiet competence rather than novelty. + +## Brand Personality + +**Confident, principled, plain.** Three words, no synonyms. + +- **Confident** — speaks with conviction about user sovereignty and data portability. Does not hedge, apologize, or oversell. +- **Principled** — every visual and copy choice is downstream of a stance: passwordless, portable, foundation-run, no lock-in. The aesthetic carries the principles. +- **Plain** — no jargon shields, no marketing froth, no decorative flourish. Plain language is a moral stance, not a design constraint. + +Voice: foundation-run public infrastructure, not a startup. Concise sentences, second person sparingly, never exclamation marks, never emojis. Refers to atproto concepts (DIDs, handles, PDSes) by name and explains them once, in plain terms, where they first appear — never hides them, never decorates them. + +Emotional goal: the user should leave the interface feeling **calm and respected**, not impressed. If the dominant feeling is "wow", the design has overreached. + +## Anti-references + +The strongest trap to avoid is the **crypto-wallet aesthetic**. Certified is not a wallet, even though `/settings/wallet` can link one. Specifically reject: + +- Neon accents on black backgrounds; gradient meshes; gradient text. +- Glassmorphism, blurred translucent cards, "Web3" depth tricks. +- Cyberpunk or terminal-coded type pairings used decoratively. +- Animated gradients, particle fields, orbiting-token hero treatments used as identity (the existing `orbiting-logos` is a partner-logo affordance, not a vibe — keep it disciplined). +- Copy that frames identity as "ownership", "your keys your X", or any other phrasing borrowed from crypto self-custody marketing. Certified is about portability, not custody. + +Secondary anti-references, weaker but still worth naming: + +- **SaaS-cream auth-as-a-service**: Auth0 / Clerk / WorkOS lane — cream backgrounds, navy with warm-orange accent, illustrated heroes, "developer-first" framing. Certified is for end users first. +- **Bluesky / consumer-social tone**: rounded cards, friendly blue accents, app-store-y hero. Certified is identity infrastructure that happens to share a protocol with Bluesky, not a social product. +- **Generic foundation / NGO**: stock photography, vague "empowering communities" copy, navy-and-gold credibility palette. The credibility has to be earned by the work, not signaled by the palette. + +## Design Principles + +1. **Quiet trust over loud marketing.** The interface should read as durable infrastructure that has been running for a decade. If a flourish exists only to impress, remove it. The brand register on `/welcome` is allowed to be more expressive, but never at the cost of feeling settled and serious. +2. **Plain language is a design element.** Copy is not a decoration applied at the end — it carries the trust contract. Explain DIDs, handles, and PDSes in human terms the first time they appear on a screen, never with disclaimers or jargon shields. +3. **Product is the substrate; brand frames the entrance.** `/welcome` and `/about` may lead with identity-forward typography and long-form content. The gated app should feel like the same foundation, but quieter — settings, groups, and profiles are tools, not surfaces to perform on. +4. **Visibly not-a-wallet.** Every color, type, and motion choice should put deliberate distance between Certified and crypto-wallet conventions. When a decision is borderline, pick the option a wallet would not. +5. **Accessibility is a cognitive on-ramp, not just a contrast check.** A11y here means both an atproto-novice and an atproto-fluent user get oriented on the same screen. Plain copy, predictable structure, and inline explanations carry as much weight as AA contrast. + +## Accessibility & Inclusion + +Floor: **WCAG 2.2 AA**, with deliberate **atproto-fluency on-ramps** layered on top. + +- AA contrast across all text and interactive states; focus rings always visible (`:focus-visible` styles already exist in `globals.css`). +- Keyboard-complete on every flow, including OAuth callback and group management. +- `prefers-reduced-motion` respected — motion is decoration, never load-bearing. +- The skip-to-main link in `layout.tsx` stays. +- Atproto concepts (DID, handle, PDS, group, attestation) get a one-line plain-language gloss the first time they appear on a screen. Power users can ignore the gloss; novices can rely on it. +- Forms surface validation in plain language ("This handle is already taken at certified.one"), never as opaque error codes. +- No flow assumes color alone conveys meaning — pair color with icon or label for status, success, and error. From 6fc27d2a1bd6829e295f26fafb1adcc1350d5ff8 Mon Sep 17 00:00:00 2001 From: holke <103380539+holkexyz@users.noreply.github.com> Date: Sun, 10 May 2026 12:58:01 +0000 Subject: [PATCH 02/15] docs(design): adopt semantic token layer and component-library canon Imports system depth from certs-social/DESIGN.md while keeping certified-app's two-register reality (brand on /welcome and /about, product everywhere else). Light-only; semantic layer is structured so a future [data-theme="dark"] is a value-flip, not a refactor. DESIGN.md now describes target state: - Two-layer color system: invariant primitives plus semantic tokens (--bg-canvas, --bg-sunken, --bg-raised, --bg-elevated, --fg-primary, --fg-secondary, --fg-muted, --btn-primary-bg). - Two-register layout doctrine: brand register full-bleed and grids on /welcome and /about; product register centered narrow column up to ~1024px with no desktop sidebars. - Three-step shadow vocabulary (--shadow-sm/md/lg), confined to floating elements. - Component library is canonical: Button (4 variants, 3 sizes), Badge, Avatar, Input, Textarea in src/components/ui/. BEM-style classes in globals.css are marked legacy and slated to migrate. - OpenType features documented: tnum on numeric UI, case on uppercase labels, zero on DID and AT URI strings. - iOS 16px auto-zoom rule on mobile inputs (<=768px). - Spring easing cubic-bezier(0.16, 1, 0.3, 1) named as the signature motion curve. - Type scale formalized as a per-role table with weight ceiling on Inter (max 600; 700 reserved for Noto Serif headlines). - 12 named rules total (was 9): adds Semantic-Token, Weight-Ceiling-on-Inter, Uppercase-Plus-Tracking; renames Overlay-Only-Shadow to Floating-Only-Shadow. - Border-radius scale with documented exceptions (4px hero CTA, 999px pills, 50% circles). - New Don'ts: mix registers, desktop sidebars, Inter 700, raw hex in tailwind utilities, fill on Lucide icons, image without onError fallback. The .impeccable/design.json sidecar is regenerated to match (12 components, 20 colors, 12 rules, 3 shadows, 3 motion tokens). AGENTS.md section 0 pointer is updated to flag both implied migrations: token refactor (concrete --color-* to semantic two-layer) and component canonicalization (BEM legacy to ui/* components). Skipped from certs-social: bottom nav, single-column-everywhere doctrine, feed cards, dark mode, three-mode navbar, skeleton states, agent-prompt-guide section. None of those fit this product. Co-Authored-By: Claude Opus 4.7 (1M context) --- .impeccable/design.json | 390 +++++++++++++++++++++---------- AGENTS.md | 4 +- DESIGN.md | 507 ++++++++++++++++++++++++++++++---------- 3 files changed, 650 insertions(+), 251 deletions(-) diff --git a/.impeccable/design.json b/.impeccable/design.json index 6368c064..ca206f9b 100644 --- a/.impeccable/design.json +++ b/.impeccable/design.json @@ -4,24 +4,129 @@ "title": "Design System: Certified", "extensions": { "colorMeta": { - "ink-black": { + "ink": { "role": "primary", - "displayName": "Ink Black", + "displayName": "Ink", "canonical": "#111111", "tonalRamp": [ "#000000", "#0a0a0a", "#111111", "#1f1f1f", - "#2e2e2e", + "#2a2a2a", "#454547", "#4c4546", "#7e7576" ] }, - "archive-gray": { + "paper": { + "role": "neutral", + "displayName": "Paper", + "canonical": "#ffffff", + "tonalRamp": [ + "#c8c8c8", + "#d8d8d8", + "#e6e6e6", + "#efefef", + "#f6f6f6", + "#fafafa", + "#fdfdfd", + "#ffffff" + ] + }, + "slate": { "role": "neutral", - "displayName": "Archive Gray", + "displayName": "Slate", + "canonical": "#5e5e5e", + "tonalRamp": [ + "#1f1f1f", + "#2e2e2e", + "#3d3d3d", + "#4c4c4c", + "#5e5e5e", + "#7a7a7a", + "#9e9e9e", + "#cccccc" + ] + }, + "bg-canvas": { + "role": "surface", + "displayName": "Canvas", + "canonical": "#f9f9f9", + "tonalRamp": [ + "#bababa", + "#cccccc", + "#dddddd", + "#e9e9e9", + "#f1f1f1", + "#f6f6f6", + "#f9f9f9", + "#fcfcfc" + ] + }, + "bg-sunken": { + "role": "surface", + "displayName": "Sunken", + "canonical": "#eeeeee", + "tonalRamp": [ + "#a8a8a8", + "#bcbcbc", + "#d0d0d0", + "#e0e0e0", + "#eeeeee", + "#f3f3f3", + "#f7f7f7", + "#fbfbfb" + ] + }, + "bg-raised": { + "role": "surface", + "displayName": "Raised", + "canonical": "#f3f3f3", + "tonalRamp": [ + "#b3b3b3", + "#c5c5c5", + "#d6d6d6", + "#e4e4e4", + "#ededed", + "#f3f3f3", + "#f7f7f7", + "#fbfbfb" + ] + }, + "bg-elevated": { + "role": "surface", + "displayName": "Elevated", + "canonical": "#ffffff", + "tonalRamp": [ + "#c8c8c8", + "#d8d8d8", + "#e6e6e6", + "#efefef", + "#f6f6f6", + "#fafafa", + "#fdfdfd", + "#ffffff" + ] + }, + "fg-primary": { + "role": "foreground", + "displayName": "Primary Foreground", + "canonical": "#111111", + "tonalRamp": [ + "#000000", + "#0a0a0a", + "#111111", + "#1f1f1f", + "#2a2a2a", + "#454547", + "#4c4546", + "#7e7576" + ] + }, + "fg-secondary": { + "role": "foreground", + "displayName": "Secondary Foreground", "canonical": "#4c4546", "tonalRamp": [ "#1d1a1b", @@ -34,9 +139,9 @@ "#cfc4c5" ] }, - "bureau-iron": { - "role": "neutral", - "displayName": "Bureau Iron", + "fg-muted": { + "role": "foreground", + "displayName": "Muted Foreground", "canonical": "#7e7576", "tonalRamp": [ "#3b3636", @@ -79,66 +184,6 @@ "#f7f7f7" ] }, - "public-stone": { - "role": "neutral", - "displayName": "Public Stone", - "canonical": "#eeeeee", - "tonalRamp": [ - "#a8a8a8", - "#bcbcbc", - "#d0d0d0", - "#e0e0e0", - "#eeeeee", - "#f3f3f3", - "#f7f7f7", - "#fbfbfb" - ] - }, - "vellum": { - "role": "neutral", - "displayName": "Vellum", - "canonical": "#f3f3f3", - "tonalRamp": [ - "#b3b3b3", - "#c5c5c5", - "#d6d6d6", - "#e4e4e4", - "#ededed", - "#f3f3f3", - "#f7f7f7", - "#fbfbfb" - ] - }, - "notice-paper-white": { - "role": "neutral", - "displayName": "Notice-Paper White", - "canonical": "#f9f9f9", - "tonalRamp": [ - "#bababa", - "#cccccc", - "#dddddd", - "#e9e9e9", - "#f1f1f1", - "#f6f6f6", - "#f9f9f9", - "#fcfcfc" - ] - }, - "pure-white": { - "role": "neutral", - "displayName": "Pure White", - "canonical": "#ffffff", - "tonalRamp": [ - "#c8c8c8", - "#d8d8d8", - "#e6e6e6", - "#efefef", - "#f6f6f6", - "#fafafa", - "#fdfdfd", - "#ffffff" - ] - }, "annotation-green": { "role": "tertiary", "displayName": "Annotation Green", @@ -154,6 +199,26 @@ "#e1ebcf" ] }, + "badge-success-bg": { + "role": "semantic", + "displayName": "Verified Badge BG", + "canonical": "#e8f5e9" + }, + "badge-success-fg": { + "role": "semantic", + "displayName": "Verified Badge FG", + "canonical": "#1b7a3d" + }, + "badge-warning-bg": { + "role": "semantic", + "displayName": "Pending Badge BG", + "canonical": "#fff3e0" + }, + "badge-warning-fg": { + "role": "semantic", + "displayName": "Pending Badge FG", + "canonical": "#b37100" + }, "success-leaf": { "role": "semantic", "displayName": "Success Leaf", @@ -183,126 +248,164 @@ "displayName": "Headline", "purpose": "Section headlines on landing surface and authenticated app pages. Noto Serif at clamp(2rem, 3vw + 0.5rem, 3rem)." }, + "card-title": { + "displayName": "Card Title", + "purpose": "App-card and dash-card titles. Noto Serif 1.375rem weight 700, line-height 1.3." + }, "body": { "displayName": "Body", - "purpose": "Default running copy. Inter 400 1rem, line-height 1.6, color archive-gray, max 65-75ch." + "purpose": "Default running copy. Inter 400 1rem, line-height 1.6, color fg-secondary, max 65 to 75ch." }, "body-sm": { "displayName": "Body Small", - "purpose": "Meta copy, helper text, captions inside cards." + "purpose": "Helper text, descriptions, captions inside cards." }, "label": { "displayName": "Label", - "purpose": "Eyebrows above section headlines and inside cards. Inter 500 0.6875rem, ls 0.2em, uppercase, color bureau-iron." + "purpose": "Eyebrows above section headlines and inside cards. Inter 500 0.6875rem, ls 0.2em, uppercase, color fg-muted. Apply font-feature-settings 'tnum' 1, 'case' 1." }, "nav-label": { "displayName": "Nav Label", - "purpose": "Authenticated top-nav links. Inter 500 0.75rem, ls 0.15em, uppercase, color bureau-iron with active state in ink-black." + "purpose": "Authenticated top-nav links. Inter 500 0.75rem, ls 0.15em, uppercase, color fg-muted with active state in fg-primary. Apply font-feature-settings 'case' 1." } }, "shadows": [ { - "name": "overlay-ambient", - "value": "0 8px 32px rgba(0, 0, 0, 0.12)", - "purpose": "The single sanctioned shadow. Reserved for modals, dropdowns, and the floating feedback sheet. Surfaces that scroll with the page are flat." + "name": "shadow-sm", + "value": "0 1px 2px rgba(0, 0, 0, 0.05)", + "purpose": "Subtle lift on small floating elements." + }, + { + "name": "shadow-md", + "value": "0 4px 12px rgba(0, 0, 0, 0.08)", + "purpose": "Dropdowns and the floating feedback trigger." + }, + { + "name": "shadow-lg", + "value": "0 12px 32px rgba(0, 0, 0, 0.12)", + "purpose": "Modals and bottom sheets." } ], "motion": [ { "name": "transition-fast", "value": "150ms ease-out", - "purpose": "Color, border, and opacity state changes (hover, focus)." + "purpose": "Micro-interactions: hover, focus, color shifts." }, { "name": "transition-base", "value": "250ms ease-out", - "purpose": "Default for transform and layout-adjacent transitions that are not on layout properties themselves." + "purpose": "Medium transitions: navbar state, dropdown entry." }, { "name": "transition-slow", "value": "400ms cubic-bezier(0.16, 1, 0.3, 1)", - "purpose": "Fade-up reveals on hero load. Exponential ease-out, never bounce." + "purpose": "Layout shifts: modal slide, bottom-sheet entry, hero reveal. The signature spring easing of the system." } ], "breakpoints": [ { - "name": "sm", - "value": "640px" - }, - { - "name": "md", + "name": "mobile-cap", "value": "768px" }, { - "name": "lg", + "name": "container-product", "value": "1024px" }, { - "name": "xl", - "value": "1280px" - }, - { - "name": "2xl", + "name": "container-brand", "value": "1536px" } ] }, "components": [ { - "name": "Primary Button", + "name": "Primary Button (md)", "kind": "button", "refersTo": "button-primary", - "description": "Solid ink-black primary action. Hero CTAs use 18px 40px padding; in-app CTAs use 16px 24px. Hover drops opacity to 0.9; active scales to 0.97.", - "html": "", - "css": ".ds-btn-primary { display: inline-flex; align-items: center; gap: 12px; padding: 18px 40px; font-size: 1.125rem; font-weight: 500; font-family: Inter, system-ui, -apple-system, sans-serif; color: #ffffff; background: #111111; border: none; border-radius: 4px; cursor: pointer; transition: opacity 200ms ease-out, transform 200ms ease-out; text-decoration: none; } .ds-btn-primary:hover { opacity: 0.9; } .ds-btn-primary:active { transform: scale(0.97); } .ds-btn-primary:focus-visible { outline: 2px solid #111111; outline-offset: 2px; } .ds-btn-primary__arrow { font-size: 1.25rem; line-height: 1; }" + "description": "Solid ink primary action via the canonical ", + "css": ".ds-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; font-family: Inter, system-ui, -apple-system, sans-serif; font-weight: 500; letter-spacing: 0.05em; border-radius: 2px; transition: all 150ms ease-out; cursor: pointer; border: none; } .ds-btn:focus-visible { outline: 2px solid #111111; outline-offset: 2px; } .ds-btn:active { transform: scale(0.97); } .ds-btn--primary { background: #111111; color: #ffffff; } .ds-btn--primary:hover { opacity: 0.9; } .ds-btn--md { padding: 10px 24px; font-size: 0.875rem; }" }, { - "name": "Secondary Button", + "name": "Secondary Button (md)", "kind": "button", "refersTo": "button-secondary", - "description": "Text-link with hairline underline on hover. Never fills. Used as the alternate sign-in path next to a primary action.", - "html": "", - "css": ".ds-btn-secondary { display: inline-flex; align-items: center; gap: 8px; padding: 18px 40px; font-size: 1.125rem; font-weight: 500; font-family: Inter, system-ui, -apple-system, sans-serif; color: #111111; background: transparent; border: none; border-bottom: 2px solid transparent; cursor: pointer; transition: border-color 150ms ease-out; text-decoration: none; } .ds-btn-secondary:hover { border-bottom-color: #111111; } .ds-btn-secondary:focus-visible { outline: 2px solid #111111; outline-offset: 2px; }" + "description": "Bordered transparent button. Hairline border deepens on hover. Use for cancel and secondary actions next to a primary action.", + "html": "", + "css": ".ds-btn--secondary { background: transparent; color: #111111; border: 1px solid rgba(0, 0, 0, 0.15); } .ds-btn--secondary:hover { border-color: rgba(0, 0, 0, 0.40); }" + }, + { + "name": "Ghost Button (sm)", + "kind": "button", + "refersTo": "button-ghost", + "description": "Minimal toolbar action. No background or border at rest. Hover surfaces a subtle overlay. Use for inline actions inside cards (Pencil, Copy, ChevronDown).", + "html": "", + "css": ".ds-btn--ghost { background: transparent; color: #7e7576; } .ds-btn--ghost:hover { background: rgba(0, 0, 0, 0.04); color: #111111; } .ds-btn--sm { padding: 6px 16px; font-size: 0.75rem; }" + }, + { + "name": "Destructive Button (md)", + "kind": "button", + "refersTo": "button-destructive", + "description": "Tinted-error action for delete, remove, and leave-group. Background uses error at 10% alpha; border at 20%. Confirms an irreversible action.", + "html": "", + "css": ".ds-btn--destructive { background: rgba(186, 26, 26, 0.10); color: #ba1a1a; border: 1px solid rgba(186, 26, 26, 0.20); } .ds-btn--destructive:hover { background: rgba(186, 26, 26, 0.15); border-color: rgba(186, 26, 26, 0.35); }" }, { "name": "Text Input", "kind": "input", "refersTo": "input-default", - "description": "Form field used in sign-in modal and feedback modal. The 1.5px border (slightly thicker than page hairlines) signals a write-here surface; focus turns the border annotation-green with a soft ring.", + "description": "Form field via the canonical component. 44px height with 1px border-default; focus shifts the border to annotation green and adds a soft ring. Mobile (<=768px) must use 16px font-size minimum to prevent iOS auto-zoom.", "html": "", - "css": ".ds-input-wrap { display: flex; flex-direction: column; gap: 8px; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-input-label { font-size: 0.875rem; font-weight: 500; color: #4c4546; } .ds-input { width: 100%; height: 48px; padding: 0 16px; border: 1.5px solid #e2e2e2; border-radius: 2px; font-size: 1rem; font-family: inherit; color: #111111; background: #ffffff; transition: border-color 150ms ease-out, box-shadow 150ms ease-out; outline: none; } .ds-input::placeholder { color: #7e7576; } .ds-input:focus { border-color: #94bb51; box-shadow: 0 0 0 3px rgba(148, 187, 81, 0.15); } .ds-input:disabled { opacity: 0.6; cursor: not-allowed; }" + "css": ".ds-input-wrap { display: flex; flex-direction: column; gap: 8px; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-input-label { font-size: 0.875rem; font-weight: 500; color: #4c4546; } .ds-input { width: 100%; height: 44px; padding: 0 16px; border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 2px; font-size: 1rem; font-family: inherit; color: #111111; background: #ffffff; transition: border-color 150ms ease-out, box-shadow 150ms ease-out; outline: none; } .ds-input::placeholder { color: #7e7576; } .ds-input:focus { border-color: #94bb51; box-shadow: 0 0 0 3px rgba(148, 187, 81, 0.15); } .ds-input:disabled { opacity: 0.6; cursor: not-allowed; } @media (max-width: 768px) { .ds-input { font-size: 16px; } }" }, { "name": "App Card", "kind": "card", - "refersTo": "card-default", - "description": "The default container in authenticated app pages. Pure white lifted onto the notice-paper page, hairline border, no shadow. Hover only deepens the border tone slightly. Internal padding 24px.", - "html": "
Account

Two-factor authentication

Add an extra layer of protection to your sign-in.

", - "css": ".ds-card { background: #ffffff; border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 2px; padding: 24px; transition: border-color 150ms ease-out; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-card:hover { border-color: rgba(0, 0, 0, 0.12); } .ds-card__label { font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.08em; color: #7e7576; margin-bottom: 8px; text-transform: uppercase; } .ds-card__title { font-family: 'Noto Serif', Georgia, serif; font-size: 1.375rem; font-weight: 700; line-height: 1.3; color: #111111; margin: 0 0 8px 0; } .ds-card__body { font-size: 0.875rem; line-height: 1.5; color: #4c4546; margin: 0; }" + "refersTo": "card-app", + "description": "Default container in the gated app. White background, hairline border, 2px radius, 24px padding. Hover deepens border tone slightly. No nested cards.", + "html": "
Account

Two-factor authentication

Add an extra layer of protection to your sign-in.

", + "css": ".ds-card-app { background: #ffffff; border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 2px; padding: 24px; transition: border-color 150ms ease-out; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-card-app:hover { border-color: rgba(0, 0, 0, 0.12); } .ds-card-app__label { font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.08em; color: #7e7576; margin-bottom: 8px; text-transform: uppercase; font-feature-settings: 'tnum' 1, 'case' 1; } .ds-card-app__title { font-family: 'Noto Serif', Georgia, serif; font-size: 1.375rem; font-weight: 700; line-height: 1.3; color: #111111; margin: 0 0 8px 0; } .ds-card-app__body { font-size: 0.875rem; line-height: 1.5; color: #4c4546; margin: 0; }" }, { - "name": "Status Chip", + "name": "Dashboard Card", + "kind": "card", + "refersTo": "card-dash", + "description": "Separator-style card for stacked content (org-settings, dash-card on profile). No background, no border-radius; only a 1px bottom border. The whitespace does the structural work.", + "html": "

Handle

Your AT Protocol handle is the public name for your account on certified.one.

", + "css": ".ds-card-dash { padding: 20px 0; border-bottom: 1px solid rgba(0, 0, 0, 0.06); font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-card-dash:last-child { border-bottom: none; } .ds-card-dash__title { font-family: 'Noto Serif', Georgia, serif; font-size: 1.375rem; font-weight: 700; line-height: 1.3; color: #111111; margin: 0 0 8px 0; } .ds-card-dash__desc { font-size: 0.875rem; line-height: 1.5; color: #4c4546; margin: 0; }" + }, + { + "name": "Verified Badge", "kind": "chip", - "refersTo": "chip-default", - "description": "Small pill used for status (2FA on/off, wallet verified/unverified, group sync changed/removed). Default fill is notice-paper-white; tinted variants use 0.1-opacity semantic color with the matching semantic text color. No shadow, no border.", - "html": "Enabled", - "css": ".ds-chip { display: inline-flex; align-items: center; padding: 4px 12px; border-radius: 2px; font-size: 0.75rem; font-weight: 500; font-family: Inter, system-ui, -apple-system, sans-serif; background: #f9f9f9; color: #7e7576; } .ds-chip--on { background: rgba(46, 204, 113, 0.1); color: #047857; } .ds-chip--off { background: #f9f9f9; color: #7e7576; } .ds-chip--warn { background: #fef9e7; color: #7a6420; } .ds-chip--err { background: rgba(186, 26, 26, 0.08); color: #ba1a1a; }" + "refersTo": "badge-verified", + "description": "Pill-shaped status badge via the canonical component. Verified uses a green tint with a Lucide CheckCircle icon at 16px.", + "html": "Verified", + "css": ".ds-badge { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px; border-radius: 999px; font-size: 0.875rem; font-weight: 500; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-badge--verified { background: #e8f5e9; color: #1b7a3d; } .ds-badge--pending { background: #fff3e0; color: #b37100; } .ds-badge--unverified { background: #f9f9f9; color: #7e7576; border: 1px solid rgba(0, 0, 0, 0.08); }" + }, + { + "name": "Avatar (md)", + "kind": "card", + "refersTo": "card-app", + "description": "Circular avatar via the canonical component. Four sizes: 32 / 48 / 64 / 96px. Fallback renders the first two characters of the initials on a sunken background.", + "html": "
\"Alice\"
", + "css": ".ds-avatar { display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; overflow: hidden; background: #eeeeee; } .ds-avatar--sm { width: 32px; height: 32px; } .ds-avatar--md { width: 48px; height: 48px; } .ds-avatar--lg { width: 64px; height: 64px; } .ds-avatar--xl { width: 96px; height: 96px; } .ds-avatar img { width: 100%; height: 100%; object-fit: cover; } .ds-avatar__fallback { font-family: Inter, system-ui, -apple-system, sans-serif; font-weight: 600; color: #4c4546; }" }, { "name": "App Nav Link", "kind": "nav", "refersTo": "nav-link", - "description": "Authenticated top-nav link. Inter 500 0.75rem, letter-spacing 0.15em, uppercase, bureau-iron at rest. Hover and active shift to ink-black; active also adds a 1.5px ink-black bottom border.", + "description": "Authenticated top-nav link. Inter 500 0.75rem, letter-spacing 0.15em, uppercase, fg-muted at rest. Hover and active shift to fg-primary; active also adds a 1.5px ink-black bottom border. Apply font-feature-settings 'case' 1.", "html": "", - "css": ".ds-nav { display: flex; align-items: center; gap: 32px; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-nav__link { font-size: 0.75rem; font-weight: 500; letter-spacing: 0.15em; text-transform: uppercase; color: #7e7576; text-decoration: none; transition: color 150ms ease-out; padding: 4px 0; border-bottom: 1.5px solid transparent; } .ds-nav__link:hover { color: #111111; } .ds-nav__link--active { color: #111111; border-bottom-color: #111111; }" + "css": ".ds-nav { display: flex; align-items: center; gap: 32px; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-nav__link { font-size: 0.75rem; font-weight: 500; letter-spacing: 0.15em; text-transform: uppercase; color: #7e7576; text-decoration: none; transition: color 150ms ease-out; padding: 4px 0; border-bottom: 1.5px solid transparent; font-feature-settings: 'case' 1; } .ds-nav__link:hover { color: #111111; } .ds-nav__link--active { color: #111111; border-bottom-color: #111111; }" }, { "name": "Modal Overlay", "kind": "card", - "refersTo": "card-default", - "description": "Sign-in modal and feedback modal share this shell. The single sanctioned shadow appears here because the surface is genuinely detached from the page. Backdrop dims to 0.7 alpha black.", + "refersTo": "card-app", + "description": "Standard modal shell (sign-in, feedback). Uses shadow-lg because the surface is genuinely detached. Backdrop dims to 0.7 alpha black. Entry animates with the spring easing curve.", "html": "

Sign in

Enter the handle for any AT Protocol account.

", - "css": ".ds-modal-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.7); display: flex; align-items: center; justify-content: center; padding: 16px; } .ds-modal { width: 100%; max-width: 440px; background: #ffffff; border-radius: 4px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); padding: 32px; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-modal__header { margin-bottom: 16px; } .ds-modal__title { font-family: 'Noto Serif', Georgia, serif; font-size: 1.5rem; font-weight: 700; line-height: 1.2; letter-spacing: -0.01em; color: #111111; margin: 0; } .ds-modal__body { font-size: 0.9375rem; line-height: 1.6; color: #4c4546; }" + "css": ".ds-modal-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.7); display: flex; align-items: center; justify-content: center; padding: 16px; } .ds-modal { width: 100%; max-width: 440px; background: #ffffff; border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 2px; box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12); padding: 32px; font-family: Inter, system-ui, -apple-system, sans-serif; } .ds-modal__header { margin-bottom: 16px; } .ds-modal__title { font-family: Inter, system-ui, -apple-system, sans-serif; font-size: 1.125rem; font-weight: 700; line-height: 1.3; letter-spacing: -0.01em; color: #111111; margin: 0; } .ds-modal__body { font-size: 1rem; line-height: 1.6; color: #4c4546; }" }, { "name": "Hero Title", @@ -314,13 +417,15 @@ ], "narrative": { "northStar": "The Notary's Ledger", - "overview": "Certified feels like a notary's ledger reimagined as a mobile app: austere, monochrome, and quietly authoritative. The near-absence of color forces attention onto the content: serif headlines anchor each card like a document title, while the surrounding chrome recedes into warm grays. The interface is deliberately under-decorated. No gradients, no brand accent hue, no playful illustrations. The restraint is the brand. The system is two-toned at the surface level: an off-white paper and a near-black ink, separated by a small library of warm-tinted neutrals that carry meta-text, dividers, and structural surfaces. Headlines are set in Noto Serif at heavy weight; signature accents lean on Instrument Serif italic, the closest thing the system has to a flourish. Body and UI labels are Inter, in restrained weights. Edges are nearly square (2px), borders are 1px and hairline, and shadows are absent except on overlay elements that genuinely need to detach from the page. This system explicitly rejects the crypto-wallet aesthetic, the SaaS-cream auth-as-a-service lane, the Bluesky / consumer-social look, and the generic foundation / NGO look. Where competitors perform, Certified records.", + "overview": "Certified feels like a notary's ledger reimagined as a mobile app: austere, monochrome, and quietly authoritative. The near-absence of color forces attention onto the content. Serif headlines anchor each card like a document title, while the surrounding chrome recedes into warm grays. The interface is deliberately under-decorated. No gradients, no brand accent hue, no playful illustrations. The restraint is the brand. The system is two-toned at the surface level: an off-white paper canvas and a near-black ink, separated by a small library of warm-tinted neutrals that carry meta-text, dividers, and structural surfaces. Headlines are Noto Serif at heavy weight; signature accents lean on Instrument Serif italic, the closest thing the system has to a flourish. Body and UI labels are Inter, in restrained weights. Edges are nearly square (2px), borders are 1px and hairline, and shadows are absent except on floating elements that genuinely need to detach from the page. Certified is a mixed surface, codified as a two-register layout doctrine: brand register on /welcome and /about with full-bleed hero and multi-column grids, product register everywhere else with a centered narrow column up to ~1024px and no desktop sidebars. The system is light-only today; tokens are structured so a future dark mode is a value-flip, not a refactor.", "keyCharacteristics": [ "Two-tone monochrome: warm-tinted neutrals, ink-black ink, no brand accent hue.", "Serif-led typographic identity: Noto Serif for authority, Instrument Serif italic for the single accent voice, Inter for everything else.", - "Near-square edges (2px default; 4px reserved for primary action surfaces).", - "Flat by default. One ambient shadow vocabulary, reserved for overlays.", - "Borders are hairline (1px) and tonal: they organize, they don't decorate.", + "Near-square edges (2px default; 999px reserved for pills; 50% reserved for circles).", + "Flat by default. A three-step shadow vocabulary, reserved for floating elements only.", + "Two-layer token system: invariant primitives plus semantic tokens. Component CSS references semantic tokens, never primitives or raw hex.", + "Component library is canonical: Button, Badge, Avatar, Input, Textarea in src/components/ui/. BEM-style classes in globals.css are legacy.", + "Spring easing (cubic-bezier(0.16, 1, 0.3, 1)) for layout shifts; ease-out for micro-interactions.", "Restraint is the affordance. If a flourish exists only to impress, remove it." ], "rules": [ @@ -331,7 +436,7 @@ }, { "name": "The Warm-Neutral Rule", - "body": "Every gray is warm. Cool grays (chroma toward blue) are forbidden: they read as web2 SaaS. Pulling a neutral from outside this list requires changing the list, not the screen.", + "body": "Every gray is warm. Cool grays (chroma toward blue) are forbidden; they read as web2 SaaS. Pulling a neutral from outside this list requires changing the list, not the screen.", "section": "colors" }, { @@ -339,9 +444,14 @@ "body": "Annotation Green is the only non-semantic color in the interface. It appears on focus rings and confirmation icons, nowhere else. If you find yourself wanting a second non-semantic color, the answer is restraint, not addition.", "section": "colors" }, + { + "name": "The Semantic-Token Rule", + "body": "Component CSS must reference semantic tokens (bg-canvas, fg-primary, border-default), never primitives (color-primary, raw hex). The semantic layer is the migration path; raw hex in component CSS is the migration target.", + "section": "colors" + }, { "name": "The Serif-Authority Rule", - "body": "Headlines are always serif. Sans-serif headlines do not exist in this system. If a sans-serif headline feels needed, it is a label or a title in disguise: set it accordingly.", + "body": "Headlines on landing surfaces and content pages are always Noto Serif. Sans-serif headlines exist in exactly one place (the sign-in modal heading), where the modal context calls for a tighter UI register. If a sans-serif headline feels needed elsewhere, it is a label or a title in disguise.", "section": "typography" }, { @@ -354,33 +464,49 @@ "body": "Body copy is capped at 65 to 75 characters per line. Edge-to-edge prose is forbidden; long lines are uncomfortable on an instrument that already asks for trust.", "section": "typography" }, + { + "name": "The Weight-Ceiling-on-Inter Rule", + "body": "Inter caps at weight 600. Weight 700 is reserved exclusively for Noto Serif headlines. Bold sans-serif body or labels are forbidden; they pull eye weight from the serif and undermine the typographic hierarchy.", + "section": "typography" + }, + { + "name": "The Uppercase-Plus-Tracking Rule", + "body": "Section labels, card labels, and navigation use text-transform uppercase with letter-spacing in the 0.08 to 0.20em range. This creates hierarchy without size increase.", + "section": "typography" + }, { "name": "The Flat-By-Default Rule", "body": "Pages, sections, cards, navigation, and buttons are flat. Shadows do not signal hover, focus, or interactivity on these surfaces.", "section": "elevation" }, { - "name": "The Overlay-Only-Shadow Rule", - "body": "A box-shadow value is allowed only on elements that are physically detached from the page (modals, dropdowns, floating sheets). If the element scrolls with the page, it is flat.", + "name": "The Floating-Only-Shadow Rule", + "body": "A box-shadow value is allowed only on elements that are physically detached from the page (modals, dropdowns, the floating feedback trigger, bottom sheets). If the element scrolls with the page, it is flat.", "section": "elevation" }, { "name": "The Hairline Rule", - "body": "Borders are 1px and use the existing border ramp. 2px or thicker accent borders, side stripes, and colored gutters are forbidden: they are the absolute ban from the impeccable design laws.", + "body": "Borders are 1px (or 1.5px on inputs to signal a write-here surface) and use the border ramp. 2px or thicker accent borders, side stripes, and colored gutters are forbidden; they are the absolute ban from the impeccable design laws.", "section": "elevation" } ], "dos": [ "Do organize hierarchy with serif headlines and tonal warm neutrals. Restraint is the affordance.", - "Do use ink-black (#111111) for primary text and primary actions. Never #000.", - "Do use notice-paper-white (#f9f9f9) for page surfaces and pure-white only for lifted cards and modals.", + "Do reference semantic tokens (--bg-canvas, --fg-primary, --border-default) in component CSS. Never hard-code hex values.", + "Do use --bg-canvas (#f9f9f9) for page surfaces and --bg-elevated (#ffffff) for lifted cards and modals.", + "Do use --fg-secondary (#4c4546) for default body text. Reserve --fg-primary for headings and primary interactive text.", "Do keep neutrals warm. If you need a new gray, it must sit on the warm side of neutral.", "Do use Instrument Serif italic for one accent phrase per hero, and nowhere else.", "Do cap body copy at 65 to 75 characters per line.", + "Do reach for the