I want you to build me a stream visualizer: a fullscreen browser scene that reacts to a voice assistant through a small local server. The engine spec below is proven. I might be on macOS, Windows, or Linux: translate anything platform-specific (the launcher, paths, browser flags) to MY system. ## The scene: a deep circuit board, seen close Near-black with a green cast. Almost everything on it is dim. The light comes from a handful of lit traces and from a warm bloom behind the centre — never from a global brightness. **Depth is the thing that sells it, and it is the thing most attempts miss.** Draw the board in THREE layers, not one: - **far** — very faint, thin, slightly blurred, low contrast. Mostly long diagonal runs. This layer should be barely readable. - **mid** — the bulk of the board. Normal weight, moderate brightness. - **near** — a few bright, crisp traces with real saturation. These are the ones the eye follows. A single layer of uniform traces reads as wallpaper. Three layers read as a board with something underneath it. ### Routing Right angles and 45-degree diagonals only, never curves. Long diagonal runs crossing the frame are the dominant gesture. Parallel runs hold a consistent gap and turn together as a bundle. Many traces **terminate in a small pad or a short stub with a dot on the end** rather than running off-screen — a board where every line exits the frame looks like a pattern, not a circuit. ### What sits on it - **Chip packages, dozens of them, in varied sizes.** Dark bodies, thin lighter outline, and **pin rows drawn along the edges** — this is most of what makes a rectangle read as a chip. Some square, some long, some with pins on all four sides. A pin-1 dot in one corner. - **Outlined-only rectangles** as well as filled ones — some parts are just a thin bright outline with nothing inside. - **Vias as small domed circles with a highlight**, not flat dots. They should look slightly glossy, like solder. - **Comb / ladder elements**: short vertical segmented bars, like a connector footprint. - **Small passives** in quantity — tiny two-terminal rectangles, most of the count, unlabelled. - Silkscreen designators on the larger parts only, small and dim. ### Colour Predominantly green, but NOT monochrome. Scatter a minority of runs in amber / gold, a few in teal, and one or two in violet. The variety is what stops it looking machine-generated. Keep them all low-saturation except where lit. ## The centre readout A black inset screen with a thin bright border, holding the assistant's name in a monospaced face with wide letter spacing, glowing green: J . A . R . V . I . S . Around it: - a **warm gold bloom behind and beneath it**, bleeding onto the board and lighting the traces nearest it. This is the main light source in the frame. - **small tick marks along the top and bottom edges** of the panel. - **traces running INTO the panel from left and right**, connecting it to the board. It must not float. **It arrives by descrambling** — characters churn through random glyphs and resolve one at a time, like something being decrypted. The churn rate follows the live energy: fast while thinking and speaking, stalled at idle. ## The status bar Four corners, all small and dim: - **top-left**: the assistant name, and under it a status line such as `NEURAL LINK — CONNECTED` - **top-right**: the current state in caps, and a running clock with seconds - **bottom-left**: microphone status, e.g. `MIC — LISTENING FOR "JARVIS"` - **bottom-right**: the wake hint, e.g. `SAY "JARVIS" — OR TAP SPACE` These are what make it read as an instrument rather than a screensaver. ## It must be LIVE, not a canned animation Nothing on screen may run on a fixed timeline. Every moving thing is driven by the signal from the server, so what is on screen is what the assistant is doing. - Packet speed and density come from the live level, not a timer. - Brightness at the centre tracks the instantaneous voice level. - Waves ripple out on the **peaks** of speech — on the rate of change, not the level. That distinction is what makes it read as talking rather than pulsing. - If the signal stops, the board visibly settles: packets thin, glow decays, the readout stalls mid-churn. Do not fake any of it with a loop and a sine wave. The only synthetic signal allowed is in the mock harness, clearly labelled. ## The pulses mean something Light packets travel the traces, and colour carries information: - **cyan** inbound — arriving, heard, received - **magenta** the assistant's own internal work - **amber** background jobs, running unasked - **green** outbound — the answer leaving, in time with the voice **The mix must follow the state**, not be assigned once at random. Thinking is mostly internal work; listening is mostly inbound; idle is mostly the quiet background jobs. If a reader cannot tell the states apart with the labels covered, the colours are decoration and you have not done this part. ## The five states - **idle** at rest, like a screensaver. Motion around 20%. - **listening** clearly busier, reading as inbound attention. The visualizer never opens a mic itself; it is driven by the bus state. - **thinking** visibly working — more traffic, more of it internal. - **speaking** the centrepiece: brightness rides the live level. - **alert** an unmistakable emergency treatment — a red bleed across the board. ## Project layout Create the project at `~/voice-visualizer/`: - `index.html` — the entire scene. One self-contained file: canvas 2D and vanilla JS, no frameworks, no CDN, no build step, works offline. - `server.py` — a tiny Python 3 server, standard library only. - A double-click launcher for my OS. ## The server (the only bridge to the voice line) `server.py` binds 127.0.0.1:8777 and does two jobs: serve the page, and serve `/state` as JSON — `{"state": "idle|listening|thinking|speaking", "level": 0.0 to 1.0, "alert": true|false}` — by reading the voice line's bus files in `~/voice-line/` (paths as constants at the top): - `.voice_state` plain text - `.voice_waveform` JSON `{"ts": , "samples": [64 floats]}`; level is the mean absolute sample scaled 0–1, live only if ts is within ~2 seconds - `.voice_alert` — alert is true while this file exists READ-ONLY on the bus, always. **Stomp-tolerance rule:** a fresh waveform means speaking regardless of what the state file says, so a stray writer cannot mute the show mid-sentence. The page polls about 10×/second and smooths client-side (~90ms on level). If `/state` stops answering or goes stale, ease back to idle. Pass the raw samples through as well. ## Details that make it feel right - ONE continuously eased energy value drives everything (attack ~0.5s, release ~1s). Every state change rides that curve. A hard step reads as a jump cut. - Glow energy and motion energy are separate axes. Speaking wants full glow at a calm cruise; thinking wants full speed. - **Bake the static board once** — substrate, all three trace layers, packages, silkscreen — to an offscreen canvas and blit it per frame. Animate only the moving layer. Keep the glow on its own blurred layer, composited additively. Cap devicePixelRatio around 1.25. This is the difference between 60fps and a slideshow. - A short boot intro; any key skips it. - An FPS meter on the F key. ## The mock harness (never fake data on the real bus) - `server.py --mock` on port 8778 walks a scripted loop through every state. - `?mockstate=speaking` simulates a state locally without touching `/state`. - `?shot=speaking&t=1200` renders deterministically, advances t milliseconds and freezes — your self-verification hook. Screenshot it headless and EYEBALL your own frames. Do not guess at visuals. Gotcha: headless Chrome fires a late resize that can clear the canvas after a synchronous render, so re-render on resize or your screenshots come back black. ## Verify before you call it done 1. **Screenshot the board at rest and zoom in.** If the traces are all one weight, if nothing has pin rows, if the vias are flat dots, or if every line runs off the edge — go back. That is the difference between this and a wallpaper. 2. Run the mock loop: all five states, in and out, no jump cuts. 3. The speaking centrepiece rides the level and is clearly gone at idle. 4. Kill the mock server mid-speaking: the scene must ease home within a couple of seconds. If it keeps dancing, something is on a timer that should be on the signal. 5. Cover the status bar and watch. If you cannot tell thinking from listening, the colour mapping is decoration — fix it. 6. The centre readout resolves faster under load and stalls at idle. 7. 60fps fullscreen (F meter). 8. After any server edit, confirm you are not talking to a stale instance — find the PID with lsof on the port and kill exactly that, never a pattern. Then show me the double-click launcher.