hosette.
← Back to projects

2025 · civic tech · location-aware · AI assistant

Discover Sacramento

Guided voice tours for locals and tourists in one mid-sized city

Role
Product manager, system architect, content editor
Stack
React 18ViteTypeScriptTailwindMapbox GLSupabaseLovable AI GatewayElevenLabsFramer Motion
Live
cts.hosette.net ↗
Discover Sacramento: Guided voice tours for locals and tourists in one mid-sized city
The masthead and the map, side by side. Filter chips above the map; pins clustered downtown.

why

A guided walking tour for Sacramento, built for both locals and tourists. Most “explore the city” apps are search engines with a map skin: they hand you a pin, a card, a phone number, and disappear. I wanted something closer to a walking companion. A voice that talks while you walk, names what used to be on this corner, fills in what the building is hiding.

Sacramento is the test case because mid-sized cities get worse coverage than they deserve, because the history is denser than the surface lets on, and because if the shape works here it’ll work anywhere with a dense enough catalogue.

what I built

A location-aware catalogue of 115 places across the city, each one a structured record (era, kind, sources, coordinates) plus a short script generated from those fields by Lovable’s AI Gateway. The script goes to ElevenLabs for text-to-speech. The audio plays in the headphones; the map pins move with the listener.

Architecturally it’s a thin React app over Supabase. Edge functions broker every external call: Mapbox tokens, ElevenLabs keys, Google Places lookups never reach the client. The catalogue is the asset; everything else is plumbing.

A Mapbox view of central Sacramento with roughly forty numbered pins clustered tight around downtown and midtown, thinning out toward Land Park, River Park, and Sac State. Filter chips run along the top: History, Arts & Culture, Parks & Nature, Architecture, Government, Food & Drink, Transportation, Community, Sports, Other. A counter in the corner reads 115 locations.
Eight categories, 115 pins, the city flattened onto a single panel. The catalogue is the asset; the rest is plumbing.

my voice, badly

The whole product is guided voice tours: a local or a tourist walks the city while someone tells them what was here before. The someone had to be a person, not a stock TTS voice. So I cloned mine in ElevenLabs and piped the place scripts through it.

It doesn’t sound anything like me. The cadence is close, the vowels are close, but the warmth isn’t there. A professional-tier clone would probably close the gap; I haven’t paid to find out. The starter plan stays. The tour still works.

One tour stop, run through the cloned voice. Decide for yourself how close it lands.

Audio generates at page load right now, which is the wrong answer. Each place should be cached after the first play, or pre-warmed for the canonical 115. TTS is the expensive call in the stack and there’s no reason to pay for it twice. I know what the fix is. I haven’t shipped it.

decisions I’d defend

Edge functions as secret broker. No third-party key reaches the client. Every Mapbox tile request, every ElevenLabs call, every Google Places lookup runs through a Supabase edge function that holds the credential. The client sees a signed URL or a buffered audio response and nothing else. Overbuilt for a side project; the right shape for the production version.

Structured place data before generated copy. The script is not the source of truth. Every place is a row (era, kind, address, sources, short editorial summary) and the model writes the script from those fields, never around them. If a fact is wrong, you fix the row and regenerate. The model doesn’t talk to the user directly. The voice does, and the voice reads from a script the data shaped.

Audio as enrichment, not the spine. When ElevenLabs fails (quota, network, moderation) the place card has to work silently. The tour degrades to a quiet map. That fallback behavior was the first thing I designed, not the last.

The Capitol District Walk tour page. A History badge and an easy difficulty pill, a serif headline reading Capitol District Walk, a one-paragraph blurb about California governance and historic buildings, and a stat strip reading 35 minutes, 0.8 miles, 4 locations. A sidecar card on the right reads Ready to Explore, Start Tour, and notes that location services improve the experience.
One tour, four stops, less than a mile. Hero image is a placeholder; Sacramento doesn't actually have a skyline like that.

what I learned

The hardest part wasn’t the AI. The model writes a serviceable place script from structured fields the first time you ask it; tightening voice and tone is a few iterations on the prompt. The hardest part was the catalogue: deciding which 115 places get a pin, sourcing dates and history, naming the era a building belongs to when it’s lived through three. AI eats the easy work. The editorial work compounds.

The voice clone surprised me. I expected the limitation to be obvious-bad and ship-blocking; it turned out to be subtle-bad and ship-permissive. The failure mode is uncanny, not broken.

what’s next

Caching, first. Then a contributor flow for adding places without touching code. Maybe a second city, picked by whoever cares enough to seed it.

See also

All projects

Working on something similar?

I take a small handful of consulting briefs a year and am always up for trading notes with anyone shipping in this space — send a note.

Or: values behind the work · obsessions that shape it · other projects.