A full Claude chat app with a South Jersey accent. You talk to Claudio, a retired machinist, or his wife Claudia, a retired seamstress and the best cook in the family. Both have opinions about your gravy.
This was my first time playing with prompt building. I'm Italian-American, wrapping a language model in that heritage sounded like fun, and Claude → Claudio was too on the nose not to do.
The first version was a portrait, a text box and a send button. Its whole personality lived in the system prompt, and it couldn't do much besides talk about pasta. The rebuild makes it a real chat app that can do anything Claude can, in his voice. Then it got a second character, and the portraits got redrawn from photos of my late grandparents.
The page is still plain HTML, CSS and a handful of ES modules, no framework and no build step. It looks like any chat app: past chats in a sidebar, a thread, and a composer that takes images, PDFs and text files. Under the hood it's one Firebase Function making one streaming call to Claude Sonnet 5.5 with adaptive thinking, web search, web fetch and prompt caching.
A toggle next to the name switches between Claudio and Claudia. Each has a backstory, a system prompt, an avatar and an accent color: sage green for him, tomato red for her. Their trades only come up when a question is near them. Ask about a leaky faucet and you'll hear about the shop floor; ask about recursion and you just get a good explanation of recursion.
A conversation stays with whoever it started with. Toggling mid-chat starts a new chat with the other one and keeps whatever you'd typed.
The function streams thinking, text, a line each time he searches or reads a page, and the sources he cited. A reply belongs to its chat, not the screen. Switch chats mid-answer and it keeps streaming in the background and saves when it's done. Come back while it's still going and the chat catches up and continues live.
Chats live in IndexedDB. For signed-in users they're mirrored to Firestore, one document per message, so they follow you to another device. A message too big for a document, like a large image or a long fetched page, goes to Cloud Storage instead.
Every visitor is signed in anonymously in the background, and gets $0.30 of actual API cost on my key, about ten messages. There's no counter; you chat until it runs out. The budget is per network over 30 days, so clearing storage doesn't buy more. After that, you can bring your own Anthropic key or pay $6 a month for a $5 allowance that resets on your billing date.
Each reply's cost is worked out from the token and search counts the API reports. That's what gets charged against the trial or the allowance, so a message that fails before he says anything costs nothing.
Saved keys are encrypted with AES-256-GCM, with the user's ID bound in, so a copied ciphertext won't decrypt on another account. The browser only ever sees the last four characters.


In v1, web search split the reply into a dozen blocks and content[0] was Claudio's private thoughts, not his answer.
One chicken-parm question ran two searches and about 49k tokens.
v1 hid its reasoning in tags and re-paid for it every turn. The rebuild uses real thinking, and the reply is just the reply.
Tying the stream to the chat instead of the screen is what made switching mid-answer safe.
A dollar budget from the API's own usage counts is fairer than a message count, and failed replies are free.
Only the last X-Forwarded-For entry comes from Google's front end. The rest can be spoofed.
onRequest), Node 22: streaming chat, account API, Stripe webhook