Nerd stuff
A keypair is the account.
Tuuut looks like a toy: your loyalty cards, a barcode, a beep. Under the hood it is a small exercise in how much you can leave out — no accounts, no state library, no i18n framework, no CRDTs — while still syncing a family's cards across devices.
On first run every device generates an Ed25519 keypair via WebCrypto, non-extractable, so the private key physically cannot leave the browser. The server knows public keys and nothing that identifies a person. Every API call is signed over a canonical string (method, path, timestamp, nonce, body hash); nonces land in a unique-indexed collection with a TTL, so a replayed request dies on a database constraint rather than on clever code.
Pairing is a QR (or link, for camera-less desktops) carrying a single-use token that is consumed atomically with one find-and-modify. Sync is deliberately boring: a monotonically increasing collection version, delta pulls, and last-write-wins per card — the docs literally say "no CRDT overkill" for a household. The nice touch: deleted cards sync as tombstones that are scrubbed first — name, code and note are blanked, only the fact of deletion travels.
IndexedDB is the source of truth; the service worker caches only the app shell, never card data — a stale cache can render an old UI but never an old barcode. Updates download silently and activate on the next open, never mid-session, for one concrete reason: nobody should lose their barcode while standing at the checkout.
1D codes render via JsBarcode as SVG with tall bars and GS1-spec quiet zones — that is what makes a laser scanner read a phone screen. Scanned UPC-A codes are normalized to EAN-13 so the leading zero stays visible (at unmanned terminals people retype the number; an invisible zero is fatal). Check digits are validated with a home-grown GS1 routine, screen brightness is forced to 100% while a card is shown, and image scanning falls back in layers: native BarcodeDetector, then html5-qrcode, then magnified horizontal strips of the screenshot.
The beep is not an mp3. It is ~20 lines of Web Audio: a 660 Hz sine with a 30 ms attack, 180 ms hold and an exponential 520 ms release — wrapped in a catch that keeps quiet, because no one should ever see an error about a sound effect.