This is a submission for the Hacktoberfest Open-Source AI Challenge: Week 1 — Touch Grass.
What I Built
Side Quest turns the streets around you into a short scavenger hunt.
You tell it how long you have (15 to 60 minutes) and what kind of clues you like (playful, poetic, detective, or for kids). It finds a few interesting places within walking distance (a temple, a mural, an old bookshop, a pond) and plans a loop that fits your time. Then Gemma, running inside your browser, writes a riddle for each stop.
Then you put the phone away.
When you think you've found the place, you check in. The app shows you one small thing to notice before you move on: count the bells, find the oldest crack in the wall, listen for one sound that repeats. At the end, Gemma writes a three-line postcard about the walk. It is saved only on your device.
Most "AI + outdoors" apps keep you looking at the screen while you're outside. I wanted the opposite: the screen is on for about ten seconds per stop, and the rest of the time you're looking at the street.
Demo
- Live app: https://hacktoberfest-26.vercel.app
- Try it from your desk: https://hacktoberfest-26.vercel.app/?at=18.5195,73.8553&sim starts you at Shaniwar Wada in Pune and adds a Teleport button, so you can play a whole quest without walking.
You need a browser with WebGPU for the model: Chrome on Android 12+, or Chrome/Edge on Windows or macOS. The first run downloads Gemma once (~0.7 GB). Without WebGPU, the walk still works with simple built-in clues.
Code
Side Quest
A short walking scavenger hunt around wherever you're standing. The clues are written by Gemma, running entirely in your browser (WebGPU via WebLLM) Your location never leaves the phone.
Built for the Hacktoberfest Open-Source AI Challenge, Week 1: Touch Grass.
How it works
- Pick how long you have (15–60 min) and a clue style (playful, poetic, detective, for kids).
- The app finds interesting places nearby from OpenStreetMap and Wikipedia: parks, temples, murals, old trees, bookshops, historic buildings.
- A small planner picks a loop of 2–4 varied stops that fits your time.
- Gemma writes a riddle for each stop plus one small thing to notice when you get there.
- Put the phone away. Check in when you think you've found it (it buzzes if the screen is on).
- At the end, Gemma writes you a three-line postcard of the walk, saved only on your device.
Why
…MIT licensed. About 1,000 lines of TypeScript, no framework.
How It Works
your phone ──► snap location to a ~1 km grid square
──► OpenStreetMap + Wikipedia: "what's interesting in this square?"
──► planner: pick 2–4 varied stops that fit your time
──► Gemma (WebGPU, in a Web Worker): write a riddle per stop
──► walk · check in · notice something · next clue
──► Gemma: write a postcard of the walk → saved on the device
1. Finding places, with two free and keyless sources. OpenStreetMap (via the public Overpass API) gives the small stuff: parks, temples, murals, named bakeries, old trees. Wikipedia's geosearch API gives named landmarks with a line of description, which turns out to be great riddle material. Near Shaniwar Wada in Pune, one query returned 188 candidate places.
2. Planning the loop, deliberately without AI. A small randomised search tries 400 greedy loops and keeps the one with the most stops, the most variety (it heavily penalises a second temple in a row), and a total time closest to what you asked for. Walking speed is 70 m/min and streets get a 1.3× detour factor. A planner is the right tool here: it's deterministic and checkable, and a 1B model is bad at geometry.
3. Writing the clues, which is where the model earns its place. Each stop's OpenStreetMap tags and Wikipedia summary go to Gemma with one job: hint at the place without naming it, and suggest one thing to notice with your senses.
const prompt = `You write clues for a real-world walking scavenger hunt.
The walker must find this place: ${describe(place)}.
Write:
1. A riddle of at most two short sentences that hints at the place without saying its name.
Use what someone would see, hear or smell there.
2. One small thing to notice once they arrive, using their senses. One sentence.
Tone: ${VOICE[vibe]}. No emoji. No hashtags.
Reply in exactly this format and nothing else:
CLUE: <riddle>
NOTICE: <thing to notice>`;
If the model's answer can't be parsed, or it gives the name away, a template clue takes over and the screen says so honestly.
Why Open Matters Here
This is the part I care about most.
A hosted model would need your live location for every clue. Here nothing about where you're standing ever reaches an AI provider, because the AI provider is your own GPU. Gemma runs in the browser through WebLLM on WebGPU, inside a Web Worker.
The only network requests are the map lookups, and even those never carry your exact position. The query box is snapped outward to a 0.01° grid (about 1.1 km) before it leaves the phone:
/**
* A bounding box snapped outward to a 0.01° grid (~1.1 km). This is the only
* thing about your location that ever leaves the phone: the map server learns
* which grid squares you asked about, never the point you're standing on.
*/
export function snappedBbox(c: LatLng, radiusM: number) { … }
Everyone standing anywhere in the same square sends the identical request.
The rest follows from using open pieces:
- It costs nothing to run. There are no API keys and no accounts. It's a static site plus one tiny cached relay function on Vercel's free tier. With a closed model API I'd be paying per riddle, or asking you for a key.
- It works offline after the first run. The model weights are cached by the browser, and map results are cached per area for a week, so a second walk in the same neighbourhood works with no signal at all.
-
The model is swappable. You can pick Gemma 3 1B (~0.7 GB, sized for phones) or Gemma 2 2B (~1.4 GB, better clues on a laptop). Swapping is one line in
MODELS.
What Went Wrong (and What I Changed)
-
The public Overpass server turns away browsers. It worked from my dev machine, then failed on the deployed site:
overpass-api.deanswers anonymous browser requests with406 Not Acceptableand no CORS header, so the browser reports a CORS error that hides the real reason. The server wants clients to identify themselves, and a browser isn't allowed to setUser-Agent. The fix is a ~50-line Vercel function,api/places.ts. It accepts only a grid-snapped box (anything off-grid or over ~9 km is rejected), sends the fixed query with an identifyingUser-Agent, falls back across four mirrors, and setss-maxage=604800so the CDN answers repeat boxes for a week without touching OpenStreetMap at all. -
Even when it identified itself, Overpass was often overloaded, with
504 Gateway Timeoutmore often than not during testing. So OpenStreetMap and Wikipedia run in parallel, the relay starts its mirrors a few seconds apart and takes the first complete answer, and either source alone is enough to plan a walk. -
Overpass sometimes "succeeds" with nothing. Under load it can give up mid-query and still answer
200 OKwith an empty list and a remark saying it timed out. The app read that as "nothing here". Those answers are now treated as failures, and an empty area is only cached for an hour. -
Wikipedia was silently dropping most results. The geosearch generator returned 47 pages, but only 10 had coordinates attached.
prop=coordinateshas its own limit (colimit) that defaults to 10. Addingcolimit=maxtook the area from 1 usable landmark to dozens. -
'gpu' in navigatoris not enough. A browser can expose WebGPU and still have no usable adapter. Chrome on Linux does exactly that by default. The check now actually callsrequestAdapter(), and if the model can't start, the walk still happens with built-in clues instead of an error screen. - Laptops don't really know where they are. Desktop browsers locate by Wi-Fi or IP and can be kilometres out. When a lookup comes back empty, the app now says how coarse your location is and offers a demo walk instead of a dead end.
- Browsers pause GPS when the screen is off. I can only buzz you on arrival while the screen is on. Otherwise you tap I think I'm here. Honestly, that turned out to be part of the fun.
What's Next
- Fine-tune a small Gemma on good riddles, since the 1B model's clues vary in quality.
- A "share this quest" link, so friends can walk the same route without either of you sending your location anywhere.
- Hindi and Marathi clues.
Prize Categories
Best Use of Gemma. Gemma is the only AI in the app. It runs entirely on-device via WebGPU and writes every clue and postcard.
Map data © OpenStreetMap contributors (ODbL). Wikipedia text CC BY-SA. Built with AI assistance (Claude Code) for the scaffolding and debugging.
Top comments (1)
Concept "never learns where you are" rất thú vị — thay vì thu thập dữ liệu vị trí để "cá nhân hóa", nó dùng randomness hoặc heuristic cục bộ (thời tiết, giờ, độ an toàn của khu vực chung) để gợi ý lộ trình. Cách này tránh hẳn việc phải xin permission location, giảm surface area cho tracking, và vẫn đạt mục đích "touch grass".
Một vài góc kỹ thuật có thể đào sâu thêm:
Hacktoberfest week 1 focus "touch grass" hợp lý — nhiều dev ngồi debug 10h/ngày quên rằng environment context (ánh sáng, không khí, chuyển động) ảnh hưởng trực tiếp đến cognitive load. Tool này nếu open-source tốt có thể thành reference implementation cho privacy-first wellness apps.
Curious: bạn handle edge case "user ở vùng vắng ít POI" như thế nào? Fallback về (site: labagent .tech)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.