design system
Dark neon, readable first.
Light is the accent: glows mark importance the way colour fills would on a light site. Every token below is a CSS custom property on :root, so a light theme can redefine them without touching components.
tokens
Colour
No pure white or pure black text pairs, to reduce halation for readers with astigmatism. Body text is #EDEDF2 on #07070B.
--bg
#07070B
Page background
--surface-1
#0E0E15
Cards, code blocks
1.0:1 on bg
--surface-2
#16161F
Raised elements, hover
1.1:1 on bg
--text
#EDEDF2
Primary text
17.2:1 on bg
--text-mute
#9A9AAF
Secondary text
7.3:1 on bg
--neon-violet
#8B5CF6
Primary accent
4.7:1 on bg
--neon-violet-soft
#B79CFF
Accent text
8.8:1 on bg
--neon-cyan
#22D3EE
Links, focus
11.1:1 on bg
--neon-lime
#A3E635
Success, 200 OK
13.3:1 on bg
--danger
#FB7185
Errors
7.5:1 on bg
--on-light
#07070B
Text on light fills
--gradient-brand
Display text, active tabs. Large sizes only.
accessibility
Contrast, checked on every build
The same list runs in npm run check:contrast before next build. Neon violet is only used for borders, dots and glows, so it is held to the 3:1 non-text rule.
| Sample | Pair | Use | Ratio |
|---|---|---|---|
| Aa 200 | text on bg | Primary text | 17.2:1 pass |
| Aa 200 | text-mute on bg | Secondary text, labels | 7.3:1 pass |
| Aa 200 | neon-cyan on bg | Links | 11.1:1 pass |
| Aa 200 | neon-lime on bg | Success, 200 OK, keys | 13.3:1 pass |
| Aa 200 | neon-violet-soft on bg | Accent labels, step numbers | 8.8:1 pass |
| Aa 200 | danger on bg | Errors | 7.5:1 pass |
| Aa 200 | text on surface-1 | Primary text | 16.5:1 pass |
| Aa 200 | text-mute on surface-1 | Secondary text, labels | 7.0:1 pass |
| Aa 200 | neon-cyan on surface-1 | Links | 10.6:1 pass |
| Aa 200 | neon-lime on surface-1 | Success, 200 OK, keys | 12.8:1 pass |
| Aa 200 | neon-violet-soft on surface-1 | Accent labels, step numbers | 8.4:1 pass |
| Aa 200 | danger on surface-1 | Errors | 7.1:1 pass |
| Aa 200 | text on surface-2 | Primary text | 15.4:1 pass |
| Aa 200 | text-mute on surface-2 | Secondary text, labels | 6.5:1 pass |
| Aa 200 | neon-cyan on surface-2 | Links | 9.9:1 pass |
| Aa 200 | neon-lime on surface-2 | Success, 200 OK, keys | 11.9:1 pass |
| Aa 200 | neon-violet-soft on surface-2 | Accent labels, step numbers | 7.9:1 pass |
| Aa 200 | danger on surface-2 | Errors | 6.7:1 pass |
| Aa 200 | prose on bg | Docs body copy | 13.0:1 pass |
| Aa 200 | on-light on text | Primary button label | 17.2:1 pass |
| Aa 200 | neon-violet on bg | Accent borders, dots, glows (non-text) | 4.7:1 pass |
| Aa 200 | neon-violet on surface-1 | Accent borders on cards (non-text) | 4.5:1 pass |
| Aa 200 | syntax.foreground on surface-1 | Code token | 16.5:1 pass |
| Aa 200 | syntax.comment on surface-1 | Code token | 7.0:1 pass |
| Aa 200 | syntax.keyword on surface-1 | Code token | 8.4:1 pass |
| Aa 200 | syntax.string on surface-1 | Code token | 12.8:1 pass |
| Aa 200 | syntax.func on surface-1 | Code token | 10.6:1 pass |
| Aa 200 | syntax.constant on surface-1 | Code token | 10.9:1 pass |
| Aa 200 | syntax.property on surface-1 | Code token | 15.4:1 pass |
| Aa 200 | syntax.type on surface-1 | Code token | 10.4:1 pass |
| Aa 200 | syntax.punctuation on surface-1 | Code token | 9.4:1 pass |
typography
Geist and Geist Mono
Display · Geist 600 · 40 → 96px · gradient with solid fallback
Speed of thought.
H2 · Geist 600 · 28 → 52px
Everything a production workload needs.
Body · Geist 400 · 16 → 18px
One API for eight models. First streamed token in about 120 milliseconds, and prices you can read without a sales call.
Code, labels, numbers · Geist Mono 400 · 13 → 15px
p50 182ms · $0.10 / 1M · 128K ctx
components
Components
Buttons · tab to see the focus ring (2px cyan + 4px glow)
Metric pills
Icon tiles
Spotlight card · move a mouse over it
The border follows the pointer.
A radial gradient in a masked ::before. One document-level pointer listener drives every card. Off on touch screens and with reduced motion.
// Every token colour passes 4.5:1 on --surface-1
const res = await fetch("https://api.synapse.example/v1/models", {
headers: { Authorization: `Bearer ${process.env.SYNAPSE_API_KEY}` },
});
const { data } = await res.json();
console.log(data.length, true, null);Example request: $ curl api.synapse.example/v1/chat \ -H "Authorization: Bearer $SYNAPSE_KEY" \ -d '{"model": "syn-flash", "stream": true, "messages": [{"role": "user", "content": "Explain latency in 8 words"}]}'. Response: HTTP 200, first token in 112 milliseconds, streamed text: Latency: the pause between asking and knowing.
motion
Motion
Aurora
Gradient drifts over 20s, transform only. Static with reduced motion.
Metric dots
Pulse every 2s. No pulse with reduced motion.
Section headings
300ms clip-path wipe on first view.
Buttons
Glow in over 200ms. Glows never flash or strobe.
Terminal
Types at 40 characters a second, then streams. Final state with reduced motion.
Spotlight
Follows mouse and pen only. Off on touch and with reduced motion.