This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
Here is the paradox I couldn't stop thinking about: every app that promises to get you outside asks you to look at a screen to do it.
Trail finders, plant identifiers and step trackers all succeed when you open them, so they are quietly designed to keep you looking. You leave the house to touch grass and spend the walk with your face in a glowing rectangle.
adventure.ai flips that goal. The only metric I cared about was how quickly it could get you to put your phone away. It is a local-AI field guide that finds green spaces near you, writes a short mission for the one you pick, and then reads it to you, so your phone can go into your pocket before you take your first step.
The whole thing takes about 30 seconds:
- Start anywhere. Tap π Find Around Me to use live GPS, or type any city and hit π Explore City.
-
Discover. The backend queries OpenStreetMap's Overpass API within a 5 km radius for parks, gardens, nature reserves, viewpoints, picnic sites, water, woods and peaks. They appear as clickable pins on an interactive Leaflet map over Esri World Topo tiles. Filter pills (
All,Gardens & Parks,Viewpoints,Water & Trails) cut the noise. -
Tap a spot, get a Field Guide. A local Gemma 2 (2B) model running in Ollama writes a Touch Grass Field Guide:
- Vibe: one sentence on why to go today
- Best Activity: the one thing worth doing there
- 3 Nature Spotters: local plants, trees or birds to look for
- 3 Screen-Free Quests: physical mini-challenges, each with a checkbox, designed to be done with the phone in your pocket
- Go eyes-free. Press π Listen to Audio Briefing and a warm three-sentence script covers the walk and the quests. Lock your phone, pocket it, and go.
It is for anyone with a spare hour and a vague sense they should be outside: students between lectures, parents looking for somewhere new on a Sunday, travellers in an unfamiliar city. Because it is built on OpenStreetMap rather than a commercial POI database, it works for any place the community has mapped, not only the cities big apps care about.
Demo
The video walks through the full flow: searching a city (Agartala), watching the 5 km Overpass discovery drop pins on the Leaflet + Esri World Topo map, filtering by category, opening a spot to generate a Gemma 2 Touch Grass Field Guide, and ticking off the screen-free quests.
Code
πΏ adventure.ai
Find a patch of green nearby, then leave your phone in your pocket.
Built for the Hacktoberfest 2026 Open-Source AI Challenge: Week 1 (Touch Grass).
adventure.ai is a privacy-first, offline-capable local nature scout and micro-adventure generator. Instead of keeping you glued to a screen, it discovers real parks, botanical gardens, viewpoints, and waterside trails within 5 km of your location (or any city worldwide) and uses local open-weight AI (Google Gemma 2 2B via Ollama) to generate a Screen-Free Field Guide with physical outdoor quests and an eyes-free audio briefing.
β¨ Key Features
-
π Zero-Key Geospatial Discovery: Uses browser geolocation (
Find Around Me) or global city geocoding via OpenStreetMap Nominatim + Overpass API to find real nearby parks, gardens, viewpoints, and water trails within a 3β5 km radiusβwith deterministic offline fallback spots if rate-limited. - πΊοΈ Interactive Topographic Map: Embedded Leaflet.js + Esri Worldβ¦
Everything is open source: github.com/sidharth-chauhan/adventure.ai
How I Built It
ββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββ
β frontend/app/(tabs)/index.tsxβ β backend/src/index.ts β
β Expo Router Web (React Native)βββββββΊβ Express + TypeScript :5000 β
β ββββββββββββββββββββββββββββ β REST β β
β β sandboxed Leaflet iframe β β β /api/discover βββΊ Nominatim β
β β (postMessage bridge) β β β ββΊ Overpass β
β ββββββββββββββββββββββββββββ β β /api/mission βββΊ MongoDB β
β localStorage guide cache β β ββΊ Ollama β
β Web Speech API fallback β β /api/tts βββΊ ElevenLabs β
ββββββββββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββ
The map is an iframe, on purpose
The UI is React Native Expo Router running on web. Leaflet is a DOM library, so I render it inside an <iframe srcDoc> with sandbox="allow-scripts" and talk to it over postMessage. The page sends { type: "data", center, spots, selectedId }. The map answers with { type: "ready" } once Leaflet has loaded and { type: "spot", id } when a pin is clicked. The parent checks e.source against the iframe's contentWindow before trusting any message.
The result is that changing a filter or selecting a spot redraws the pins without reloading the map. The map also only re-fits its bounds when the set of spots changes, not when you select one, so the view doesn't jump around while you read a guide.
Discovery: Nominatim, Overpass, and a demo that never dies
A city name goes to Nominatim to get coordinates. Then one Overpass query pulls every named feature in the radius:
nwr["leisure"~"^(park|garden|nature_reserve)$"]["name"](around:5000,lat,lon);
nwr["tourism"~"^(viewpoint|picnic_site|attraction)$"]["name"](around:5000,lat,lon);
nwr["natural"~"^(water|wood|peak)$"]["name"](around:5000,lat,lon);
Results are deduplicated, categorised, distance-sorted with a haversine function, and capped at 40.
Overpass is a free community service and it sometimes rate-limits or stalls. So if the call times out, fails, or returns fewer than six spots, the backend fills in plausible parks, viewpoints and trails around the coordinates. The generator uses a tiny seeded PRNG (mulberry32) keyed on the coordinates, so the same place always produces the same fallback spots instead of reshuffling on every refresh.
Gemma 2 2B in JSON mode
When you tap a spot, the backend asks a local Ollama instance for the guide:
body: JSON.stringify({
model: "gemma2:2b",
prompt, // spot, category, city, current month, exact JSON schema
stream: false,
format: "json", // constrain output to valid JSON
options: { temperature: 0.8 },
})
A 2B model is small enough to run on a laptop CPU, but small models drift. Even with format: "json", a key can be missing or an array can come back with two items. So every response goes through a normaliser that coerces types and pads arrays to exactly three entries from a hand-written template guide. If Ollama is down entirely, the user gets that template guide, clearly labelled as a starter guide, and it is never cached, so a real AI guide can replace it later.
Dual offline caching
Guides are cached twice:
-
Server side (MongoDB): keyed on spot, category, city and rounded coordinates, written with an upsert. If Mongo isn't reachable, the server falls back to an in-memory
Mapso nothing breaks. -
Client side (
localStorage): a guide you've opened once loads instantly and works with no backend at all. This is what the πΎ Offline Cached badge on the mission card refers to.
Voice: ElevenLabs first, browser second
The three-sentence voiceScript goes to /api/tts. With an ELEVENLABS_API_KEY, the server calls eleven_multilingual_v2 and returns base64 MP3 that the page plays through Audio. With no key, or if the call fails, it returns { fallback: true } and the page reads the script with the Web Speech API (speechSynthesis) instead. The briefing always plays; only the voice quality changes.
Running it
docker compose up -d starts MongoDB and Ollama and pulls gemma2:2b into a persistent volume. Then run the Express server and expo start --web.
Why Does Open Innovation Matter?
I could have built this with a cloud LLM in an afternoon. Here are three reasons it would have been a worse product.
1. Offline resilience. The places this app is for, such as forest trails, hill paths and park edges, are exactly where signal gets thin. The guide is generated by Gemma on hardware I control, and once a guide is cached it loads with no network and can be read aloud by the browser's built-in voice. To be precise, discovery and map tiles still need a connection, so the intended pattern is to plan at the trailhead and walk offline. Even so, "the guide still works when the bars drop" is a property a closed API can't give you.
2. Location privacy. Where you walk is sensitive data. In this design your coordinates are never sent to an LLM vendor. The prompt that includes them goes to a model running on your own machine. Spatial queries go to OpenStreetMap's open infrastructure, not to a commercial ad-tech stack. The only thing that can leave for a third party is the finished audio script, and only if you opt into ElevenLabs by setting a key.
3. No token meter, no engagement trap. Local inference has no per-token cost, so there is no pressure to monetise attention. An app whose job is to get you to leave the screen cannot sit on a business model that rewards screen time. Running open weights, I can set the objective to "get you outside faster" without a cloud bill working against it.
My Agent Session
Prize Categories
Best Use of Gemma. Gemma 2 2B is the creative engine of the whole product. It is the smallest model that still writes a vivid, structured, location-aware field guide, and it runs locally with format: "json". I treat its weaknesses as design constraints: a strict schema, a normaliser that pads and coerces the output, and a labelled template fallback. That is what makes a 2B model reliable enough to build a product on.
Best Use of ElevenLabs. Voice is the interface, not a bolt-on. The eyes-free briefing is what lets you actually lock the phone and walk. I use eleven_multilingual_v2 for a warm, natural narration of each guide, and the Web Speech API fallback means the feature degrades gracefully instead of failing.
Best Use of MongoDB Atlas. Atlas holds the shared guide cache. A guide is generated once by Gemma and stored with an upsert keyed on spot and location, so every later request, from any device or backend instance, gets it instantly instead of waiting on a 2B model. The server keeps an in-memory fallback so the demo never depends on one connection.
Top comments (0)