Gradient design system

Dark base, single signal, radius zero. Everything is derived from tokens.json and enforced by design-system-integrity.md. This page is the living reference. Use the toggle to preview both modes.

Logo and favicon

The logo is the 9×9 dot-field mark. The wordmark is not used — the mark alone identifies Gradient. Signal (#D94A12) is the same in both modes. The favicon collapses to a single lit dot below 40px.

Favicon

SVG favicon: a single filled circle, signal on transparent. Works on any browser chrome regardless of light or dark tab bar. At 32×32 and below, the full 9×9 field is too small — use the single dot. At 48px+, use the full field.

on dark chrome
on light chrome
Gradient
browser tab
16px
32px

Logo clear space

Minimum clear space around the mark equals the diameter of one dot in the field. The mark is always centered in its bounding box. Never rotate, stretch, add effects, or change the color.

clear space clear space
do: signal on page, signal on surface, ink on page do: center in bounding box, maintain clear space don't: rotate, stretch, add shadow, add glow don't: change dot color, use partial mark don't: place on busy backgrounds without contrast

Favicon source

The favicon is an inline SVG data URI — no external file request. Transparent background so it adapts to any browser theme automatically.

<link rel="icon" href="data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E %3Ccircle cx='16' cy='16' r='14' fill='%23D94A12'/%3E %3C/svg%3E" />

Dark and light

Dark is the base system. Light is a mirror, not a variant — every token has a counterpart. Opt in via [data-theme="light"]. Signal (#D94A12) is the same in both modes. Status colors shift for contrast on light grounds.

dark (default)

page #0A0A0A
surface #141413
line #1E1E1D
muted #8A8A85
ink #FAFAF9
signal #D94A12

light (mirror)

page #FAFAF9
surface #FFFFFF
line #E4E4DF
muted #5E5E57
ink #0A0A0A
signal #D94A12

Status in both modes

dark status

pending #8A8A85
settled #5BC489
action #E8C547
failed #E85A5A
idle #57574F

light status

pending #5E5E57
settled #1A7F4B
action #8A6A00
failed #B3322F
idle #C4C4BD

Elevation ladder

dark elevation

00
01
02
03
04

light elevation

00
01
02
03
04

Palette

Six core tokens. Signal appears at most once per view. Status uses a separate functional palette — signal is never status.

page #0A0A0A
surface / elev-01 #141413
line #1E1E1D
muted #8A8A85
ink #FAFAF9
signal #D94A12

Status

pending
settled
requires action
failed
idle

Elevation

Depth by lightness, never shadow. Surfaces separate by stepping up the elevation ladder.

00 page
01 surface
02 raised
03 overlay
04 top

Type scale

Schibsted Grotesk for display and prose. Martian Mono for labels, data, and financial figures. Money is always two decimals, tabular nums.

display — 68px / 600 / -0.045em

$1,250,000.00

h1 — 38px / 600 / -0.035em

Balance overview

h2 — 22px / 600 / -0.025em

Recent activity

lead — 17px / 400

Card issuing and stablecoin settlement for financial institutions.

body — 14px / 400

Submit a verification request to be enabled for real money on mainnet. Test freely with test funds — sends, deposits, and balances all work.

label — 10px / mono / uppercase / 0.08em

gradient reference

mono data — 12px / tabular-nums

tr_gcN2cIp2gpeQdKwkDI2T   $12,500.00   2026-09-29

4px scale

Every spacing value sits on the 4px base. Banned values: 20, 28, 36. Row height is 32px. Control height is 40px. Section gap is 96px.

4
8
12
16
24
32
48
96

Buttons and inputs

40px control height. Radius zero. Signal for primary, border for secondary. Focus ring is 2px signal with 2px offset (ink on filled signal controls).

Data rows

The card/row pattern from the transaction detail. Label on top, description below, value right-aligned. 32px row height minimum, 12px vertical padding. 1px line dividers between siblings.

gradient reference unique id for this transaction tr_gcN2cIp2gpeQdKwkDI2T
direction outbound = funds left your account outbound
amount value moved, in your display currency $12,500.00 USD
initiated when the transfer was submitted Sep 29, 2026, 02:31 PM CDT

Mark and avatars

The mark is a lowercase g from a 5×7 display font, centered in a 9×9 dot field. Dots are round. Lit dots at full opacity, unlit at 0.08. Below 40px, collapse to a single lit dot. Avatars are square, radius 0. People are monograms only — never photographs.

Mark sizes

64px
40px (min field)
24px (dot only)

Avatar sizes

24 row
32 list
40 header
64 detail

Monograms

Below 24px or when no logo is set. Two uppercase letters, mono font, elev-02 surface. People are always monograms — never photographs.

GS
24px
BM
32px
JP
40px
AB
64px

Supplied logos

Preferred format: monochrome SVG. In tables and lists, render at 100% ink — never in the client's brand colors. Full-color logos only at ≥64px on detail pages and press materials. Image media uses border-radius: 12px (the only non-zero radius in the system). Shape is square. No circular crop on logos.

Icon system

Icons use the same 9×9 grid as the mark. Drawing area is the inner 7×7 well — the outer ring is always empty. Stroke is 1 unit, currentColor, square caps, miter joins. No curves, no fill. One icon per control.

home
send
receive
activity
settings

Link treatments

Three link types. Prose links use signal-colored underline. Chrome links (nav, breadcrumbs) are muted with no underline — hover lifts to ink. Standalone links are uppercase mono with an arrow. Visited state is never shown. Color alone never indicates a link.

View states

Every view ships four states: populated, empty, loading, error. No spinners. No shimmer. Loading holds geometry identical to the result. Empty cells show an em dash (—), not blank. Under 400ms of loading, render nothing. No success toasts — row state change is the confirmation.

populated
transfer in +$12,500.00
transfer out −$3,200.00
empty

no transfers yet.

loading (hold geometry)
error

could not load activity. reload to retry.

Empty cell vs. empty view

An em dash in a cell means no data for that field. It is not a view-level empty state.

recipient —
network —
amount $12,500.00 USD

Image and media

Three aspect ratios. Ground is always page color (#0A0A0A). No filters, no color-sampled photography. Inset media gets border-radius: 12px — the only non-zero radius in the system. Full-bleed media uses 0. Camera angles: 0° elevation or 15° three-quarter.

16:9 full-bleed
radius: 0
4:3 detail
radius: 12px (inset)
1:1 card
radius: 12px (inset)

Charts and graphs

Bar charts use signal color. The current/today bar is full opacity. Historical bars are at 0.35 opacity. Date labels left-aligned, value labels right-aligned. Axes use muted color, mono font. No gradients, no 3D, no shadows. D3 for rendering — never Canvas for data charts (accessibility).

Horizontal (timeline)

Use for trailing balance history, time-series where the date axis reads top-to-bottom. Date labels left, value labels right.

Sep 24 $8,200.00
Sep 25 $10,500.00
Sep 26 $9,100.00
Sep 27 $12,000.00
Sep 28 $12,500.00
Sep 29 $13,200.00

Vertical (comparison)

Use for category comparison, volume by day/week/month, or any chart where the x-axis is the primary dimension. Y-axis ticks on the left, category labels below. Same opacity rules apply.

$15k $10k $5k $0
$8.2k
$10.5k
$9.1k
$12.0k
$12.5k
$3.2k
$7.8k
$13.2k
Sep 22 Sep 23 Sep 24 Sep 25 Sep 26 Sep 27 Sep 28 Sep 29

Chart rules

Color: signal only. No multi-color series. Differentiate by opacity.
Today / current: full opacity. All other bars at 0.35.
Labels: mono font, muted color. Values always two decimals for money.
Axes: 1px line token. Y-axis ticks left-aligned. No grid lines inside the chart area.
Renderer: D3 (SVG). Never Canvas — screen readers can't parse it.
Refresh: auto-poll every 60s for live data. No animation on bar height changes.
Banned: gradients on bars, 3D, shadows, rounded bar caps, pie charts, donut charts.

Destructive action tiers

Four tiers of friction for destructive actions. No signal on destructive controls — never a red filled button. Default focus is always cancel. Minimum 24px gap between primary and destructive actions.

tier 1 Reversible No friction + 10s undo toast
tier 2 Irreversible, no money One dialog, verb-labelled confirm
tier 3 Irreversible, moves money Type the exact amount or reference id
tier 4 Above threshold ($1M) Second named approver + audit timestamp

Viewports and breakpoints

Four breakpoints on the token scale. Mobile is not a degraded desktop — it's a distinct layout with larger touch targets and stacked tables. Content never scrolls horizontally.

sm

< 640px

One column. 16px gutter. 44px touch targets. Tables stack into rows — never horizontal scroll.

md

640 – 1023px

Sidebar collapses to 56px icon rail. Two-column forms go single. Content fills width.

lg

1024 – 1439px

Full sidebar. Standard gutter (40px). Default comfortable density.

xl

≥ 1440px

Content caps at 1100px and centers. No stretch beyond max-width.

Viewport behavior

mobile (sm) single column
tablet (md) icon rail + content
desktop (lg) full sidebar
ultra-wide (xl) centered, 1100px cap

Responsive rules

Tables: on sm, tables stack into card rows. Never scroll horizontally.
Forms: multi-column forms collapse to single column below md.
Gutter: 40px on lg+, 16px on sm.
Touch targets: minimum 44px on sm. 32px row height on desktop, 44px on mobile.
Input font: ≥ 16px on mobile to prevent iOS Safari auto-zoom on focus.
Safe areas: respect notch/inset with env(safe-area-inset-*).
Zoom: never disable browser zoom. Content remains usable at 200%.
Banned: horizontal scroll on content, hiding content on mobile, auto-zoom triggers.

Touch and hit targets

Visual and hit targets must match. If the visual element is smaller than 44px, expand the hit target invisibly to meet the minimum. On desktop, 32px row height is acceptable; on mobile, every interactive element reaches 44px.

44px mobile
24px visual, 44px hit
32px desktop

Density

Row height: 32px comfortable (default), 24px compact (admin tables only).
Control height: 40px. All buttons, inputs, selects.
Card padding: 16px. Internal gutters follow the 4px scale.
Touch target: 44px minimum on mobile. touch-action: manipulation on controls.
No dead zones: labels, checkboxes, and radios share a single generous hit target.

Keyboard and focus

Every flow is keyboard-operable. Tab order follows DOM order — no positive tabindex. Focus rings are never removed. Overlays trap focus and return it on dismiss.

Reserved shortcuts

⌘ K command menu / focus table filter ⌘ ⏎ submit form Esc dismiss topmost overlay Shift ? shortcut list

Focus rules

Focus ring: 2px solid var(--signal), outline-offset: 2px. On filled signal controls, 2px solid var(--ink).
Never removed: focus ring is never replaced by a surface change or hidden.
:focus-visible: prefer :focus-visible over :focus to avoid distracting pointer users.
Tab order: DOM order. No positive tabindex. Skip link at top.
Overlay focus: trap focus inside modals/drawers. Return focus on dismiss.
Sticky elements: headers, footers, and banners never cover the focused element.

Interaction patterns

Links are links: use <a> for navigation. Never <button> or <div>. Cmd+Click, middle-click must work.
Optimistic updates: update UI immediately when success is likely. Roll back on failure.
URL as state: filters, tabs, pagination live in the URL. Share, refresh, back/forward all work.
Hover increases contrast: hover state is elev-02 (120ms). Never decreases contrast.
Overscroll: overscroll-behavior: contain on modals and drawers.
Scroll positions persist: back/forward restores prior scroll position.
Deep-link everything: filters, tabs, expanded panels — anything with useState.

Form patterns

Every control has a label. Enter submits single-input forms. Never block typing or paste. Errors appear next to the field, not in a toast. Submit stays enabled until the request fires.

Input types

<!-- Email — correct keyboard, autocomplete --> <input type="email" autocomplete="email" spellCheck={false} placeholder="you@yourinstitution.com" /> <!-- Numeric — number pad, no spinner --> <input inputMode="numeric" pattern="[0-9]*" placeholder="50309" /> <!-- Sensitive — masked, no autocomplete --> <input type="password" autoComplete="off" placeholder="XXX-XX-XXXX" /> <!-- Mono data — invite code --> <input type="text" spellCheck={false} autoCapitalize="characters" placeholder="GRD-XXXXX-XXXXX" />

Form rules

Labels everywhere: every control has a <label> or aria-label. Clicking the label focuses the control.
Enter submits: when a text input is the only control, Enter submits. In multi-field forms, Enter submits from the last field.
Never block paste: never disable paste on any input. Password managers and 2FA codes must work.
Don't block typing: allow any input, show validation feedback. Blocking keystrokes is confusing.
Submit stays enabled: don't pre-disable submit. Let users submit to surface validation. Disable only during the in-flight request.
Error placement: errors appear next to their field. On submit, focus the first error. State the rule, not the violation.
Placeholders: example values or patterns (e.g. GRD-XXXXX-XXXXX). End with ellipsis for open-ended fields.
Autocomplete: set meaningful autocomplete values. off for sensitive fields (SSN, EIN). Correct type for keyboards.
Spellcheck: disable on emails, codes, IDs, addresses, URLs.
Unsaved changes: warn before navigation when data could be lost.
Idempotency: every POST/PATCH includes an Idempotency-Key header. Button shows loading state and original label.

Performance

The product is an SPA on Cloudflare Workers. Fast by default — keep it that way. Every interaction should feel instant. Network requests complete under 500ms.

Loading under 400ms: render nothing. Over 400ms, show skeleton. Over 10s, name what is happening.
Stable skeletons: skeleton geometry matches the result exactly. No shimmer. No layout shift.
Network budget: POST/PATCH/DELETE complete in <500ms.
Font loading: preconnect to font origins. Use font-display: swap to avoid invisible text.
No CLS: set explicit dimensions on images and media. Reserve space for async content.
Lazy-load below fold: only preload above-the-fold assets. Lazy-load everything else.
Virtualize long lists: lists over 50 items use virtual scrolling or content-visibility: auto.
Optimistic updates: update UI immediately, reconcile on server response. On failure, roll back and show error.
No main-thread blocking: heavy computation goes to Web Workers. Never block interaction.
Auto-poll, don't push: live data refreshes on 5–60s intervals. Settling transfers poll every 5s. Balance history every 60s.

Accessibility

Financial software must be accessible to everyone. These screens end up in audit files, regulatory reviews, and compliance workflows. Accessibility is not optional.

Semantics first: prefer native elements (<button>, <a>, <label>, <table>) before aria-*.
Headings: hierarchical <h1–h6>. One <h1> per page. Skip link at top of body.
Icon-only buttons: always provide aria-label. Decorative icons use aria-hidden.
Color alone: never rely on color alone to convey status. Every status dot has a text label beside it.
Contrast: body text holds 4.5:1. Headline-scale holds 3:1. Signal on page = 4.44:1 ✓. Signal on surface (#111110) = 4.1:1, fails for body.
Reduced motion: all animations respect prefers-reduced-motion. Blinks resolve to solid. No entrance animations.
Charts: D3 SVG only, never Canvas. Screen readers can parse SVG elements. Add role="img" and aria-label on chart containers.
translate="no": on "Gradient", organization names, IDs, and code tokens. Prevents auto-translate corruption.
Announce async updates: use aria-live="polite" for status changes and inline validation.
Focus traps: modals and drawers trap focus. Esc dismisses and returns focus to the trigger.
Test: keyboard walkthrough on every PR. Tab through every interactive element. Focus ring visible and unobscured.

Voice and content

Second person, active voice. Sentence case everywhere except legal names. Name the action — never promise a feeling. The brand contract at /llms.txt is the source of truth for both humans and agents.

Writing rules

do

Issue cards and settle in stablecoins through a single API.
Submit a verification request to enable mainnet.
8 nodes · 400 Gb/s · $1,250,000.00

don't

Unlock the Power of Seamless Stablecoin Settlement!
Our revolutionary platform supercharges your treasury.
8nodes, 400Gb/s, $1250000

Banned words

These never appear in any surface that carries the Gradient name.

seamless revolutionary powerful unlock supercharge

Formatting

Case: sentence case. Never title case. Labels are uppercase mono.
Numbers: digits for all counts. Space before the unit: 8 nodes, 400 Gb/s.
Money: currency code after amount: $1,200,000.00 USD. Always two decimals.
Names: translate="no" on "Gradient" and organization names.
Banned: emoji, exclamation marks, title case, marketing superlatives.

SEO baseline

Every public page must include these meta tags. The product UI is an SPA behind auth — only the root, style guide, and brand assets are indexable. Private routes return no content to crawlers.

Required meta tags

<!-- SEO --> <title>Gradient — Card issuing and stablecoin settlement</title> <meta name="description" content="Card issuing and stablecoin settlement for financial institutions." /> <meta name="robots" content="index, follow" /> <link rel="canonical" href="https://gradientsystems.com/" /> <!-- Theme color: both modes --> <meta name="theme-color" content="#0A0A0A" media="(prefers-color-scheme: dark)" /> <meta name="theme-color" content="#FAFAF9" media="(prefers-color-scheme: light)" />

Open Graph

<meta property="og:type" content="website" /> <meta property="og:site_name" content="Gradient" /> <meta property="og:title" content="Gradient — Card issuing and stablecoin settlement" /> <meta property="og:description" content="Card issuing and stablecoin settlement for financial institutions." /> <meta property="og:url" content="https://gradientsystems.com/" />

Structured data (JSON-LD)

<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Organization", "name": "Gradient Systems", "url": "https://gradientsystems.com", "description": "Card issuing and stablecoin settlement for financial institutions." } </script>

Infrastructure

✓ robots.txt — allows public pages, blocks /api/ and /admin/
✓ sitemap.xml — lists indexable public URLs
✓ favicon.svg — inline SVG data URI, transparent background
✓ canonical URL — prevents duplicate content indexing
✓ theme-color — both dark and light with media queries

AI visibility (GEO)

Generative Engine Optimization ensures AI systems (ChatGPT, Claude, Perplexity, Gemini, Google AI Overviews) can discover, understand, and cite Gradient accurately. The goal is correct representation in AI-generated answers — not traffic.

llms.txt

The brand contract at /llms.txt is the machine-readable source of truth. It tells AI crawlers and agents what Gradient is, how to describe it, and what the design system rules are. Every surface generated by an agent must conform to this file.

# Gradient Systems — brand contract # Canonical: https://gradientsystems.com/brand # Machine reference: /brand/tokens.json # Human reference: /brand # rev 0.3 ## Scope Applies to every surface that carries the name: marketing site, product UI, docs, decks, print, and anything an agent generates on our behalf. ## Voice Second person, active. Sentence case everywhere except legal names. Name the action; never promise a feeling. No exclamation marks. Banned: "seamless", "revolutionary", "powerful", "unlock", "supercharge".

AI crawler access

robots.txt explicitly allows AI crawlers on public pages. Private product routes return no content to unauthenticated requests — the SPA shell is empty without auth.

✓ GPTBot — allowed on /, /llms.txt, /style-guide.html
✓ ChatGPT-User — allowed on public pages
✓ Claude-Web — allowed on public pages
✓ PerplexityBot — allowed on public pages
✓ Google-Extended — allowed on public pages
✓ Bingbot — allowed on public pages

Citability structure

Content should be structured so AI systems can extract and cite specific claims accurately.

First sentence: every page's first sentence must be a complete, citable description of the topic.
Headings: use descriptive headings that work as standalone answers. "Card issuing API" not "Our solution".
Facts over feelings: "settles in under 60 seconds" not "blazing fast settlement".
Structured data: JSON-LD Organization and SoftwareApplication schemas help AI understand entity type.
Consistent naming: always "Gradient" — never "Gradient Systems" in product copy, always "Gradient Systems" in legal/schema.
Single source: one canonical URL per concept. Never duplicate content across pages.

Agent standards

AI agents working on Gradient surfaces (code, content, design) must follow these rules. The agent contract chain is: AGENTS.md (repo) → tokens.json (design) → llms.txt (brand). If any conflict, the more specific file wins.

Contract chain

✓ AGENTS.md — repo-level agent instructions. Security rules, stack, terminology, ID prefixes.
✓ tokens.json — apps/web/src/brand/tokens.json. Every color, space, type, and radius value. Machine-parseable.
✓ llms.txt — /llms.txt. Brand voice, visual rules, imagery, content formatting. Human-readable for LLMs.
✓ design-system-integrity.md — PR review checklist. Critical violations block merge.

Agent rules

Read first: agents must read tokens.json and design-system-integrity.md before making UI changes.
On-system only: every hex must exist in tokens.json. Every spacing value on the 4px scale. Every radius 0 or 12.
Voice compliance: generated copy follows llms.txt — sentence case, no banned words, no emoji.
Security: never hardcode secrets. Never surface PAN, KYB, or PII. Redact sensitive fields in logs.
Terminology: bank clients see Gradient only. "Balance" not "ValueType". "Send" not "Transfer". See AGENTS.md mapping.
Four states: every new view must ship populated, empty, loading, and error states.
Test: typecheck, build, and run existing tests before committing.

How agents discover the contract

# tokens.json declares the contract URL { "agentContract": "https://gradientsystems.com/brand/llms.txt" } # index.html links to it <link rel="author" href="https://gradientsystems.com/llms.txt" /> # AI crawlers find it via robots.txt Allow: /llms.txt

Do and don't

Radius: 0 on every surface and control. 12px only on inset media.
Shadow: never. Depth is lightness.
Motion: steps(1, end) only. 120ms state changes. No ease, no cubic-bezier.
Signal: one per view. Never for status.
Case: sentence case everywhere. Labels are uppercase mono.
Money: always two decimals. Tabular nums. Martian Mono.
Empty cell: em dash (—), not blank.
States: every view ships populated, empty, loading, error.
Focus: 2px signal ring, 2px offset. Never removed.
Banned: gradients, emoji, title case, spinners, shadows, eased transitions, success toasts.
tokens.json — apps/web/src/brand/tokens.json — canonical design tokens
llms.txt — /llms.txt — brand contract for humans and agents
integrity checklist — design-system-integrity.md — PR review gates
CSS variables — apps/web/src/index.css — token-derived custom properties
AGENTS.md — AGENTS.md — repo-level agent instructions
robots.txt — /robots.txt — crawler access rules
sitemap.xml — /sitemap.xml — indexable URLs