diff --git a/.impeccable/design.json b/.impeccable/design.json new file mode 100644 index 00000000..ca206f9b --- /dev/null +++ b/.impeccable/design.json @@ -0,0 +1,537 @@ +{ + "schemaVersion": 2, + "generatedAt": "2026-05-10T00:00:00.000Z", + "title": "Design System: Certified", + "extensions": { + "colorMeta": { + "ink": { + "role": "primary", + "displayName": "Ink", + "canonical": "#111111", + "tonalRamp": [ + "#000000", + "#0a0a0a", + "#111111", + "#1f1f1f", + "#2a2a2a", + "#454547", + "#4c4546", + "#7e7576" + ] + }, + "paper": { + "role": "neutral", + "displayName": "Paper", + "canonical": "#ffffff", + "tonalRamp": [ + "#c8c8c8", + "#d8d8d8", + "#e6e6e6", + "#efefef", + "#f6f6f6", + "#fafafa", + "#fdfdfd", + "#ffffff" + ] + }, + "slate": { + "role": "neutral", + "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", + "#2c2a2b", + "#3b393a", + "#4c4546", + "#5e5757", + "#7e7576", + "#a39c9d", + "#cfc4c5" + ] + }, + "fg-muted": { + "role": "foreground", + "displayName": "Muted Foreground", + "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" + ] + }, + "annotation-green": { + "role": "tertiary", + "displayName": "Annotation Green", + "canonical": "#94bb51", + "tonalRamp": [ + "#3f5621", + "#577a2f", + "#6f9c3e", + "#84b048", + "#94bb51", + "#abc97a", + "#c5d8a1", + "#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", + "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)." + }, + "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 fg-secondary, max 65 to 75ch." + }, + "body-sm": { + "displayName": "Body Small", + "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 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 fg-muted with active state in fg-primary. Apply font-feature-settings 'case' 1." + } + }, + "shadows": [ + { + "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": "Micro-interactions: hover, focus, color shifts." + }, + { + "name": "transition-base", + "value": "250ms ease-out", + "purpose": "Medium transitions: navbar state, dropdown entry." + }, + { + "name": "transition-slow", + "value": "400ms cubic-bezier(0.16, 1, 0.3, 1)", + "purpose": "Layout shifts: modal slide, bottom-sheet entry, hero reveal. The signature spring easing of the system." + } + ], + "breakpoints": [ + { + "name": "mobile-cap", + "value": "768px" + }, + { + "name": "container-product", + "value": "1024px" + }, + { + "name": "container-brand", + "value": "1536px" + } + ] + }, + "components": [ + { + "name": "Primary Button (md)", + "kind": "button", + "refersTo": "button-primary", + "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 (md)", + "kind": "button", + "refersTo": "button-secondary", + "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 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: 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-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": "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": "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, 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; 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-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: 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", + "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 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; 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": [ + { + "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 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 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" + }, + { + "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 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 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 (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 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