One design language — near-black stage, warm bone ink, a single saturated green that carries every action. Tactile orbs for relays, editorial italic for emphasis, JetBrains Mono for the technical voice. Ships to web, iOS, Android, Mac Catalyst from the same tokens below.
#22e07a — carry every action. Voice states get their own chromatic identity so input, execution, and reply are unambiguous.
RadialGradient layers + shadow. Tap to toggle.
// CSS · the orb is fully declarative — no JS, no canvas. .orb { width: 82px; height: 82px; border-radius: 50%; background: radial-gradient(circle at 32% 28%, rgba(255,255,255,.05) 0%, transparent 24%), radial-gradient(circle at 50% 55%, #1a1c22 0%, #0b0c10 68%, #06060a 100%); box-shadow: inset 0 -7px 14px rgba(0,0,0,.65), inset 0 4px 8px rgba(255,255,255,.035), 0 3px 14px rgba(0,0,0,.55); /* on state swaps the body gradient + adds outer glow */ } .orb.on { background: radial-gradient(circle at 30% 26%, rgba(255,255,255,.75) 0%, transparent 22%), radial-gradient(circle at 50% 50%, #5cff9f 0%, #22e07a 42%, #118a4d 76%, #053b1f 100%); box-shadow: 0 0 38px 6px rgba(34,224,122,.55), 0 0 76px 16px rgba(34,224,122,.22), inset 0 -8px 16px rgba(0,0,0,.40), inset 0 6px 12px rgba(255,255,255,.32); }
// State machine — drive everything from data-state on the overlay root. const STATE_MIX = { idle: 0.0, listen: 0.33, think: 0.66, speak: 1.0 }; const STATE_VERB = { idle: 'Hold to speak', listen: 'Listening', think: 'Executing', speak: 'Speaking', }; // The shader uniform interpolates smoothly between these anchors. uniforms.uStateMix.value += (target - uniforms.uStateMix.value) * 0.06;
| Band | Drives | Effect |
|---|---|---|
| Bass · 0–8 bins | uBass | Vertex displacement + outer halo scale + base glow |
| Mid · 8–64 bins | uMid | Hue shift + rim saturation |
| Treble · 64–256 bins | uTreble | Chromatic aberration (R/G/B rim split) |
Color extensions on iOS, MaterialTheme tokens on Android. Change one here; change everywhere.
| Token | Value | Use |
|---|---|---|
| --r-sm | 8 px | Buttons, inputs, chips, toasts |
| --r-md | 12 px | Inline cards, thumbnails |
| --r-lg | 18 px | Panels, relay cells, modals |
| --r-xl | 24 px | Device card wrapper |
| --sp-1 … --sp-10 | 4 · 8 · 12 · 16 · 22 · 32 · 48 · 72 | 4pt base, skip 20 + 56 + 64 |
| shadow-card | 0 14px 40px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.03) | Glass panels + device card |
| shadow-orb-on | 0 0 38px 6px rgba(34,224,122,0.55), 0 0 76px 16px rgba(34,224,122,0.22) | Relay ON halo |
| blur-glass | backdrop-filter: blur(10px) | Every panel/card |
| Token | iOS (SwiftUI) | Android (Compose) |
|---|---|---|
| --bg | Color(hex: 0x0A0B0E) | Color(0xFF0A0B0E) |
| --on | Color.accent | MaterialTheme.colorScheme.primary |
| JetBrains Mono | Font.custom("JetBrainsMono-Medium", size:) | FontFamily(Font(R.font.jetbrains_mono_medium)) |
| Instrument Serif italic | Font.custom("InstrumentSerif-Italic", size:) | FontFamily(Font(R.font.instrument_serif_italic)) |
| Orb ON body | RadialGradient + .shadow(.green, 38) + .shadow(.green, 76) | Brush.radialGradient + Modifier.shadow(glow) |
cubic-bezier(0.22, 1, 0.36, 1). Voice-orb state transitions are interpolated across six-frames at 0.06 per frame — silky, not jumpy.
| Token | Value | Use |
|---|---|---|
| --ease-out | cubic-bezier(0.22, 1, 0.36, 1) | Default. Buttons, cards, state fades. |
| --ease-in-out | cubic-bezier(0.65, 0, 0.35, 1) | Modal in/out, overlay fade. |
| dur-tap | 100 ms | Button press (transform: scale(0.98)) |
| dur-state | 250–350 ms | Orb color shift, border glow |
| dur-modal | 450 ms | Voice overlay fade-in |
| loop-pulse | 1.1 s ease-in-out | Status dot during listen/speak |