Stay Same

Inspiration

Trip planning tools are good at logistics and bad at feeling like your trip. Ask for an itinerary and you get a top-10 list that reads the same for every traveler on earth. Ask a chatbot instead and it hands you something personal, half of which it invented — a restaurant that closed two years ago, a hotel that doesn't exist. We wanted both at once: real, and yours.

The side quests are where that idea actually became a product decision instead of a mission statement. Every itinerary tool will tell you to "visit the museum." We wanted it to also say "find the best view in the city without paying for it" — a small, specific dare instead of a checklist item, the kind of thing that turns a trip into a story you tell afterward rather than a schedule you followed.

What it does

Stay Same gets you into a trip three different ways:

  • Plan a trip — name a destination, pick a continent, or let it surprise you within your budget. Claude writes a day-by-day plan built from real spots (Google Places) and, when configured, real dated events (Ticketmaster), with a side quest or two worked in per day. Both of those are optional additions rather than hard requirements — the itinerary generates either way, it just tells you plainly which sources actually grounded it. Hotel picks show up ranked by actual walking distance to what you planned, not just "downtown."
  • From a photo — don't know where you want to go? Show us. Gemini reads the aesthetic of a photo you drop in and suggests three real destinations that match it, hotels included.
  • Memories — after the trip, your own photos become a small scrapbook: polaroid cards with an AI-written caption each, a tiny generated melody that plays when you hover one, a film-strip you can drag to reorder, a photo that burns to ash when you remove it, and one button that turns the whole set into a short narrated story with real spoken audio.

We also wired up sign-in through Auth0, going straight into Supabase's row-level security rather than sitting beside it, so the traveler-matching feature we haven't built yet has real accounts to attach to when we get there.

How we built it

Next.js on top of Supabase, with three AI providers each doing exactly one job instead of one model doing everything adequately.

Claude writes the itinerary itself, through structured outputs rather than prompt-and-hope — the response comes back matched to a JSON schema, so there's no markdown-fence-stripping and no parse-retry loop when the model gets chatty. Gemini handles anything with eyes: reading a photo's mood, captioning a whole batch of trip photos in one call so they read like the same person wrote them instead of N independent guesses, and some of the narration writing. ElevenLabs turns text into every sound in the app — narration, sure, but also the camera-click when a photo lands in the album, the soft crumble when one leaves, even the melodies, all from the same sound-generation endpoint, just steered by a different prompt each time.

The design rule underneath all of it: a missing key or a failed call should degrade the feature, not the app — and this isn't just theory, it's the state of our own build right now. Google Places is wired up and actively grounding every itinerary. Ticketmaster isn't configured yet, so real events aren't in the mix at the moment, and the itinerary generates from Claude's own knowledge for that part instead, with the UI honestly saying so rather than pretending every recommendation is verified. No Supabase, trips still generate, they just don't save. No ElevenLabs, a sound effect just quietly doesn't play instead of throwing an error in someone's face.

Challenges we ran into

The biggest one wasn't a bug. It was realizing, about twelve hours before submission, that our original idea wasn't the idea anymore.

We'd started building Ghostwriter — upload a photo, get matched to a hotel that fits its vibe. It worked. It also turned out to be thin the moment we actually used it: no real plan, no persistence, nothing to do once you'd found a hotel besides look at it. We'd built the thing we pitched and it still didn't feel like enough of a product. So we tore most of it down and rebuilt around a bigger idea — a full trip planner grounded in real places and events, with the photo-matching folded in as one entry point among several instead of the whole app — and kept building past the point that felt safe. The old prototype is still sitting in the repo's client/ and server/ folders, partly as a record of where this started, partly because its Gemini and Stay22 integrations were good enough to carry forward into what "From a photo" became.

Everything after that happened fast and under pressure, which is its own category of challenge. Stay22's geocoder was the first real surprise, and it had nothing to do with AI at all: searching "Lisbon" put every hotel 5.5km from where the itinerary actually was, a different neighborhood entirely. We stopped trusting the city name and started anchoring hotel search on the center of mass of the itinerary's own real coordinates instead — data we already had, reused rather than a second geocoding call. Country-scale inputs needed their own fix too: "Japan" plus a beach photo now resolves to Okinawa, Ishigaki, and Miyakojima before a single hotel search runs.

Duplicate listings were next: a top-5 search for one Lisbon address came back as three units of the same building. De-duplicating by street sounds simple until you hit º and ª, which are Unicode letters, not punctuation — a naive symbol strip left "rua da rosa º" looking like a different street from "rua da rosa" entirely.

Then there was the deploy. Everything worked in next dev, and then Vercel's serverless functions started timing out mid-generation, killed at the platform's default limit. That took a real production debugging session.

And underneath all of it, the quieter, constant problem: rate limits. Testing the photo and Memories flows burns through Gemini calls fast — captioning, narration, and per-photo melodies all hit the same API — and there's no clever engineering trick around a hard quota with hours left on the clock, just retries with a stricter prompt when the JSON comes back malformed, and patience for everything else.

What we learned

The unglamorous 80% of building with generative AI turned out to be the grounding and the failure modes, not the prompts themselves. A model that confidently invents a restaurant is a worse product than one that says "I don't have verified data for this," so a real chunk of the engineering here went into proving what actually grounded a recommendation — the sources block, the "not saved" banners, the line between a real Ticketmaster event and an invented side quest — rather than just generating more convincing text.

We also learned that the idea you pitch on day one isn't sacred. The idea that survives contact with actually using your own product is the one worth keeping, even if that means admitting twelve hours out that you need to rebuild.

Built With

Share this project:

Updates

Submission history