Spaces:
Running
Running
File size: 9,430 Bytes
1f28297 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | # 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 `Β·WebSocket` transport 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 `1px` borders 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** (`--text` fill, `--bg` text) β 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`) are `36px`, 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.booting` disables all transitions until the first
frame commits (stripped after one rAF in `main.js`). Anything new that would
otherwise animate-in on load must respect this.
- Honor `prefers-reduced-motion` for 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-motion` respected.
- Tap targets β₯ `44px`; `touch-action: manipulation` on anything tappable.
---
## Before you ship β the mirror check
1. Is every saturated color either on the orb or a word-sized role echo? If a fill or
border is colored, remove that accessory.
2. Is mono used only for machine/system text, and Inter for everything human?
3. Does any new state have both a `--glow` and (if it appears in chat) a `--voice-*`
alias?
4. Are separations hairlines + spacing, not boxes and heavy shadows?
5. Did you add motion? Is it quieter than the orb and reduced-motion-safe?
6. Remove one accessory. The minimal look survives on precision, not addition.
|