WHERE.TASTE.TRAVELS.FROM
How did half of India end up in this one plate?
An animated storytelling web app that visualizes how ingredients from different Indian regions converge into a single dish. Watch as spices, grains, and flavors journey across the map to tell the geographic story behind your favorite Indian dishes.
Enter a dish name. Watch the magic unfold:
- AI identifies the key ingredients (5-10 meaningful ones, no water-from-Ganges nonsense)
- Maps each ingredient to its geographic origin (district-level precision!)
- Animates the journey as ingredients "travel" from source regions to the dish's origin
- Tells the story with witty, Bill Wurtz-style commentary
The result? A beautiful, educational visualization that answers: "How did half of India end up in this one plate?"
- 🗺️ District-Level Visualization: Precise geographic mapping with district highlights
- 🎬 Smooth Animations: D3.js-powered zoom, pan, and path animations
- 💬 Witty Commentary: Fast-paced, pun-filled narration (Bill Wurtz vibes)
- 📱 Mobile-First Design: Collapsible info panel, optimized touch targets, responsive layout
- ⚡ Instant Results: Cached suggestions for popular dishes (no LLM wait time)
- 🎨 Minimalist Aesthetic: IBM Plex Mono font, Hermès orange accents, clean UI
- Node.js 18+ and npm
- A Gemini API key (Get one here)
-
Clone the repository
git clone <your-repo-url> cd spice-routes
-
Install dependencies
npm install
-
Set up environment variables
cp env.example .env # Edit .env and add your GEMINI_API_KEY -
Start the backend (Terminal 1)
npm run dev:api # or: vercel dev -
Start the frontend (Terminal 2)
npm run dev
-
Open your browser
http://localhost:5173
💡 Tip: You need both terminals running! The backend handles API calls, the frontend serves the UI.
See DEPLOYMENT.md for detailed Vercel deployment instructions.
- Frontend: Vanilla TypeScript + Vite
- Visualization: D3.js (SVG rendering, geo projections, animations)
- Map Data: TopoJSON (India state & district boundaries)
- Backend: Vercel Serverless Functions
- AI: Google Gemini 2.5 Flash API
- Styling: Pure CSS (mobile-first, responsive)
spice-routes/
├── api/ # Vercel serverless functions
│ ├── dish.ts # Main dish analysis endpoint
│ ├── suggestions.ts # Cached suggestions endpoint
│ └── seed-cache.json # Pre-computed popular dishes
├── src/
│ ├── main.ts # App entry point & UI logic
│ ├── animation.ts # D3.js animation engine
│ ├── map.ts # Map utilities & district resolution
│ ├── api-client.ts # Frontend API client
│ ├── domain.ts # TypeScript interfaces
│ └── styles.css # Styling (mobile-first)
├── public/
│ ├── india.json # TopoJSON India map data
│ └── state2district_list.json # District mapping data
└── media/
└── cover_img.png # Project cover image
Your API key is secure!
- ✅ Stored server-side only (Vercel environment variables)
- ✅ Never exposed to the frontend
- ✅ Only used in serverless functions
- ✅ Not visible in browser network requests
The frontend makes requests to /api/dish, which then securely calls Gemini API on the server. Your key never leaves Vercel's infrastructure.
Minimalist. Functional. Fun.
- Typography: IBM Plex Mono (monospace, developer-friendly)
- Color Palette: Hermès orange accents on cream/beige backgrounds
- Layout: Mobile-first, collapsible panels, touch-optimized
- Animation: Smooth, purposeful, not distracting
Inspired by the clean, terminal-like aesthetic of modern developer tools, but with a playful twist.
- DEPLOYMENT.md - Deploy to Vercel
- LOCAL_SETUP.md - Detailed local development guide
- BACKEND_SETUP.md - Backend architecture overview
- CACHE_AND_DISTRICTS.md - Caching & district resolution
- QUICK_START.md - Quick troubleshooting guide
- User enters dish name → Frontend sends to
/api/dish - Backend checks cache → Returns instantly if cached
- If not cached → Calls Gemini API (2-stage process):
- Stage 1: Identify ingredients & origins
- Stage 2: Resolve exact districts from place names
- Backend caches result → Saves to JSON file for future requests
- Frontend receives data → Renders map, animates paths, shows commentary
- Suggestions update → New dish appears in carousel
- Approximate accuracy: Ingredient origins are AI-inferred, not historically rigorous
- District resolution: Some place names may not resolve to exact districts
- Cache persistence: Vercel
/tmpis ephemeral (cache resets on deployment) - No user accounts: Stateless, single-session use
Found a bug? Have an idea? Feel free to:
- Open an issue
- Submit a PR
- Share feedback
This is a fun project—contributions welcome! 🎉
MIT License - Use it, modify it, make it your own.
- India TopoJSON: udit-001/india-maps-data
- State and district boundaries
- Geographic coordinate data
- Visual Aesthetic: Inspired by @vamsibatchuk's wanderword app
- Built with: Cursor + an ensemble of LLMs
- Pair programming with AI assistants
- Iterative refinement and debugging
- Yes, the robots helped write this. They're pretty good at it.
- Gemini 2.5 Flash: Google's Gemini API for ingredient analysis
- D3.js: Mike Bostock's incredible visualization library
Every dish tells a story. A story of geography, culture, trade routes, and migration. SPICE.ROUTES makes that story visible.
What started as a weekend project to visualize ingredient origins became a journey through Indian geography, food culture, and the art of storytelling through code.
Because sometimes, the best way to understand a place is through its food.
Made with 🍛 and ❤️
"How did half of India end up in this one plate?" — Now you know.
