This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
What I Built
I built Touch Grass because identifying a bird call shouldn't mean uploading your life to someone else's server. Most nature apps work like this: you record audio, it ships to a cloud API, you wait, you get a result, and somewhere a company logs that you were near a lake at 7am. I didn't want any of that.
Touch Grass is a Progressive Web App that runs open-weight AI models entirely inside the browser. You record a 3-second bird clip, the app converts it to a mel spectrogram locally, and an ONNX model predicts the species — all without a network call after the initial download. The same approach works for plants and insects from photos. Once the model is cached in IndexedDB, the app works completely offline. No accounts, no tracking, no "please sign in."
It's for people who actually want to spend time outside. The whole philosophy is: identify quickly, then put the phone back in your pocket and enjoy the trees.
Demo
Live site: https://touch-grass-143.pages.dev
Install it like a normal app — the service worker caches the shell, the manifest is set up, and offline identification works after the first model download. I tested the PWA install on Android Chrome: add to home screen, open it, allow the mic, record 3 seconds, and you'll see results come back from the local BirdNET model with no signal required. I haven't done a long birding trip yet, but the whole point is that it works exactly where the map stops — on the trail with no bars.
Code
Gaurav5189
/
touch-grass
A Progressive Web App that helps people identify birds, plants, and insects using open-weight AI models running entirely on-device. Works offline, keeps all data local, and minimizes screen time so you can get back to nature.
Touch Grass — Local-First Nature Identification PWA
A Progressive Web App that helps people identify birds, plants, and insects using open-weight AI models running entirely on-device. Works offline, keeps all data local, and minimizes screen time so you can get back to nature.
Philosophy: Identify quickly, then put the phone away and enjoy the outdoors.
Features
- Bird Call ID — Record 3-second audio clips, identify species via BirdNET-ONNX (~20MB, quantized, 3000+ species), all on-device.
- Plant & Insect Photo ID — Capture or import a photo, get top-5 matches from a MobileNetV3 model (~5MB), with descriptions and similar species.
- Fully Offline — After initial model downloads, no internet required. Service worker caches the app shell; IndexedDB stores observations and model cache.
- Private by Default — No accounts, no tracking, no server requests for inference. Optional GPS tagging is opt-in only.
- Observation History — Auto-save after every identification. Filter, search, and export…
Repo: Gaurav5189/touch-grass
Key parts worth looking at:
-
src/features/bird-id/— AudioRecorder hook, BirdResults UI, RegionFilter -
src/features/photo-id/— CameraCapture, GalleryImport, MobileNet inference stub -
src/features/history/— Observation list, filters, CSV/JSON/iNaturalist export -
src/workers/inference.worker.ts— ONNX Runtime Web with WebGPU preferred, WASM fallback -
public/manifest.json+vite-plugin-pwa— full PWA setup
How I Built It
Stack is straightforward: React 18, TypeScript 5.3, Vite 5, Tailwind 3.4. Nothing exotic. The interesting part is what runs inside the browser.
-
Bird identification: BirdNET-ONNX (quantized INT8, ~20MB, Apache-2.0, 3000+ species). It runs in a Web Worker via
onnxruntime-web, with WebGPU first and WASM fallback. - Plant / insect identification: MobileNetV3 + custom iNaturalist head (~5MB, Apache-2.0 / CC-BY data).
-
Offline storage: IndexedDB (
idb) for observations and model cache; Cache API for the app shell. - Privacy: Zero server requests for inference. Optional GPS is opt-in only. No analytics, no telemetry.
I used OpenCode for the main structure — scaffolding the PWA, setting up the folder layout, and getting the build pipeline clean (CI with typecheck, lint, build). Then Antigravity helped with refinements and bug fixes — fixing TypeScript casting issues with navigator.gpu, cleaning up import paths for onnxruntime-web.d.ts, resolving build errors in useModelDownload, and fixing the Playwright / Vitest conflict so E2E tests could run separately.
The model strategy is open-weight all the way: BirdNET from the BirdNET-Analyzer repo, MobileNet weights fine-tuned on public iNaturalist data. All licenses documented in DOCS/RULES.md. No proprietary or GPL dependencies.
Why Does Open Innovation Matter?
If this was a closed API, you couldn't swap the bird model for a regional 8MB pack. You couldn't upload your own ONNX file for a custom species set. You couldn't verify that your audio isn't being stored somewhere else. And you definitely couldn't run it in a cabin with no signal.
Open-weight models make this possible because the weights are files you download, not services you call. The BirdNET team released their quantized model under Apache-2.0. The iNaturalist dataset is CC-BY. That means I can bundle them into a browser app without licensing anxiety or vendor lock-in. Open innovation here isn't a marketing line — it's the reason the app can work offline, stay private, and let users control which model they run.
Prize Categories
- Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
- Open Source / Open Weight Models
- Open Source AI Challenge
Top comments (1)
doing the mel spectrogram in-browser is the bit most people skip. how big is the birdnet onnx file once it's cached in indexeddb, and did you see wasm vs webgpu make any real gap on older android phones?