Abstract — Modern browsers already reach the camera, GPS, microphone, files and the OS share sheet directly from a web page. This is a tiny gallery of those capabilities, each a self-contained demo, made to show how far the web goes before you reach for a local-first native build. It ships as a single server-rendered Go binary and installs as a PWA — and the same page works on both iOS Safari and Android Chrome.
A client asked me to build a native app for something a web page does on its own. Rather than argue in the abstract, I built the demo: a handful of device features you'd assume need an app, each working in the browser, on a real phone, with nothing installed. It's live at wf.manulobato.com — open it on your phone.
What's in it
Each tile is one capability, kept deliberately small so the mechanism is the whole point:
- Media capture — camera, gallery and video uploads driven purely by a file input's
accept,captureandmultipleattributes. - GPS —
getCurrentPosition()for latitude, longitude and accuracy, posted back to the server. - Phone —
tel:,sms:andmailto:links that open the native dialer, messages and mail apps. - Web Share —
navigator.share()opening the real OS share sheet, including sharing a server-generated file. - Audio recording —
getUserMedia+MediaRecorder, uploaded asFormData. - File download, screen orientation, and a connectivity monitor that combines the browser's online/offline events with a server heartbeat.
How it's built
The whole thing is one Go binary: server-rendered HTML with templ, a classless/semantic stylesheet (Oat) plus a thin project shim, Lucide icons inlined as static SVG (no runtime JS, no layout shift), and Datastar for the live bits — hot reload and toasts over a single SSE connection. It's a PWA (manifest + service worker), supports light/dark, and animates page changes with cross-document view transitions.
No JS build step, no framework, no app store review. It cross-compiles to a stripped static Linux binary and deploys by copying that one file to a VPS behind Caddy.
The point isn't that you should never build native — it's that "we need an app" deserves a second look. A lot of the time, the browser already did it.