Nerd stuff
Crypto by line of sight, GPS by complex numbers.
A festival field has ten thousand phones and no bandwidth, so the app assumes the network is gone: the entire shell and all festival data ship in the bundle, and everything personal stays on the device. What does cross the network is either anonymous or encrypted.
The service worker precaches the complete app shell — every chunk, icon and the hand-drawn map — and the line-up, timetable and POIs are compiled into the bundle as code, not fetched. Votes queue in localStorage and flush when the network returns. Even the classic deploy-while-open black screen is handled: a failed dynamic import (stale chunk hash) triggers one forced reload per 30 seconds, max.
Buddy pairing generates a 256-bit AES-GCM key that exists only inside a QR code held between two phones — it never touches the network and the server cannot derive it. The relay is a deliberate post office: PUT/GET an opaque blob under a pair id, 96 KB cap, no accounts. Even unpairing is encrypted: the leaver writes a sealed tombstone that only the other phone can read — the server cannot tell a goodbye from a profile update.
The wallet scans your e-ticket with ZXing (with a lazily-loaded jsQR second opinion for grainy screenshots), then stores the raw payload plus the barcode format. At the gate it re-renders through bwip-js as the identical symbology — Code 128 stays Code 128, ITF stays ITF — so the scanner sees exactly what the ticket shop printed, not a QR-ified copy.
The festival map is a drawing, not geodata. The app fits a similarity transform (rotate + scale + translate) between GPS and map pixels as a least-squares problem in the complex plane — one complex multiply does the whole rotation and scaling, and metres-per-pixel falls out of the same fit for free, which sizes the accuracy circle. Two anchors suffice, and there is a field-calibration mode: stand somewhere, tap where you are.
Set times are stored as minutes past midnight that happily exceed 1440, so a 24:30 set sorts and clashes correctly without date math. A festival day runs 06:00 to 06:00, so the 3 a.m. beer counts toward yesterday. The booze-o-meter walks your drink log with real per-drink unit values from the actual bar menu and burns one unit per hour — explicitly labelled a joke, not a health instrument. And you can only rate a band ten minutes into their set: listen first, shout later.