Spaces:
Running
Design language
The reference for keeping this app visually coherent as it grows. Read it before
touching style.css, index.html, or any DOM-building code in main.js. Every
rule here is already live in the codebase β this file explains the why so changes
extend the system instead of drifting from it.
The thesis: color belongs to the voice
This is a voice app. The one thing in the room that should have color is the thing that is talking. So:
- The orb carries saturated color. It glows, and the glow's hue changes with conversational state.
- Everything else is monochrome β a precise cool-grey dark canvas. Surfaces, borders, buttons, panels, the transcript: all greyscale.
- The only exceptions are tiny role echoes (a one-word mono label, a small icon) that borrow the orb's state hue so the transcript reads in the same color language the orb speaks. They are accents the size of a word, never fills.
- Brand logos keep their own color. The Hugging Face mark (
#ffd21e) and the Cerebras mark (#f15a29) render in their brand colors in the identity credits and the about panel β a deliberate, owner-approved exception. It applies to those two logos only; do not generalize it to other chrome.
If you find yourself adding a tinted background, a colored border, or a bright button anywhere outside the orb, stop β that color almost certainly belongs to the orb instead, or shouldn't exist.
Color tokens
All defined in :root in style.css. Use the variables, never raw hex in rules.
Canvas (the monochrome world)
| Token | Value | Use |
|---|---|---|
--bg |
#0a0b10 |
Page background (a cool near-black) |
--bg-elev |
#13151c |
Raised surfaces: bubbles, panels, icon buttons |
--bg-elev-2 |
#1b1e29 |
Surfaces on surfaces: history bodies, inputs |
--border |
rgba(255,255,255,.08) |
Default hairline |
--border-strong |
rgba(255,255,255,.16) |
Emphasised hairline |
--text |
#f5f6fa |
Primary text; also the primary button fill |
--text-dim |
rgba(245,246,250,.65) |
Secondary text |
--text-faint |
rgba(245,246,250,.42) |
Captions, labels, footer |
Voice (the only saturated hues)
These are the orb's state colors. They appear on the orb, and as small role echoes in the transcript β nowhere else.
| Token | Value | Meaning |
|---|---|---|
--accent / --speaking |
#8b7dff violet |
Assistant speaking |
--accent-2 / --listening |
#22d3ee cyan |
You / listening |
--processing |
#f59e0b amber |
Thinking / tool call |
--error |
#ff6a75 |
Error |
--success |
#34d399 |
Ready / connected |
Role echoes (semantic aliases β use these in chat code)
| Token | Maps to | Where it shows |
|---|---|---|
--voice-user |
cyan | YOU label + user bubble accents |
--voice-assistant |
violet | ASSISTANT label + assistant accents |
--voice-tool |
amber | TOOL CALL label, wrench icon |
Why this mapping: it mirrors the orb exactly β when you speak the orb is cyan
(state-listening), when the assistant speaks it's violet (state-ai-speaking),
when it's working it's amber (state-processing). The transcript is a quiet replay
of the orb's color story.
Orb state β glow (.circle.state-* β --glow)
| State | Glow |
|---|---|
signed-out |
violet #8b7dff |
authenticated / ready |
green #34d399 |
connecting / connected / starting |
yellow #facc15 |
listening / user-speaking |
cyan (--listening) |
processing |
amber (--processing) |
ai-speaking |
violet (--speaking) |
error |
red (--error) |
Adding a new state? Give it a --glow, and if it surfaces in the transcript, add a
matching --voice-* alias rather than a one-off color.
Typography
Two faces, two jobs. Never reach for a third.
- Inter β body and UI. Wordmark, buttons, inputs, panel titles, prose, history message bodies. The workhorse; it should feel neutral and get out of the way.
- Geist Mono (
--font-mono) β the machine voice. Reserved for text the system emits or identifiers it reports, never for human prose.
When mono is correct
Mono signals "this is the machine talking or naming itself." Use it for:
- the orb's status caption (
.circle-caption) - role eyebrows (
YOU/ASSISTANT/TOOL CALL) - tool-call names and argument JSON
- the
Β·WebSockettransport tag, bitrate readouts, connection identifiers - the empty-state label
Mono text is set uppercase with letter-spacing: ~0.1β0.14em and weight 500, so it
reads as a typed status line, not a headline. Body copy, button labels, and
explanatory small text stay Inter β putting prose in mono breaks the metaphor.
The font is loaded in index.html; the stack falls back to system mono gracefully if
the CDN is blocked.
Layout
- One continuous canvas. No dividers under the topbar or above the footer, no
panel chrome competing with content. The topbar and footer float over the stage.
Keep it that way β a new section earns a hairline (
--border) only if it genuinely needs separating. - The orb is the hero and the center of gravity. It sits dead-center on the stage. Controls flank it (mic / stop), captions sit beneath. Don't crowd it.
- Hairlines, not boxes. Separation comes from
1pxborders at 8β16% white and from spacing, not from heavy fills or shadows. Shadows are soft and low (0 4px 18px rgba(0,0,0,.32)), used only to lift floating elements (bubbles, panels, modal). - Radii:
--radius-sm: 8px(buttons, inputs, chips),--radius-md: 14px(bubbles, message bodies, modal),--radius-lg: 22px(reserved). Pick by element size; don't invent new values. - Two reading surfaces for the transcript: ephemeral bubbles top-right (desktop only) that log and fade, and a slide-in history panel for review. On phones the bubble stream is dropped and the panel goes full-screen β the panel is the single source of truth there.
Components
- Buttons. Default (
.btn) is a neutral elevated surface. The primary button is near-white on dark (--textfill,--bgtext) β the highest-contrast thing on the page that isn't the orb. There is no colored button; emphasis comes from contrast, not hue. - Icon buttons (
.icon-btn) are36px, elevated surface, dim icon that brightens on hover. Side controls (.side-btn) are circular, collapse to zero size until the session is live (by width on desktop, by height in the mobile column). - Chat bubbles & history messages share one neutral surface. They are
distinguished by side (you = left, assistant = right) plus the mono role
label in the role-echo hue β not by tinted fills. Tool entries use the wrench icon
- mono + amber, on the same neutral surface.
- Badge (new-message dot) is monochrome white β a signal, not a color accent.
- Focus is visible and neutral: inputs focus to
--text-dim; the orb uses a--glow-colored outline (it's the orb, so color is allowed).
Motion
- The orb is audio-reactive, not timer-driven. Mic RMS (
--audio-level) and the assistant output level (--ai-audio-level) drive scale/opacity at display rate, so every syllable moves it. This is the signature animation β keep new motion subordinate to it. - Quiet by default. Breathing/glow throbs are slow (1.4β2.4s) and low-contrast. Resist adding scattered micro-animations; an orchestrated moment beats many small ones, and excess motion reads as AI-generated.
- First paint is frozen.
body.bootingdisables all transitions until the first frame commits (stripped after one rAF inmain.js). Anything new that would otherwise animate-in on load must respect this. - Honor
prefers-reduced-motionfor any motion you add.
Writing / copy voice
- Sentence case, plain verbs, no filler. Tuned and quiet β match the minimal canvas.
- Name things by what the user controls, not by the system's internals. A button says exactly what it does, and keeps the same word through the flow.
- Empty states invite action ("Tap the orb and start talking"), they don't just set a mood.
- Errors state what happened and how to recover, in the interface's voice β they don't apologize and are never vague.
- Mono labels are terse identifiers (
YOU,TOOL CALL); prose stays in Inter.
Responsive floor (non-negotiable)
Every change ships meeting these:
- Works down to a
360px-wide phone. The@media (max-width: 600px)block already handles the phone layout β extend it, don't fight it. - Visible keyboard focus on every interactive element.
prefers-reduced-motionrespected.- Tap targets β₯
44px;touch-action: manipulationon anything tappable.
Before you ship β the mirror check
- Is every saturated color either on the orb or a word-sized role echo? If a fill or border is colored, remove that accessory.
- Is mono used only for machine/system text, and Inter for everything human?
- Does any new state have both a
--glowand (if it appears in chat) a--voice-*alias? - Are separations hairlines + spacing, not boxes and heavy shadows?
- Did you add motion? Is it quieter than the orb and reduced-motion-safe?
- Remove one accessory. The minimal look survives on precision, not addition.