# Build a living galaxy for your AI

I want you to give my AI a face: one full-screen scene where a **wireframe orb** floats in a **deep-space nebula**, surrounded by a **slowly turning network of glowing star clusters** joined by faint lines. The orb changes colour and motion with what my AI is doing (idle, waiting for the mic, listening, thinking, speaking, error), rides the waveform of real audio, and the whole galaxy brightens and swells while it talks.

This is a description of the experience and the engineering approach, not a code listing. Use your own judgment on the implementation, but match the look, the feel and the structure described here closely. The goal is something that feels alive, not a placeholder spinner.

**Work layer by layer.** Each layer below ends with something visible on screen and a check. Don't start a layer until the previous one renders correctly, and don't merge layers together. The point is that each piece can be debugged on its own.

---

## Layer 0: Interview me first (don't write code yet)

Ask me these in one round, grouped so I can answer quickly. If I skip one, use the default in parentheses.

1. **What's my web stack?** (Default: a small TypeScript project built with Vite. If I name a framework, wrap the engine in one component but keep the engine itself framework-free.)

2. **Should the engine be its own package** that any page can embed, or live inside my app? (Default: its own small package with a demo page. The engine should never depend on my backend.)

3. **How does my frontend learn my AI's state today:** a WebSocket, events, polling, or nothing yet? (Default: expose a plain "set state" function, and build a demo page with buttons for every state.)

4. **Do I have live audio** (mic input and/or spoken replies) for the orb to react to? (Default: build the real audio path *and* a synthetic fallback, chosen automatically.)

5. **What's my accent colour?** (Default: a calm teal.)

6. **Dark only, or dark and light?** (Default: both, with a proper switch.)

Then restate the plan in three or four lines and start Layer 1.

---

## The big picture (read once)

- **One renderer, one WebGL context, three passes per frame.** First the sky, a full-screen shader drawn straight to the screen. Then the star network, with its own perspective camera that drifts slowly. Then clear the depth buffer and draw the orb on top with its own camera. Many builds use two stacked canvases; don't. One context is cheaper and avoids compositing glitches.

- **Pin and bundle three.js from npm.** No CDN at runtime. The page must work offline.

- **Keep the maths pure and tested.** The state table, the easing, the audio-level calculation and the performance-mode decision should each be small functions with unit tests and no WebGL in them. The three.js code should only take numbers that were already computed and draw them.

- **All state lives in one table.** Every visual parameter for every state is defined in exactly one place. Each frame, every parameter moves part of the way toward the current state's target. Nothing snaps.

- **Easing must be frame-rate independent.** Use "cover this fraction of the remaining distance, adjusted for how much time passed" so the orb moves the same on a 30 fps laptop and a 120 fps monitor. Test that 60 small steps and 30 double-length steps land in the same place.

---

## Layer 1: The orb

- A **wireframe icosahedron**, radius 1, detail level about 24 (dense enough to read as a fine mesh). The camera has a 45° field of view and sits 4.2 units away, with the pixel ratio capped at 2.

- **Vertex displacement from 3-octave simplex noise.** Sample the noise at low, medium and high frequency, weighted about 0.5, 0.3 and 0.2. The high octave only switches on with audio. The key to a calm orb is that **the noise is multiplied by how much is going on**: a small base, plus a gentle "breath" (a slow sine at about 0.7 rad/s scaled to 0.04), plus audio level × amplitude × 0.55, plus bass × amplitude × 0.35. Add a small bass "push" wave and clamp the result to ±0.45. At rest, the orb should barely ripple.

- **Fragment: a thin fresnel edge.** The edges glow and the face you look straight at is nearly transparent. Tint parts that push outward slightly toward the secondary colour. Transparent, no depth writes.

- **A glow shell:** a larger, lower-detail icosahedron (radius 1.3), drawn back-faces only with additive blending and its own soft fresnel. It should read as a halo, **not a filled disc**. If it looks like a solid blob when the orb is loud, the falloff is backwards.

- **Three thin orbital rings** at radii around 1.55, 1.65 and 1.75, each tilted differently and turning at its own speed. They fade in only while "thinking".

- Slow rotation. Scale grows slightly with voice level and bass.

**Check:** a teal wireframe sphere floats and breathes gently on a dark background.

---

## Layer 2: The sky

A full-screen shader quad, drawn first with depth testing off.

- **Deep space base:** near-black navy, slightly darker toward the edges.

- **Three nebula layers** from fractal value noise (5 octaves, each rotated), drifting at a glacial pace. One teal-green, one purple, one blue, each faded in with a soft threshold so they read as wisps rather than fog.

- **An orb-coloured bloom** behind the centre of the screen: a few soft radial falloffs in the orb's current colour, with a tiny pale-cyan core. It brightens with the voice.

- **Two star layers:** a fine field of pinpoint stars on a hashed grid, and a sparser layer of larger soft stars. Each twinkles at its own speed.

- **Make the star grid square in screen space.** Pass the screen's aspect ratio into the shader, or the stars stretch into little dashes on tall or wide windows.

- A gentle vignette.

**Check:** a moody, slowly moving nebula with twinkling stars, and a soft glow behind the orb.

---

## Layer 3: The star network

This is what makes it feel like a living mind rather than a ball on a background. Give it its own perspective camera (60° field of view, about 42 units back) and draw it between the sky and the orb.

- **Eight clusters.** Scatter eight cluster centres on a shell 13–25 units from the middle. Put 6–8 nodes in each, spread within a few units of the centre. Give each cluster its own hue from a cool palette: teal, cyan, blue, purple, green-teal, indigo, blue-teal and violet.

- **A few stray nodes** fill out the rest (about 100 nodes in total), in a muted blue-grey.

- **Connections:** draw a faint line between any two nodes closer than about 10 units, coloured by its endpoints, at very low opacity (about 12%). That gives constellations inside each cluster and occasional bridges between them.

- **Nodes are soft glowing sprites:** bright core, half-bright at 30% of the radius, fading to nothing at the edge, with additive blending and size shrinking with distance. No texture file is needed; draw the falloff in the shader.

- **Dust:** a few hundred tiny, faint blue-grey points on a much larger shell.

- **Motion:** the whole network turns very slowly and tilts gently back and forth. The camera drifts a couple of units over minutes.

- **Seed the randomness.** The layout should be identical every time for the same seed, so screenshots and visual tests are reproducible, and a thinner performance-mode layout is the same galaxy with fewer points.

**Check:** a quiet constellation of coloured clusters slowly turning around and through the orb.

---

## Layer 4: States and voice

Six states, each a full set of targets (colours, opacity, fresnel sharpness, noise speed, displacement amplitude, glow, rotation speed, ring opacity):

- **Idle:** dim teal, slow breathing, faint glow.

- **Arming:** dim ember. The mic was requested but no audio has arrived yet. Showing this honestly matters: the user can tell "waiting for permission" from "listening".

- **Listening:** bright gold. The recording light, the only warm thing in a cool universe. High amplitude, strong glow.

- **Processing (thinking):** teal shifting to purple on a slow cycle, with a small pulse, the rings visible, and faster rotation.

- **Speaking:** bright teal, driven by the playback audio.

- **Error:** red, still, sharp edge.

**Audio:** tap the mic stream and the playback stream with an analyser (tap only; never change what's heard). Read the frequency bins each frame:

- **voice level** is the average of bins 10–60%

- **bass** is the first 6 bins

- **treble** is 65% and up

Boost the mic about ×2.4 with a small floor so the orb never looks dead between syllables. Smooth with a fast attack (0.45) and a slow decay (0.08) so it breathes instead of twitching. When no real audio is available, use a deterministic synthetic envelope that looks like speech.

**The galaxy answers the AI, not the user.** While speaking or thinking, feed the voice level into the network: lines brighten from about 12% toward 65% opacity, nodes grow and brighten, the network swells a few percent, rotation speeds up, the camera pushes in slightly and shakes almost imperceptibly, and the sky's nebula and bloom brighten. Listening affects only the orb.

**Check:** buttons for every state change the scene smoothly within about a second, the orb swells when you talk into the mic, and the galaxy lights up when a test tone plays.

---

## Layer 5: Themes, performance and accessibility

- **A real Dark/Light switch.** Make it a two-option control with icons (moon, sun) in a corner, not a button whose label flips. Remember the choice, and default to the operating system's preference. In light mode:

  - The sky becomes a pale dawn with the nebula as soft tints and no stars.

  - Additive blending disappears on a light background, so switch particles and the orb's glow to normal blending, and make nodes lighter and smaller so they don't read as ink blots.

- **Design tokens.** Every UI colour lives in one CSS file with dark and light sets. No colour values anywhere else; add a lint check if your project has CI.

- **Performance mode.** Measure frame rate. If it stays under 45 fps for 3 seconds, drop to pixel ratio 1, lower orb detail and fewer nodes and dust, and stay there (no flapping back and forth). Offer a manual on/off/auto control too.

- **Reduced motion.** Honour the operating system's reduce-motion setting by slowing a separate animation clock, not by multiplying time. Multiplying time makes every phase jump when the setting changes.

- **A text live region** that names the current state, so people using a screen reader know whether it's listening or thinking.

- **Clean teardown.** Stop the frame loop, disconnect observers and audio taps, dispose every geometry and material, and release the WebGL context. Browsers cap how many can be live at once.

**Check:** switch themes, force performance mode, turn on reduced motion, and destroy and recreate the scene a few times with no console errors.

---

## The feel, in one paragraph

Quiet until it matters. At rest it should look like a mind idling in deep space: a fine teal mesh barely breathing, clusters of cool light slowly turning, stars twinkling at the edge of attention. When you speak, it warms to gold and leans in. When it thinks, colour flows and rings appear. When it talks, the whole galaxy lights up with its voice. Every change eases; nothing snaps, flickers or shouts. If it ever looks busy when nothing is happening, turn it down.
