Live POTA & SOTA spots and a full QSO logbook in a single-file PWA — installable, offline-capable, and account-free.
- Overview
- Key features
- Architecture
- Getting started
- Using PileUp
- Running the tests
- Contributing
- License
PileUp is an operating aid for amateur radio operators who hunt Parks on the Air (POTA) and Summits on the Air (SOTA) activations. It combines a live spot feed with a QSO logbook in one screen, so the chase-and-log loop is a single tap instead of a juggle between a spotting site and separate logging software.
The problems it solves:
| Problem | PileUp's answer |
|---|---|
| Spotting sites and loggers are separate tools | Tap + log on any spot — the QSO form is pre-filled with frequency, callsign, mode, and reference |
| Most loggers need accounts, installs, or licenses | One URL. No account, no tracking, no app store — data lives in your browser's local storage |
| Field operation means flaky or absent connectivity | Full PWA: installs to the home screen, caches its shell and last spot fetch, works offline |
| Working the same station twice wastes band time | Duplicate warning on entry, and worked callsigns are struck through in the spot list until the UTC day rolls over |
| Logs are useless if you can't get them out | One-tap CSV and ADIF 3.1.4 export with POTA_REF / SOTA_REF tags for award submission |
Why it stands out: the entire application is one HTML file with zero runtime dependencies — no framework, no bundler, no build step. It loads fast on weak cell coverage, is auditable in a single read, and will still work when today's framework churn is long forgotten.
| Area | Functionality |
|---|---|
| Spot feed | Live POTA + SOTA spots in compact expandable rows, auto-refreshed every 60 s (pauses when hidden or repeatedly failing) |
| Filtering | Band chips, mode chips (CW / Phone / Data), free-text search; sort by frequency or age; settings persist |
| Navigation aids | Distance (km) and bearing (°) to every activation, computed from your saved coordinates |
| Worked tracking | Logged callsigns struck through in the spot list until 0000 UTC; duplicate warning when logging |
| Logbook | Pre-filled entries from spots, manual entry, edit/delete, live band-aware search (20m matches 14 MHz) |
| Callsign lookup | Type a call in the log form and press Enter to auto-fill the operator's name plus QTH/grid (free public database, keyless) |
| Hunter stats | QSOs, unique calls, refs, active days; per-band bars and per-mode counts, live from the log |
| Map | Full-screen world map + azimuthal "QTH radar" views of live spots and logged contacts — band-colored, pinch/zoom/pan, embedded coastline, zero network needed |
| Export | CSV (spreadsheet-ready) and ADIF 3.1.4 (POTA_REF / SOTA_REF included) |
| Data safety | One-tap JSON backup of everything; restore or import ADIF logs with duplicate-safe merging; persistent-storage request |
| Station tools | Callsign + lat/lng + Maidenhead grid with GPS auto-locate |
| UI | Dark/light themes, three font sizes, 12/24 h clock, kiosk mode (fullscreen + wake-lock); dual UTC (Zulu) + local LED clocks with seconds |
| Responsive | One layout from 320 px phones to desktop — bottom nav on mobile, a left sidebar on wide screens; spot rows shed columns to keep full callsigns visible |
| Offline | Service-worker shell cache, versioned; last spot fetch cached for offline reload |
PileUp is a single responsive layout that runs from a 320 px phone up to a desktop browser. On phones (shown above) you get a bottom tab bar and one comfortable column; on wide screens the tabs move to a left sidebar and the content stays readable rather than stretching. Spot rows measure the actual width and shed columns as needed, so a full callsign is never truncated at any size.
PileUp/
├── index.html # The entire app — markup, styles, and logic in one file
├── sw.js # Service worker: versioned offline cache of the app shell
├── manifest.webmanifest # PWA manifest (icons, theme, display mode)
├── icons/ # App icons (SVG + PNG) and social-preview image
├── docs/ # Screenshots used by this README
├── test/
│ └── smoke.mjs # Smoke suite: syntax, DOM-id coverage, jsdom boot
└── .github/workflows/smoke.yml # CI — runs the smoke suite on every push and PR
Inside index.html, the script section is organized as: constants and reference data (band plan, storage keys) → helpers (Maidenhead grid, haversine distance/bearing, formatting) → spot fetch/normalize/render → logbook CRUD and exports → station tools and preferences.
Data flow: spots are fetched from the public POTA and SOTA APIs. If a browser blocks the cross-origin request (common in iOS in-app browsers), the fetch falls back through a relay chain — allorigins → corsproxy.io. A Content-Security-Policy meta tag pins network access to exactly these hosts.
| Source | Endpoint |
|---|---|
| POTA | https://api.pota.app/spot/activator |
| SOTA | https://api-db2.sota.org.uk/api/spots/40/all |
Persistence: everything is localStorage under pileup_*_v1 keys (preferences, log, station location, cached spots). Nothing leaves the device except the spot-feed requests.
Open https://cdburgess75.github.io/PileUp/ — that's it. You don't have to install anything; installing just adds a home-screen icon, offline use, and fullscreen. To pin it as an app:
| Platform | Steps |
|---|---|
| Android / desktop | Tap the Install button PileUp shows (or the browser's install icon) — one tap |
| iOS / iPadOS | Safari → Share → Add to Home Screen (Apple allows no one-tap install) |
There's also a QR code on the Tools → Share card — point another operator's phone at it to open PileUp instantly.
Prerequisites: Node.js ≥ 18 (for the test suite only — the app itself needs nothing but a browser). No API keys or environment variables are required.
git clone https://github.com/cdburgess75/PileUp.git
cd PileUp
npm install # dev-only dependency: jsdom
npx serve . # or python3 -m http.server — any static server worksOpen http://localhost:3000 (or just open index.html directly — the app runs from file:// too, minus the service worker).
The app has four tabs along the bottom: Spots, Log, Map, and Tools. The version you're running is shown under the PILEUP title, and an update banner slides up from the bottom whenever a new version is deployed — tap ⬆ Update to apply it.
Do this once so distances, bearings, and your grid square work everywhere.
- Open Tools → ◆ My station.
- Enter your callsign.
- Tap ◉ Use GPS to fill your coordinates automatically, or type latitude/longitude by hand.
- Tap Save. Your Maidenhead grid (e.g.
FN42li) appears in the header, and every spot now shows its distance and bearing from you.
Also on the Tools tab: dark/light theme (the ☀/☾ in the header toggles it too), font size, 12/24-hour clock, Kiosk mode (fullscreen with screen-wake for a shack dashboard), and the Backup & restore card (see below).
- Pick a program: POTA · Parks or SOTA · Summits at the top.
- Rows are collapsed by default to one line — frequency, callsign, mode, band, park/summit reference, and age. Tap a row to expand it for the full detail (park name, distance, bearing, comments); tap again to collapse.
- To log a contact, tap the + button — on the collapsed row or inside the expanded card. The QSO form opens pre-filled with frequency, callsign, mode, and reference; add your signal reports and Save.
- Narrow the list: mode chips (CW / Phone / Data), the colored band chips, or the search box (matches callsign, reference, name, state, mode).
- Sort by frequency or age with the button next to the search box.
- Worked indicator: any station you've already logged today (UTC) shows struck through with a green ✓ until 0000 UTC, so you don't work a dupe. The app also warns you if you try.
- The list auto-refreshes every 60 seconds; the ⟳ button forces a refresh. Spots older than 45 minutes dim.
- Filter by program: the All / POTA / SOTA / Other switch at the top. "Other" is any contact without a park or summit reference — ragchews, nets, DX.
- Search across callsign, reference, mode, date, notes, frequency, and band — multiple terms narrow further (
cw 20m= CW contacts on 20 m). It combines with the program filter. - Add a contact by hand with + New entry; edit or delete any entry from its row.
- Callsign lookup: type a callsign in the log form and press Enter (or leave the field) — PileUp fills the operator's Name and shows their QTH and grid, using a free public database (best coverage for US/FCC calls). A name you've already typed is never overwritten, and the Name travels with CSV and ADIF export.
- ▤ Stats opens a live dashboard: total QSOs, unique callsigns, unique references, active days, plus bars by band and counts by mode.
- Export for awards or other software:
- Export CSV — opens in any spreadsheet.
- Export ADIF — the standard log format; imports into LoTW, QRZ, and most logging programs, with
POTA_REF/SOTA_REFtags included.
- World shows everything geographically on a full-screen map; Radar shows an azimuthal view centered on your station, where a marker's angle is the true bearing to point your antenna.
- Hollow rings are live spots, colored by band; green dots are your logged contacts; the amber dot is you. The All / Live spots / Logged chips filter which layers show.
- Pinch to zoom, drag to pan, double-tap to zoom in (mouse wheel and double-click on desktop). The ⟲ button returns to the home view.
- Tap ⛶ for full screen — the map fills the whole screen; tap ✕ or press Esc to exit. Zoom and pan keep working.
- Tap any marker for its callsign, reference, distance, bearing, and date.
Everything works offline and stays on your device — no account, and nothing leaves your phone except the spot-feed requests.
Your log lives in the browser, so back it up:
- ⬇ Backup saves a JSON file with your whole log, station, and settings.
- ⬆ Restore / Import reads that backup or an ADIF file from other logging software. Contacts merge in, duplicates are skipped, and your current station is never overwritten.
PileUp keeps your log on your device — it doesn't sync to POTA. To submit it, Export ADIF and upload that file to the service you use; the card links straight to the POTA, QRZ, LoTW, and Club Log upload pages. Note that POTA hunter credit is automatic once the activator uploads their log — you only need to submit a log if you activated a park.
npm testThe smoke suite (test/smoke.mjs) runs four checks in about a second:
| # | Check | Catches |
|---|---|---|
| 1 | Script parses (new Function) |
Syntax errors anywhere in the app |
| 2 | Every getElementById call has a matching id="" |
Typos between markup and logic |
| 3 | Full boot inside jsdom with stubbed fetch/localStorage |
Runtime errors on startup, missing elements |
| 4 | VERSION === sw.js cache name === README badge |
Version drift between app, cache, and docs |
The same suite runs in CI on every push and pull request via GitHub Actions.
Contributions are welcome. To keep the project true to its design goals:
- Open an issue first for anything beyond a small fix, so the approach can be agreed before you invest time.
- Respect the constraints — single file, zero runtime dependencies, no build step. If a feature needs a framework, it doesn't fit this project.
- Match the existing style — compact vanilla JS, CSS custom properties for theming,
localStoragefor persistence. - Run
npm testbefore pushing; CI runs the same suite on your PR. - Branch from
main, keep PRs focused on one change, and include a screenshot for UI changes.
Released under the MIT License.



