An NFC-powered travel gallery that lives on your fridge. Tap a souvenir fridge magnet on a hidden reader and a framed touchscreen flies a globe to that trip - tracing the actual route, road-trip legs and flight arcs alike - then opens a photo gallery: an auto-rolling carousel, an Apple Photos-style grid, and a "things we ate" section.
Originally inspired by thecodedose's EverAfter (concept only; all code and art here is original). Unlike EverAfter, content is fully runtime-driven: adding a trip is a Drive folder drop, not a rebuild.
magnet (NTAG sticker) -> PN532 reader -> nfc-service -> POST /api/scan
|
Google Drive folder -> rclone pull -> ingest pipeline |
(thumbs, GPS route, globe frames) v
FastAPI server -- WebSocket --> kiosk UI
(serves media + API + kiosk build)
server/— FastAPI: trips API, media serving, WebSocket scan events, photo management, background sync jobkiosk/— React + Vite: the kiosk experience plus the admin at/admin(Sync / Library / Magnets)ingest/— content pipeline: photos in; manifest, thumbnails, GPS-derived route, and the globe animation outnfc-service/— PN532 polling daemon for the Raspberry Pideploy/— Raspberry Pi setup script, systemd units, andpublish_web.sh(static site deploy)library/— runtime data root (gitignored):trips/<slug>/,tags.json
The signature moment. Every trip gets a pre-rendered spin + zoom sprite sheet (buttery on a Pi - no live 3D), and the kiosk overlays pins, place labels, and an animated route using the same orthographic projection math:
- Photo EXIF GPS is clustered into stops (>25 km apart), ordered by timestamp.
- Legs under ~400 km draw as solid ground paths (road trip); longer legs as dashed, bowed flight arcs.
- The zoom level adapts to the trip's spread: a road trip zooms deep into one region, an international hop stays wide.
- No GPS? The folder name is geocoded to a single destination. Wrong guess? Fix it in the admin - the globe re-renders in the background.
# 1. Python env
python3 -m venv .venv && .venv/bin/pip install -r server/requirements.txt
# 2. Generate demo content (3 sample trips with routes)
.venv/bin/python -m ingest.demo_content
# 3. Kiosk build
cd kiosk && npm install && npm run build && cd ..
# 4. Run
.venv/bin/uvicorn server.app.main:app --port 8000Open http://localhost:8000 — press keys 1..9 to simulate magnet scans, u
for an unknown magnet (pairing flow). Admin lives at http://localhost:8000/admin.
For development with hot reload: cd kiosk && npm run dev (proxies API to :8000).
Drop photos into the Drive folder Wanderwall/<Place Name>/ (food shots into
a food/ subfolder), then press Sync in the admin - or locally, drop into
library/inbox/<Place Name>/ and run:
.venv/bin/python -m ingest.ingestRe-syncing the same folder merges new photos into the existing trip. To pair a magnet, just scan the blank NFC sticker on the kiosk and pick the trip.
Every trip is reachable at /trip/<slug> (e.g. /trip/japan-2025; add
?view=globe to play the globe flight first). This is the URL to write onto
each magnet's NTAG215 sticker — one encoding serves both worlds:
- Phone: tapping the magnet pops the trip gallery in the browser — no app,
no kiosk hardware needed. Use a free app like NFC Tools to write the full
public URL (record type "URL", e.g.
https://wanderwall.pages.dev/trip/japan-2025— see "Publishing the website" below), then optionally lock the tag. - Pi kiosk: the PN532 service reads the same URL off the tag, parses the slug, and opens the trip on the wall display. URL-encoded tags need no pairing; blank tags still work via the admin Magnets pairing flow (UID mapping), which also serves as a fallback if a tag's URL slug goes stale.
Slugs are permanent once written to a physical sticker — trip titles can change freely, but renaming a slug breaks the magnets pointing at it.
The public site is a static export — no machine of yours runs in production. Your PC does the work (sync, ingest, admin edits), then one command snapshots everything and pushes it to Cloudflare's CDN:
your PC: Drive sync -> ingest -> admin fixes -> export ──deploy──> Cloudflare Pages (free)
^
phone taps magnet ── opens /trip/<slug>
One-time setup (free Cloudflare account required):
npx wrangler login
npx wrangler pages project create wanderwallPublish (and republish after every new trip or edit):
deploy/publish_web.shThat runs python -m ingest.export_static — which builds the kiosk in static
mode (VITE_STATIC_SITE=1: JSON snapshots instead of live API, no websocket,
no admin) and assembles dist-public/ with api/trips.json,
api/trips/<slug>.json, the photo library, and an SPA fallback for
/trip/<slug> deep links — then deploys the folder with wrangler. Your site
lands at https://wanderwall.pages.dev (custom domains are free in the Pages
dashboard); that's the URL to write on magnet stickers.
Notes:
- The published site is a snapshot: it updates only when you re-run
deploy/publish_web.sh. Admin, Drive sync, and live scan events stay on your PC / the Pi — they never ship publicly. - A static site is public to anyone who has the link. To restrict it, put Cloudflare Access (free tier) in front of the Pages project.
python -m ingest.export_static --skip-build --out <dir>reuses the last kiosk build or changes the output folder.
See deploy/README.md for the full Pi 5 + Touch Display 2 + PN532 wiring and install guide. Google Drive sync uses rclone with your own account (read-only scope; the folder stays private).