This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
Touch Grass — say how much time and energy you've got, and it gives you one short, concrete dare to go do outside. Finish it, snap a photo as proof, and it counts toward a daily streak. No feed, no infinite scroll, no "just one more dare" loop — one dare, go outside, come back later.
It's a PWA: install it, and after the first load it keeps working with no signal, because the shell and the model are both cached by a service worker.
Who it's for: anyone who opens their phone meaning to do one quick thing and surfaces 40 minutes later having done none of them. Tell it you've got 10 minutes and low energy, it won't give you a half-marathon.
Demo
🔗 Live: https://touch-grass-bkii.onrender.com
💻 Code: https://github.com/ibrahim-iqbal/touch-grass
The whole app is a little outdoor world you stand in. The painted pixel-art scene fills the screen, your character stands in the grass, and the controls sit at the edges like a game HUD — nothing floats in a box over the middle.
Flip the theme and the same world re-lights from a cozy night to a warm daytime — the backdrop art crossfades, the moon slides out and the sun comes up.
Tap the mic, say your time and energy, and a dare slides up from the bottom in a dialogue ribbon — tagged with roughly how long it takes and how much it'll cost you — with a "prove it outside" button that opens the camera.
ibrahim-iqbal
/
touch-grass
voice dares that get you outside — local gemma, zero backend
touch grass
voice dares that get you outside. say how much time and energy you have it gives you a short outdoor dare, you snap a photo as proof when you're done.
built for the hacktoberfest open-source ai challenge, week 1: "touch grass."
why open
- dare generation runs on-device with gemma 3 270m via transformers.js, not a hosted api
- works after the first load even with no signal — service worker caches the page and the model
- nothing you say or type leaves your browser; your hugging face token and elevenlabs key (both optional) are stored locally and used directly from your device
- swap the model, edit
dares.json, or rip out the generation step entirely — it's all plain js
running it
just a static site, no build step.
python3 -m http.server 8000
open localhost:8000. voice input needs chrome or edge (web speech api support
is patchy elsewhere)…
Voice input needs Chrome or Edge (Web Speech API support is patchy elsewhere). Camera proof needs HTTPS or localhost — both are satisfied on the live link.
How I Built It
Plain HTML/CSS/JS, no framework, no build step, no backend. A few deliberate choices:
-
Default mode needs zero setup. Say your time/energy/place, it matches you against a small curated dare bank (
dares.json) and speaks the result with the browser's ownspeechSynthesis. This always works, offline, with nothing to configure. - Optional smart layer: Gemma 3 270M via transformers.js. Paste a Hugging Face token into settings and dare generation switches to the model, running entirely in your browser via WASM — not a hosted API call. Nothing about your request ever leaves your device.
- Optional voice layer: ElevenLabs, same pattern — your key, used directly from your browser, swappable back to the built-in voice at any time.
- The UI is a game HUD, not a form. The painted pixel-art world fills the screen and your character stands in it; the level, stats and controls pin to the corners like an RTS, and each dare appears in a slide-up dialogue ribbon like an RPG. The backdrop art is day/night and crossfades when you toggle the theme, with a sliding sun/moon, drifting clouds and foliage that fills in as your streak grows.
- A service worker caches the app shell so the whole thing — including the model, once downloaded — works with no connection after the first load. That's the actual point of the "touch grass" theme: the app should survive exactly the situation where you're outside with no signal, which is where it's most useful.
- Cross-origin isolation (COOP/COEP) configured on the Render deploy so the WASM runtime gets the full, fast execution path rather than a degraded fallback.
-
Camera proof uses
getUserMedia+ canvas, with a defensive timeout — confirmed by hand thatgetUserMediacan hang indefinitely on a device with no camera rather than reject, which otherwise traps the UI with no way out. -
Deployed on Render as a static site, auto-deploying from
mainon every push.
Why Does Open Innovation Matter?
Two reasons this specific shape only works because the model is open-weight:
It can genuinely run with nothing hosted by me. There's no backend, no API key of mine in the loop, no bill that scales with usage. A user who wants the smarter dare generation brings their own free Hugging Face token and the model downloads and runs on their device. I can't see their request, because it never reaches a server I control. A closed API would force a hosted proxy — which means a cost center, a privacy boundary I'd be asking users to trust, and a single point of failure the moment I stop paying the bill.
It made "fully optional" actually honest. Because Gemma 3 270M is small enough to run client-side, the "smart" mode isn't a crippled free tier nudging you toward a paywall — it's a real optional upgrade with a real reliability floor underneath it (the curated dare bank) that works identically whether or not you ever touch the settings gear. That's a hard thing to build honestly on top of a closed, metered API; it's the default shape of an open-weight one.
Prize Categories
- Best Use of Render — static site, deployed and auto-deploying from this repo, with COOP/COEP headers configured for the WASM runtime
- Best Use of Gemma — Gemma 3 270M running fully client-side via transformers.js for live dare generation
- Best Use of ElevenLabs — optional nicer voice output for the spoken dare, key used directly from the browser




Top comments (1)
tr.ee/dev-to