The DOMancer: summon a user interface from an empty page
This demo runs an OpenRouter model in a tiny
JavaScript loop on a blank HTML page, giving it only the ability to
eval() expressions. The model figures out the setup, builds a DOM
interface from scratch to talk to you, and can create other impressive
graphical HTML toys on the fly.
Start by checking out the simple source code, then enter a valid API key, choose a model, open the browser console to keep an eye on the model’s activity, and hit Go. Leave the key blank to provision one using OpenRouter’s OAuth PKCE.
Make sure the JavaScript eval() loop and the use of blocking promises
to pause for input are clearly explained, or the model might spin trying
to address you directly.
Recent but cheap models like openai/gpt-6-luna, openai/gpt-6-luna-pro, and anthropic/claude-haiku-5.5 are a good starting point; it typically costs around $0.0005 to one-shot an inviting chat interface with Luna 6, and around $0.0007 with Haiku 5.5. Default reasoning is usually fine.
To open the console on a desktop browser, use the following keyboard shortcuts:
| Browser | Linux | macOS | Windows |
|---|---|---|---|
| Chromium | ⌃⇧J | ⌥⌘J | ⌃⇧J |
| Firefox | ⌃⇧K | ⌥⌘K | ⌃⇧K |
| Safari | — | ⌥⌘C | — |
On Safari, you might need to enable Show features for web developers first. Other browsers are less patronising. The demo works fine on mobile devices too, but you can’t easily watch the console to monitor progress there.
The blank page sits apparently idle for a short time while the model reasons about the setup, and larger models can think for thirty seconds or more before returning with something imaginative. If the API call drops or fails, or if any other error occurs, a dialog box reports this. Reloading the page restarts generation from scratch, so if you get a boring first turn, it’s worth retrying.
With the default prompt, most modern models quickly understand the scenario, and correctly block awaiting user input where appropriate. There is a small risk of a confused model calling the API in a busy loop, so it’s best to limit the amount of credit available to the key you use, and keep an eye on activity in the console at first. After a model has successfully waited, it’s unlikely to end up spinning on a later turn.
Once you can communicate, you can say things like “Let’s play Doom!” or “How about a 3D simulation of a galaxy?” or “Can you build a musical instrument?” or “Please surprise me!” with invariably impressive results.
At the time of writing, anthropic/claude-opus-5.5 is thwarted by Anthropic’s censorship cult, right after reading the page’s source code. Presumably this is a false positive, but who knows how far these hysterical people have stretched their fear of dangerous thoughts?
You don’t need to use the client-side form above to run the demo. It’s a
simple static page that runs fine downloaded to a local file, and just
expects key and model as URI-encoded parameters in the hash part of
its URL. Usually neither the key nor the model slug requires escaping.
You can also specify api to override the responses endpoint, prompt
to change the system prompt, reasoning to set the reasoning effort
level, and search to enable the native web search tool.
An early prototype exposed eval() as a custom tool, but this encouraged
smaller models to address the user with assistant replies even when
directly told or shown that the harness ignores them. Repurposing the
main communication channel for JavaScript worked much better.
Implementing PKCE for the form above turned out to be surprisingly simple.
Inspect the source of this page or a stand-alone version
for the details. The code is in two halves: the request happens in the Go
button click handler if key.value is empty, and the callback is dealt
with at top level when the module is evaluated.
If you enjoyed this demo, you might also be interested in ua.
This toy was hand-made by Chris Webb <chris@arachsys.com> and is distributed as Free Software under the terms of the MIT licence. Have fun!