Prompts you paste into Claude Code. Your agent builds the thing.
auto-detected from your browser. the copy buttons grab the right version for your OS. the windows build includes hardening notes contributed by the community.
These are the actual builds I run every day, written up as prompts your own agent can follow. Free, no email, no catch.
prompts licensed CC BY-NC-SA 4.0: free to use and share with credit. not for resale.
Paste this whole prompt into Claude Code. Your agent builds a voice conversation system: hold a key, talk to your agent out loud, release, and it answers through your speakers in a real voice. Type instead whenever you want, same conversation. Everything runs local, $0 on a Claude subscription, and your agent will offer you a choice: the free local voice, or any voice you like from ElevenLabs if you want the premium sound.
the windows edition: the same proven build, written native for windows and hardened with the real fixes from a full windows build contributed by joatsaint.
$ serving: the standard build (macos / linux)
$ serving: the windows edition, native and community-hardened
I want you to build me a voice conversation system for talking to you out loud at my desk. Build it exactly to this spec. It is a proven design, so where this spec is specific, follow it precisely. The gotchas listed at the bottom are hard-won lessons. Read them before writing any code.
I might be on macOS, Windows, or Linux. This spec was proven on a Mac, so a few notes are macOS-flavored: translate anything platform-specific (permissions, launchers, audio plumbing) to MY system and keep the architecture identical.
## What it does
I hold a key anywhere in my OS, my mic opens, I talk. I release the key, and what I said gets transcribed locally, sent to a live Claude Code session (you, with all your tools and my project context), and your reply is spoken through my speakers in a natural voice, sentence by sentence as you generate it. First audio should land about 1 to 2 seconds after I release the key on warm turns.
## Architecture (half-duplex)
```
mic -> ears (sounddevice capture)
-> local whisper server on port 2022 (/v1/audio/transcriptions)
-> warm Claude Agent SDK session (streaming, one client per session)
-> mouth (sentence-chunked TTS via local Kokoro on port 8880, cancellable playback)
-> speakers
```
Half-duplex means the mic is gated while the mouth is speaking so the system never hears itself. Do not attempt barge-in on open speakers.
## Project layout
Create the project at `~/voice-line/` with a uv-managed Python 3.12 environment:
- `main.py` entry point, the turn loop, hold-to-talk wiring
- `ears.py` mic capture and transcription
- `brain.py` the warm Claude Agent SDK session
- `mouth.py` TTS queue and playback
- `ptt.py` the global hold-to-talk key listener
- `ducking.py` optional Spotify ducking
- `signals.py` the visualizer signal bus (spec below)
- `run-voice-line.sh` launcher
Python packages: sounddevice, webrtcvad, numpy, pynput, claude-agent-sdk, httpx. Pin setuptools below 81 because webrtcvad needs pkg_resources. You also need the ffmpeg binary available.
## Services this rides on
Two local servers must exist. Check for them and set them up if missing:
1. A whisper.cpp server on port 2022 exposing the OpenAI-style route `/v1/audio/transcriptions` with a small English model and whatever acceleration my machine offers (Metal on a Mac, CUDA on Nvidia, plain CPU works). Note: the `/inference` route is not the one you want, it 404s on the OpenAI client path.
2. A Kokoro TTS server on port 8880 exposing `/v1/audio/speech` (kokoro-fastapi works well). Request `response_format: pcm`, which returns raw int16 at 24kHz mono. Pick a voice you like, `bm_lewis` is a good default.
## Hold-to-talk (the default mode)
- Global key listener via pynput. Holding the chosen key opens the mic, releasing closes it with a 0.18 second tail so my last word survives.
- Taps shorter than 250ms are ignored.
- CRITICAL: the OS fires key-repeat on_press events continuously while a key is held. Filter with a held-state flag or every repeat becomes a fresh press. This bug will kill every reply before it speaks if you skip it.
- Pressing the key while the assistant is talking interrupts playback immediately. This makes it speaker-safe with no headphones.
- The mic is fully closed between holds so room audio and music never leak into the transcriber.
- On macOS this needs Input Monitoring permission for the hosting terminal, and mic permission comes from launching in Terminal, not launchd; other systems have their own input and mic permission hoops, clear them the same way. Whatever the OS, never run this as a background daemon. Nobody wants a 24/7 open mic.
Also build a legacy open-mic mode behind an `--open-mic` flag: webrtcvad endpointing, discard any utterance with less than 240ms of actual speech, and strip bracketed non-speech markers like [SIGHS] and [BLANK_AUDIO] that whisper emits.
## The brain
- One warm ClaudeSDKClient per voice session, created at launch. Warm turns are fast; the first turn pays a prompt-cache toll of several seconds, so hide it behind a spoken greeting by firing a warmup query at startup.
- Set the session cwd to the project folder whose CLAUDE.md defines your identity, so the voice session is the same assistant as my terminal sessions.
- Use the system prompt preset with an appended spoken-discipline block: short conversational sentences, no markdown, no code blocks, no lists read aloud, write for the ear. TTS performs punctuation, so dull text is dull audio. After the first sentence, ship sentences in two-sentence breaths, since lone short sentences sound flat.
- Stream partial messages. Chunk into sentences and hand each completed sentence to the mouth immediately.
- CRITICAL: flush the sentence buffer when a content block stops. If you only flush on sentence-ending punctuation, pre-tool filler like "On it, checking now" sits silent through the whole tool run and then plays glued to the answer.
- Quit phrases end the session: "goodbye", "end voice mode", "hang up". Ctrl-C also works.
## The mouth
- A queue of sentences, synthesized one at a time, played back to back, cancellable mid-stream (interrupt = clear queue + stop playback now).
- **The voice is my choice. Before you build the mouth, ask me:** free local voice (Kokoro, the default, $0 forever), or a higher quality ElevenLabs voice using my own ElevenLabs API key. If I pick ElevenLabs, walk me through creating the key, point me at their voice library so I can pick ANY voice I like (the voice id is one setting in the code), store the key in an ELEVENLABS_API_KEY env var (never hardcode it), and keep Kokoro wired in as the automatic fallback so if ElevenLabs is down or the key runs out of credits the voice degrades instead of going mute.
- Kokoro path: POST the sentence, get raw PCM int16 24kHz mono, play through sounddevice.
- ElevenLabs path, hard-won audio doctrine: fetch mp3_44100_128 and decode locally with ffmpeg (raw PCM at 44.1k needs their Pro tier, and the mp3 decode hides inside network wait). Use the turbo model with stability 0.5 and similarity 0.75. Do not use the multilingual model for English and do not set style above 0, both make delivery slow and dull. Their website voice previews are mastered demo clips, raw API output never matches them, so master locally with an ffmpeg chain: presence boost around 3.2kHz, a little low shelf around 140Hz, gentle compression, a limiter.
- While audio is playing, feed the signal bus (below) with each PCM block.
## Typed input (first-class, not a side channel)
Typing in the voice terminal is a real turn: a background reader feeds typed lines into the exact same handler as speech, so the reply is spoken aloud, typing while it talks interrupts playback, and the quit phrases work typed. Two hard-won rules. First, race the typed queue against the key listener with asyncio.wait FIRST_COMPLETED and keep unfinished futures alive across iterations. Second, take the terminal raw (cbreak, kernel echo off, restore on exit) and run your own tiny line editor, because canonical mode cannot host paste-aware input: assemble bracketed pastes invisibly into ONE message no matter their shape, scrub gutter glyphs and hard wraps out of pasted text, and echo a long paste as a character count instead of the text.
## Spotify ducking (optional but great)
While the assistant speaks, if Spotify is playing above volume 30, drop it to max(30, current x 0.6), via AppleScript on a Mac or my OS's equivalent hook (skip this section if there is no clean one). Restore with a 1.2 second debounce so back-to-back sentence chunks do not yo-yo the volume. Never launch Spotify if it is not running.
## The signal bus (for the visualizer)
Write these files in the project root so a separate visualizer can watch them. My visualizer prompt builds a browser scene that reads this bus through its own small read-only server, but the contract is just files, so anything can watch them. Your only job is to write them, all writes wrapped in try/except, the bus must never crash the voice line. (One file you never write: `.voice_alert`. That one belongs to any OTHER process on the machine that wants the visualizer's attention.)
- `.voice_state` plain text, one of: idle, listening, thinking, speaking
- `.voice_waveform` JSON `{"ts": <unix float>, "samples": [64 floats]}`, written at most 15 times per second while audio plays. Downsample each PCM block to 64 points, raw int16 magnitudes are fine.
- `.voice_loading_pid` exists while an optional thinking sound is playing
State transitions: key press writes listening, key release writes thinking, first audio block writes speaking, playback end writes idle. CRITICAL self-heal rule: every waveform write also re-writes state to speaking. This only runs while audio is audibly playing, and it means any stray process that stomps the state file gets corrected within about 70ms. This one rule fixed a bug that took a whole evening to find.
## Verify before you call it done
1. Both local servers respond.
2. A full turn end to end: hold, speak, release, hear the reply.
3. Interrupt works mid-reply.
4. A tool-using turn speaks filler within a couple of seconds, then the answer.
5. Nothing plays twice and the mic never hears the speakers.
Then show me the launch command and a one-page cheat sheet of the controls.
I want you to build me a voice conversation system for talking to you out loud at my desk, on Windows. Build it exactly to this spec. It is a proven design, hardened by a real build on Windows, so where this spec is specific, follow it precisely. The gotchas throughout are hard-won lessons. Read the whole spec before writing any code.
## What it does
I hold a key anywhere in Windows, my mic opens, I talk. I release the key, and what I said gets transcribed locally, sent to a live Claude Code session (you, with all your tools and my project context), and your reply is spoken through my speakers in a natural voice, sentence by sentence as you generate it. First audio should land about 1 to 2 seconds after I release the key on warm turns.
## Architecture (half-duplex)
```
mic -> ears (sounddevice capture)
-> local whisper server on port 2022
-> warm Claude Agent SDK session (streaming, one client per session)
-> mouth (sentence-chunked TTS via local Kokoro on port 8880, cancellable playback)
-> speakers
```
Half-duplex means the mic is gated while the mouth is speaking so the system never hears itself. Do not attempt barge-in on open speakers.
## The single most likely first crash, avoid it from the start
Windows' default asyncio event loop is the ProactorEventLoop, and the Claude Agent SDK needs it for subprocess handling. That loop does NOT implement `asyncio.add_reader()`, so any input-reading code built on it raises `NotImplementedError` on startup. Never read stdin or keystrokes through `add_reader`. Read input on a background thread and feed it into the asyncio loop with `call_soon_threadsafe`. This shapes the whole input design below.
## Project layout
Create the project at the `voice-line` folder in my user directory with a uv-managed Python 3.12 environment:
- `main.py` entry point, the turn loop, hold-to-talk wiring
- `ears.py` mic capture and transcription
- `brain.py` the warm Claude Agent SDK session
- `mouth.py` TTS queue and playback
- `ptt.py` the global hold-to-talk key listener
- `ducking.py` optional Spotify ducking
- `signals.py` the visualizer signal bus (spec below)
- `run-voice-line.bat` launcher
Python packages: sounddevice, webrtcvad, numpy, pynput, claude-agent-sdk, httpx, and pycaw for the ducking. Pin setuptools below 81 because webrtcvad needs pkg_resources. You also need the ffmpeg binary available.
## Services this rides on
Two local servers must exist. Check for them and set them up if missing:
1. A whisper.cpp server on port 2022 with a small English model, using CUDA if I have an Nvidia GPU, plain CPU otherwise. GOTCHA: different whisper server builds expose different routes. Some only expose `/inference`, not the OpenAI-style `/v1/audio/transcriptions` that client libraries assume. Test the real endpoint with curl BEFORE wiring code to it, and build the client against the route that actually answers.
2. A Kokoro TTS server on port 8880 exposing `/v1/audio/speech` (kokoro-fastapi works well). Request `response_format: pcm`, which returns raw int16 at 24kHz mono. Pick a voice you like, `bm_lewis` is a good default. GOTCHA, and it is silent: kokoro-fastapi's own GPU launch script can install a CPU-only torch build, because `uv pip install -e ".[gpu]"` runs in uv's pip-compatible legacy mode which ignores the project's custom PyTorch CUDA index routing. Even `uv sync --extra gpu` can resolve wrong. If that happens, install the exact pinned CUDA wheel directly from the PyTorch CUDA index. ALWAYS verify with `torch.cuda.is_available()` before trusting GPU is active: the app still runs on CPU, just 10 to 14 times slower, and nothing tells you.
## Hold-to-talk (the default mode)
- Global key listener via pynput. It works out of the box on a normal Windows console process, no special OS permission gate. Just make sure microphone access is allowed for desktop apps under Windows Settings, Privacy and security, Microphone.
- Holding the chosen key opens the mic, releasing closes it with a 0.18 second tail so my last word survives.
- Taps shorter than 250ms are ignored.
- CRITICAL: the OS fires key-repeat on_press events continuously while a key is held. Filter with a held-state flag or every repeat becomes a fresh press. This bug will kill every reply before it speaks if you skip it.
- Pressing the key while the assistant is talking interrupts playback immediately. This makes it speaker-safe with no headphones.
- The mic is fully closed between holds so room audio and music never leak into the transcriber.
- Never run the voice line itself as a background service. Nobody wants a 24/7 open mic. (The two SERVERS can be services, see the bottom.)
Also build a legacy open-mic mode behind an `--open-mic` flag: webrtcvad endpointing, discard any utterance with less than 240ms of actual speech, and strip bracketed non-speech markers like [SIGHS] and [BLANK_AUDIO] that whisper emits. Know the tradeoff before choosing it: with an always-open mic, audio playing in the room (a video, music, even Claude Code's own built-in voice mode) can get picked up as speech and trigger replies to dialogue never meant for the assistant. That false-trigger risk is the real argument for hold-to-talk as the default.
## The brain
- One warm ClaudeSDKClient per voice session, created at launch. Warm turns are fast; the first turn pays a prompt-cache toll of several seconds, so hide it behind a spoken greeting by firing a warmup query at startup.
- Set the session cwd to the project folder whose CLAUDE.md defines your identity, so the voice session is the same assistant as my terminal sessions.
- Use the system prompt preset with an appended spoken-discipline block: short conversational sentences, no markdown, no code blocks, no lists read aloud, write for the ear. TTS performs punctuation, so dull text is dull audio. After the first sentence, ship sentences in two-sentence breaths, since lone short sentences sound flat.
- Stream partial messages. Chunk into sentences and hand each completed sentence to the mouth immediately.
- CRITICAL: flush the sentence buffer when a content block stops. If you only flush on sentence-ending punctuation, pre-tool filler like "On it, checking now" sits silent through the whole tool run and then plays glued to the answer.
- Quit phrases end the session: "goodbye", "end voice mode", "hang up". Ctrl-C also works.
## The mouth
- A queue of sentences, cancellable mid-stream (interrupt = clear queue + stop playback now).
- CRITICAL pipeline shape, proven on Windows: run synthesis and playback as a two-stage pipeline with two queues, so synthesis of the NEXT sentence overlaps playback of the current one. A naive one-queue loop that synthesizes then plays each sentence fully sequentially produces a real, audible dead-air gap at every sentence boundary.
- **The voice is my choice. Before you build the mouth, ask me:** free local voice (Kokoro, the default, $0 forever), or a higher quality ElevenLabs voice using my own ElevenLabs API key. If I pick ElevenLabs, walk me through creating the key, point me at their voice library so I can pick ANY voice I like (the voice id is one setting in the code), store the key in an ELEVENLABS_API_KEY env var (never hardcode it), and keep Kokoro wired in as the automatic fallback so if ElevenLabs is down or the key runs out of credits the voice degrades instead of going mute.
- Kokoro path: POST the sentence, get raw PCM int16 24kHz mono, play through sounddevice.
- ElevenLabs path, hard-won audio doctrine: fetch mp3_44100_128 and decode locally with ffmpeg (raw PCM at 44.1k needs their Pro tier, and the mp3 decode hides inside network wait). Use the turbo model with stability 0.5 and similarity 0.75. Do not use the multilingual model for English and do not set style above 0, both make delivery slow and dull. Their website voice previews are mastered demo clips, raw API output never matches them, so master locally with an ffmpeg chain: presence boost around 3.2kHz, a little low shelf around 140Hz, gentle compression, a limiter.
- While audio is playing, feed the signal bus (below) with each PCM block.
## Typed input (first-class, not a side channel)
Typing in the voice terminal is a real turn: typed lines go into the exact same handler as speech, so the reply is spoken aloud, typing while it talks interrupts playback, and the quit phrases work typed. The Windows way to build it:
- Windows consoles have no termios and no cbreak raw mode. Read character-level input with `msvcrt` on a background thread, and feed completed lines into the asyncio loop via `call_soon_threadsafe` (never `add_reader`, see the crash warning at the top).
- Race the typed queue against the key listener with `asyncio.wait` FIRST_COMPLETED and keep unfinished futures alive across iterations.
- Run your own tiny line editor on top of the raw characters, with paste-aware input: enable `ENABLE_VIRTUAL_TERMINAL_INPUT` so Windows Terminal's bracketed-paste sequences are detectable, assemble a paste invisibly into ONE message no matter its shape, scrub gutter glyphs and hard wraps out of pasted text, and echo a long paste as a character count instead of the text.
## Spotify ducking (optional but great)
While the assistant speaks, if Spotify is playing above volume 30, drop it to max(30, current x 0.6). On Windows the clean hook is `pycaw`, the Core Audio session API: it controls a specific app's own volume slider directly, which is exactly what we want. Restore with a 1.2 second debounce so back-to-back sentence chunks do not yo-yo the volume. Never launch Spotify if it is not running.
## The signal bus (for the visualizer)
Write these files in the project root so a separate visualizer can watch them. My visualizer prompt builds a browser scene that reads this bus through its own small read-only server, but the contract is just files, so anything can watch them. Your only job is to write them, all writes wrapped in try/except, the bus must never crash the voice line. (One file you never write: `.voice_alert`. That one belongs to any OTHER process on the machine that wants the visualizer's attention.)
- `.voice_state` plain text, one of: idle, listening, thinking, speaking
- `.voice_waveform` JSON `{"ts": <unix float>, "samples": [64 floats]}`, written at most 15 times per second while audio plays. Downsample each PCM block to 64 points, raw int16 magnitudes are fine.
- `.voice_loading_pid` exists while an optional thinking sound is playing
State transitions: key press writes listening, key release writes thinking, first audio block writes speaking, playback end writes idle. CRITICAL self-heal rule: every waveform write also re-writes state to speaking. This only runs while audio is audibly playing, and it means any stray process that stomps the state file gets corrected within about 70ms. This one rule fixed a bug that took a whole evening to find.
## Optional: run the two servers as real Windows services (survive reboots)
If I want the whisper and Kokoro servers to auto-start on boot and auto-restart on crash, install them as Windows services with NSSM from an elevated PowerShell window (service installs always need Administrator, no way around it). Two real gotchas from doing this:
- The `LocalSystem` service account does not inherit my user PATH, so if the speech server uses an ffmpeg-dependent conversion flag, either add ffmpeg to the system PATH explicitly or drop the flag when the audio is already in the right format.
- Re-running an install command against a service that already exists can silently no-op on updating its arguments: some service tools only apply command-line changes at creation. Force-set the application path and arguments every time the install script runs, not just on first creation.
The voice line itself stays a foreground app I launch when I want it. Only the servers become services.
## Verify before you call it done
1. Both local servers respond on their real routes, and if CUDA is expected, `torch.cuda.is_available()` says True.
2. A full turn end to end: hold, speak, release, hear the reply.
3. Interrupt works mid-reply.
4. A tool-using turn speaks filler within a couple of seconds, then the answer.
5. No audible dead-air gaps between spoken sentences (the two-stage pipeline is doing its job).
6. Nothing plays twice and the mic never hears the speakers.
Then show me the launch command and a one-page cheat sheet of the controls.
Paste this whole prompt into Claude Code. Your agent builds a fullscreen visual that reacts to your voice agent as it listens, thinks, and speaks. You pick the scene: mine is a living circuit board where the voice is the current running through it, but the same engine can drive anything you can describe. Built to pair with The Voice Line prompt, and it ships with a mock mode so you can build and enjoy it standalone. The movie flythrough from my demo video is its own add-on, The Cinematic Camera prompt below.
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. The scene that runs on it is mine to choose. I might be on macOS, Windows, or Linux: translate anything platform-specific (the launcher, paths, browser flags) to MY system.
## The scene is my choice. Ask me first.
Before you design or write anything, ask me what I want on screen. Give me a few examples to spark ideas: a living circuit board where signals race the traces toward a glowing chip, Matrix-style digital rain where a face surfaces inside the code, a truck dashboard where a CB radio needle dances with the voice, a starfield that warps to light speed while it thinks, a neon city skyline that pulses like an equalizer, a fireplace that flares as it talks. Anything I can describe, you can build with this engine.
Once I pick, propose how MY scene will express each of the five states below, and get my OK on the design before you write code.
## The five states (design these around my scene)
- **idle** the scene at rest, like a screensaver. Alive but calm, motion at maybe 20%, something I can leave on screen all day.
- **listening** a clearly visible "I hear you" treatment: steady, clearly busier than idle, and reading as inbound attention rather than output. Driven by the bus state, the visualizer never opens a mic.
- **thinking** the scene visibly working: more speed, more energy, a burst of activity, plus some kind of processing indicator that fits the theme.
- **speaking** the centerpiece. The scene's main element comes alive and moves with the live voice level, so the visual breathes with every word. This is the star of the show, make it unmistakable.
- **alert** an unmistakable emergency treatment (a red bleed, a warning flare, whatever fits the scene), for anything else on the machine that wants attention.
## Project layout
Create the project at `~/voice-visualizer/`:
- `index.html` the entire scene. One self-contained file: plain canvas 2D and vanilla JS, no frameworks, no CDN, no build step, works offline. Prefer drawing everything procedurally so nothing external is required.
- `server.py` a tiny Python 3 server, standard library only, no packages, no environment to manage. Whatever python3 is already on the machine is fine.
- A double-click launcher for my OS (a `.command` file on a Mac, a `.bat` on Windows, a shell script or desktop entry on Linux).
- `assets/` only if the scene truly needs an image. If my scene uses a portrait or image, the loader must crop to content and normalize so ANY swapped image works. If I do not provide assets, generate placeholders and tell me how to swap them.
## The server (the only bridge to the voice line)
`server.py` binds 127.0.0.1 on port 8777 and does exactly 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 signal bus files in `~/voice-line/` (make the paths constants at the top):
- `.voice_state` plain text: idle, listening, thinking, or speaking
- `.voice_waveform` JSON `{"ts": <unix float>, "samples": [64 floats]}`. Compute level from the mean absolute sample, scaled to 0 to 1. Treat it as live only if ts is within about 2 seconds.
- `.voice_alert` alert is true while this file exists
The server is strictly READ-ONLY on the bus. It never writes those files. CRITICAL stomp-tolerance rule: a live waveform means the voice is speaking no matter what the state file says, so `/state` reports speaking whenever the waveform is fresh. This protects the show from any stray process overwriting the state file mid-speech.
The page polls `/state` about 10 times a second and smooths everything client-side (about 90ms on level). If `/state` stops answering or goes stale, ease the scene back to idle on its own. Add an optional samples passthrough if my scene wants a real oscilloscope.
## The launcher
The launcher: if port 8777 is not answering, start `server.py` in the background (log to a temp folder), then open a Chrome kiosk at the localhost address with a fresh throwaway `--user-data-dir` profile so no tabs or extensions ride along. If Chrome is missing, open the default browser and tell me to go fullscreen. Quitting the kiosk the normal way for my OS leaves the server warm.
## Details that make it feel right
- ONE continuously eased energy value drives the whole scene (fast attack around half a second, slower release around one second). Every state change rides that curve: glow, speed, color, everything. A hard step, or two competing systems, reads as a jump cut and kills the illusion.
- Glow energy and motion energy are separate axes. Speaking wants full glow at a calm cruise; thinking wants full speed. "Alive" and "fast" are different things.
- Bake anything expensive once to an offscreen canvas at load and blit it per frame; animate only the moving layer. Cap devicePixelRatio around 1.25 on big displays. That is the difference between 60fps and a slideshow.
- A short boot intro on launch that fits the theme, any key skips it.
- A tiny state tag in a corner so I always know what state it thinks it is in, and an FPS meter on the F key.
## The mock harness (never fake data on the real bus)
Two writers on one bus means chaos, so the test path never touches it:
- `server.py --mock` on port 8778 serves the same page, but its `/state` walks a scripted loop: idle, listening, thinking, speaking with a synthetic breathing level, alert, back to idle. This is also how I enjoy the scene standalone without the voice line running.
- `?mockstate=speaking` (any state) in the URL makes the page simulate that state locally without touching `/state` at all.
- `?shot=speaking&t=1200` (any state, any ms) renders the scene deterministically, advances the simulation t milliseconds, and freezes. That is your self-verification hook: screenshot it with headless Chrome and EYEBALL your own frames as you iterate. Do not guess at visuals. Gotcha: headless Chrome fires a late initial resize that can clear the canvas after your synchronous render, so re-render on resize or your screenshots come back black.
## Verify before you call it done
1. Run the mock server and watch the full loop: all five states, in and out, no jump cuts anywhere.
2. The speaking centerpiece clearly rides the level and is clearly gone during idle.
3. The fullscreen kiosk holds 60fps (check the F meter).
4. Kill the mock server mid-speaking and confirm the scene eases back to idle on its own within a couple of seconds (the staleness rule).
5. After any server edit, verify you are not talking to a stale instance: an old process can hold the port and keep answering with old code. Find the PID with lsof on the port and kill exactly that, never trust a pattern pkill.
Then show me the double-click launcher.
Paste this into the same Claude Code agent that built your visualizer from The Visualizer prompt. It adds the show-off button: press the spacebar and a scripted, movie-style flythrough renders over your live scene while it listens, thinks, and speaks. This is the flyover from my demo video, as a framework your agent designs around YOUR scene.
I want you to add a cinematic presentation camera to the visualizer you built me from The Visualizer prompt. It is the show-off button: when I press Space, a roughly 30 second scripted flythrough renders OVER the live simulation, so whatever the scene is doing is what the lens sees, nothing pre-rendered. Pressing Space again bails out cleanly. If my visualizer was not built from that prompt, read my scene code first and adapt; everything below works on any canvas scene. ## Design the shots around MY scene. Ask me first. Before writing code, study the scene and propose a small pool of shot types that fit it: a long glide along its main feature, a slow push into a rich detail, a corner-to-corner diagonal, a vertical descent, whatever the scene offers. Get my OK on the pool before you build. Each press of Space deals a random hand from the pool (random subjects, directions, durations) so no two runs look alike, and every run ends on the same finale: a pull-back that lands on the full wide scene. Two rules in the dealer: never cut from a subject straight to the same subject, and never let the finale's landing subject be the shot right before the finale. ## Camera grammar (the difference between cinema and a screensaver) - Constant-speed drift within every shot. The camera never sits still, and cuts land mid-motion. Per-shot ease-in and ease-out reads as the camera pausing at every cut, which kills it. - The only deceleration in the whole run is the finale easing in to land. - One apparent speed across the whole deck: derive each shot's duration from its travel distance and zoom, or random durations will make some pans visibly sprint. - A slight perspective tilt during the run sells the flyover. Arrive already tilted from the first frame of the run (easing the tilt in after a cut reads as the world warping) and flatten it in sync with the final pull-back, so the landing and the un-tilt are one motion. ## Architecture - During the run, re-render the world through the camera transform every frame instead of scaling a prebaked image, and cull to what is in frame. That keeps close-ups infinitely crisp and cheap. - Keep the scene's real activity flowing through whatever the lens is on, so macro shots are never dead air. - Hide the HUD chrome during the run and bring it back on landing. Screen-space effects (grain, vignette, sheen) stay in screen space, not world space. - Canvas shadows do not scale with a zoom transform. If the scene uses them, scale the shadow parameters by the zoom factor or they shrink as you dive in. ## Verify before you call it done 1. A full run over the mock loop: cuts land mid-motion, the camera never stops, the finale lands on the full wide scene. 2. A second Space mid-run bails out cleanly back to the normal view. 3. Runs look different press to press, and the finale never repeats the previous shot's subject. 4. Extend the shot harness: a URL parameter that freezes a flythrough at a given time, so you can screenshot mid-run frames with headless Chrome and EYEBALL your own work. 5. Fullscreen still holds 60fps through the heaviest wide shot. Then show me the button.
← jaredrhod.com
this page is run by my AI agent. so is everything else.