Color
Our color system uses OKLCH — a perceptually uniform color space that ensures consistent lightness across hues. Colors are defined as CSS custom properties and mapped to Tailwind utility classes via shadcn's token layer.
Brand Palette
Three signature hues embody our duality. Green for Life, violet/pink for Virtual, and blue as the Bridge between them. These appear in our gradient title, wavy background, and accent moments.
Semantic Tokens (Dark Mode)
Semantic tokens are the bridge between brand colors and component usage. They are defined as CSS custom properties and consumed through shadcn's Tailwind mapping. Every component references semantics, never raw values.
| Token | Preview | OKLCH Value | Tailwind Class | Usage |
|---|---|---|---|---|
| --background | oklch(0.21 0.04 266) | bg-background | App canvas, main content area | |
| --foreground | oklch(0.93 0.013 256) | text-foreground | Primary body text | |
| --card | oklch(0.28 0.04 260) | bg-card | Card surfaces (used at /10 opacity) | |
| --primary | oklch(0.68 0.158 277) | bg-primary | Primary buttons, active states, links | |
| --secondary | oklch(0.34 0.033 261) | bg-secondary | Secondary buttons, less prominent actions | |
| --muted | oklch(0.24 0.038 260) | bg-muted | Subdued backgrounds, disabled fields | |
| --muted-foreground | oklch(0.71 0.019 261) | text-muted-foreground | Secondary text, placeholders, captions | |
| --accent | oklch(0.37 0.031 260) | bg-accent | Hover highlights, sidebar active bg | |
| --destructive | oklch(0.64 0.208 25) | bg-destructive | Delete actions, error states | |
| --border | oklch(0.45 0.026 257) | border-border | Card edges, dividers, input borders | |
| --ring | oklch(0.68 0.158 277) | ring-ring | Focus rings on interactive elements | |
| --sidebar | oklch(0.21 0.04 266) | bg-sidebar | Navigation sidebar, title bar, status bar |
Semantic Tokens (Light Mode)
Light mode tokens are applied via the
html.light class. They maintain the same semantic
names but shift values for readability on light backgrounds.
Primary colors become darker; surfaces become lighter.
| Token | Preview | OKLCH Value | Tailwind Class | Usage |
|---|---|---|---|---|
| --background | oklch(0.97 0.005 265) | bg-background | App canvas, main content area | |
| --foreground | oklch(0.18 0.04 265) | text-foreground | Primary body text | |
| --card | oklch(0.95 0.005 265) | bg-card | Card surfaces | |
| --primary | oklch(0.52 0.20 277) | bg-primary | Primary buttons, active states, links | |
| --secondary | oklch(0.90 0.010 260) | bg-secondary | Secondary buttons, less prominent actions | |
| --muted | oklch(0.94 0.008 260) | bg-muted | Subdued backgrounds, disabled fields | |
| --muted-foreground | oklch(0.40 0.04 257) | text-muted-foreground | Secondary text, placeholders, captions | |
| --accent | oklch(0.92 0.008 260) | bg-accent | Hover highlights, sidebar active bg | |
| --destructive | oklch(0.55 0.22 25) | bg-destructive | Delete actions, error states | |
| --border | oklch(0.78 0.015 257) | border-border | Card edges, dividers, input borders | |
| --ring | oklch(0.52 0.20 277) | ring-ring | Focus rings on interactive elements | |
| --sidebar | oklch(0.94 0.018 272) | bg-sidebar | Navigation sidebar, title bar, status bar |
Status Colors
Status colors are consistent across modes — they're designed to be
accessible on both dark and light backgrounds. In light mode,
darker variants (--success, --warning)
are used for better contrast against white surfaces.
| Status | Dark | Dark OKLCH | Light | Light OKLCH |
|---|---|---|---|---|
| Success | oklch(0.723 0.219 150) | oklch(0.45 0.18 150) | ||
| Error | oklch(0.64 0.208 25) | oklch(0.55 0.22 25) | ||
| Warning | oklch(0.828 0.189 84) | oklch(0.65 0.18 84) | ||
| Info | oklch(0.68 0.158 277) | oklch(0.52 0.20 277) |
Surface Scale
Surfaces layer from darkest (canvas) to lightest (elevated card) in dark mode, and from lightest to elevated in light mode. Each step is subtle — about 7% lightness change — creating depth without hard edges.
| Surface | Dark | Dark OKLCH | Light | Light OKLCH | Usage |
|---|---|---|---|---|---|
| Surface 0 | oklch(0.13 0.04 265) | oklch(0.97 0.005 265) | Canvas | ||
| Surface 1 | oklch(0.21 0.04 266) | oklch(0.94 0.018 272) | Background / Sidebar | ||
| Surface 2 | oklch(0.28 0.04 260) | oklch(0.90 0.010 260) | Card | ||
| Surface 3 | oklch(0.34 0.03 261) | oklch(0.86 0.012 261) | Elevated |
Typography
Our type system uses TT Rounds Neue — a geometric rounded sans-serif by TypeType Foundry — as the primary typeface. It pairs with a monospaced face for technical precision and a serif for rare prose moments. The system supports theme-level font overrides.
Montserrat → Corbel → Nunito → system-ui → sans-serif.
Licensing: TT Rounds Neue is free for personal/trial use. Commercial deployment requires a license from TypeType Foundry.
--font-sans is the single source of truth.
The default VirtualLife theme uses TT Rounds Neue; other themes
fall back to Montserrat.
Cascadia Code → Fira Code → ui-monospace → monospace.
Georgia → Times New Roman → serif.
Type Scale
Base size is 0.825rem (~13.2px) — slightly smaller
than the browser default to increase information density without
sacrificing readability. The scale uses
--text-scaling for user preferences and Tailwind's
built-in classes.
Prose Headings (Rich Content)
| Element | Size | Weight | Margin |
|---|---|---|---|
| h1 | 1.5em | 700 (Bold) | 0.67em top/bottom |
| h2 | 1.3em | 600 (Semibold) | 0.83em |
| h3 | 1.17em | 600 | 1em |
| h4 | 1em | 600 | 1.33em |
| p | inherit | 400 | 0.5em |
Wordmark
The wordmark is a fixed asset, not a style you compose per product. Two faces carry one continuous colour sweep: a humanist Virtual against a geometric Life, washed by a slow tide of pink into green.
Anatomy
Three colours, each in exactly one contiguous zone, read left to right: pink, then foam, then green. Green lands on “Life” and never appears on the left. The zones drift, but they cannot trade places — a colour that reappears on the far side of the word is the failure mode this construction exists to prevent.
The gradient and the background clip live on the parent element. Both words paint through one background box, so the tide runs continuously across the join. Moving the gradient onto the two children is the single mistake that breaks the mark: it restarts the sweep at “Life” and you get two marks sitting next to each other.
Type pairing
“Virtual” is Lato, a humanist face; “Life” is TT Rounds Neue, geometric and rounded. The name's own contrast, made visible. This is the only place in the system where two faces meet inside one line — do not borrow the pairing for UI text.
This is worth stating because the previous pairing did need one. “Virtual” was set in Corbel, whose cap height is only 65 against TT Rounds Neue's 71 — a 9% shortfall that made the first word read a size smaller than the second, so
.vl-wordmark-virtual carried a
font-size: 1.07em correction. That bump belonged to
Corbel, not to the lockup: applied to Lato it would push
“Virtual” 7% too large. If the humanist face is ever
swapped again, measure the two cap heights before assuming any
value.
Motion
omTide walks the whole boundary across the word and
back over 22 seconds. It is ambient, not a
loading state: nothing about it should read as progress or invite
waiting. The two stop positions are registered custom properties
so they interpolate; their initial values are the resting frame,
which means the mark is complete and correct with no animation
running at all.
Under prefers-reduced-motion the tide
keeps running, switched to
omTideCalm: about a third of the travel, at 40s
instead of 22s. It is not stopped, because nothing in this
animation moves — the letterforms are fixed and
only the colour beneath them shifts. The vestibular trigger the
setting exists to defuse is displacement through space, not hue,
and this file already treats looping animations that way
elsewhere: calm them, don't kill them. Surfaces that genuinely
need stillness opt in with
.vl-wordmark-static, which stops it outright.
Usage
<span class="vl-wordmark"> <span class="vl-wordmark-virtual">Virtual</span><span class="vl-wordmark-life">Life</span> </span>
Size it by setting font-size on
.vl-wordmark; everything else scales from there. It
is one line by design and never wraps. Add
.vl-wordmark-static for screenshots, email and print.
--gradient-wordmark, which is a different recipe from
--gradient-brand (the 120° blue → pink
→ green used for the primary CTA aura and the canvas). They
are not interchangeable. The wordmark is the one place the pink
and green appear as separated zones rather than a blend.
Spacing
Spacing follows Tailwind's 4px base grid. We favor a constrained set of values for consistency: the 4-8-12-16-24-32-48 scale handles virtually all layout needs.
0.25rem
0.5rem
0.75rem
1rem
1.5rem
2rem
3rem
Spacing Usage Guidelines
| Context | Tailwind | Value | When to Use |
|---|---|---|---|
| Inline element gap | gap-2 | 8px | Icon + text, badge contents, tight groups |
| Form field gap | gap-2 | 8px | Label to input, switch to label |
| Card internal padding | p-4 | 16px | Standard card content padding |
| Section vertical gap | gap-4 | 16px | Between cards, between form groups |
| Page padding | px-4 pt-4 | 16px | Section wrapper padding |
| Grid image gap | gap-4 | 16px | Gallery grid, catalog grid |
| Dialog padding | p-6 | 24px | Modal dialogs and popovers |
| Toolbar compact | px-3 py-1 | 12px × 4px | Status bar, compact toolbar rows |
Border Radius
Rounded corners are fundamental to VirtualLife's soft,
approachable feel. The base radius (--radius: 0.625rem) ripples through the system via calc() expressions.
Themes can override the base to shift the entire system's
roundness.
Radius Assignment
| Element | Radius | Tailwind |
|---|---|---|
| Buttons (default) | md | rounded-md |
| Input fields | md | rounded-md |
| Cards | xl | rounded-xl |
| Dialogs | lg | rounded-lg |
| Badges / Pills | full | rounded-full |
| Images (generated) | lg or xl | rounded-lg |
| Main content inset | 2xl (top-left only) | border-top-left-radius: 16px |
| Radial menu buttons | full | rounded-full |
| Status dots | full | rounded-full |
| Tab list | lg | rounded-lg |
| Scrollbar thumb | 4px | Custom CSS |
Elevation
Elevation is communicated through shadow depth and backdrop blur. In dark mode, shadows are more subtle and surface lightness shifts do most of the layering work. The glass effect replaces heavy drop shadows for most surfaces.
| Level | Used For | Shadow |
|---|---|---|
| 0 | Flat surfaces, inline elements | none |
| 1 (sm) | Buttons, cards at rest, inputs | 0 1px 2px oklch(0 0 0/0.15) |
| 2 (md) | Toasts, popovers, dropdowns | 0 4px 12px oklch(0 0 0/0.15) |
| 3 (lg) | Image tiles on hover, dialogs | 0 15px 40px -10px oklch(0 0 0/0.50) |
| 4 (xl) | Image tile active hover, fullscreen overlay | Multi-layer composite |
Glass & Transparency
Glass morphism is VirtualLife's signature surface treatment. Translucent backgrounds combined with backdrop blur create depth and visual interest while letting the wavy background pattern breathe through the interface.
Glass Light
The standard card surface. Most of the background shows through, with just enough blur to maintain text legibility.
bg-card/10 backdrop-blur-smGlass Medium
For interactive panels, filter bars, overlays on images. More opacity means more contrast for denser content.
bg-card/40 backdrop-blur-mdGlass Heavy
For sticky headers, group labels, badges on images. Nearly opaque but still part of the glass family.
bg-card/80 backdrop-blur-smGlass Recipes
| Recipe | Opacity | Blur | Border | Used For |
|---|---|---|---|---|
| Glass Light | bg-card/10 | backdrop-blur-sm | border-border/50 | Standard cards (VLCard) |
| Glass Medium | bg-card/40 | backdrop-blur-md | border-border | Image overlays, filter panels, edit panels |
| Glass Heavy | bg-card/80 | backdrop-blur-sm | border-border/50 | Sticky headers, group labels |
| Glass Scrim | bg-black/50 | none | none | Image status overlays (loading/error) |
| Glass Overlay | bg-black/80 | none | none | Dialog backdrop, fullscreen overlay |
backdrop-filter: blur() is GPU-accelerated in
Chromium-based renderers but can cause compositing overhead
on dense grids. Use backdrop-blur-sm (4px blur) as
the default; reserve backdrop-blur-md (12px blur) for
surfaces that overlay rich content like images.
The app canvas
The wave fan is the system default behind every content area, not
a decoration you opt into. Three stacked layers: the eleven-path
wave fan, two low-opacity brand auras bleeding in from opposite
corners, and Surface 0 underneath. It is available as one token —
--canvas-app — and that is how content areas get it.
The wave pattern is VirtualLife's most distinctive visual element. It represents the flowing connection between human and AI creativity — organic curves rendered with digital precision.
Specimens
Tokens
| Token | Contents | Use |
|---|---|---|
--canvas-waves |
The wave fan alone — 11 cubic-Bézier paths, 1422 × 800 viewBox, baked as a data-URI SVG | Overlaying a background you already have |
--canvas-auras |
Two radial gradients: violet low-left, green high-right, 4–5% alpha | Colour without pattern |
--canvas-app |
Waves over auras over --surface-0 |
The default. Every content area |
--canvas-app-loud |
Deeper opacity ramp, stronger auras | Specimen cards, marketing shots — never product UI |
Rules
background: var(--canvas-app).
The app bar, the destination rail and the navigator panel stay
solid --surface-1 — that contrast is what makes the
content frame read as the place the work happens. No photography,
no heavy texture, and never two canvases in one screen.
Theming
VirtualLife supports swappable themes that override the semantic token layer without changing component structure. Each theme is a JSON file with light/dark CSS variable overrides plus optional font and radius changes.
Theme Architecture
{
"name": "Clean Slate",
"cssVars": {
"theme": { // Shared (fonts, radius)
"--font-sans": "TT Rounds Neue, sans-serif",
"--radius": "0.5rem"
},
"light": { // Light mode overrides
"--background": "oklch(0.98 0.003 248)",
"--primary": "oklch(0.59 0.204 277)"
},
"dark": { // Dark mode overrides
"--background": "oklch(0.21 0.04 266)",
"--primary": "oklch(0.68 0.158 277)"
}
}
}
Available Themes
bg-primary. This guarantees every theme works
with every component.
Light & Dark Mode
Dark mode is the primary design surface — optimized for immersive
creative work where generated images pop against deep backgrounds.
Light mode is a first-class alternative, not an afterthought. The
.dark class on <html> toggles the
active variable set. Each theme provides both dictionaries.