2026-06-17 11:42:50 +10:00
2026-06-17 11:42:50 +10:00

Newbury Nights

A fan-made AR ghost-hunting web app — a tribute to the AR mechanics of the LEGO® Hidden Side™ app. The focus is the Hunter Mode AR loop: raise your phone as a ghost detector, scan the colour wheel to uncover gloom, lock on, and blast ghosts down before your battery dies.

Fan-made tribute. Not affiliated with, sponsored by, or endorsed by the LEGO Group. LEGO® and Hidden Side™ are trademarks of the LEGO Group.

What's inside

  • Backend — Node + Express 5, SQLite via better-sqlite3, bcrypt password hashing, JWT auth (8h), multer image uploads.
  • Frontend — Three.js (ES module import maps via CDN), getUserMedia camera passthrough with DeviceOrientation gyro look (iOS Safari friendly), BarcodeDetector QR scanning with a manual code fallback.
  • Ghost rendering — animated-GIF billboards (texture pumped via texture.needsUpdate each frame) when a ghost has an uploaded image; procedural Three.js wisp meshes otherwise.
  • Spawning — rarity-weighted: ★ common down to ★★★★ legendary.
  • Admin panel (/admin) — JWT-gated. Upload / enable / disable / delete ghost images, create sets with scan codes linked to ghost rosters (many-to-many via set_ghosts).
  • Public endpointGET /api/scan/:code requires no auth and returns a set's ghost roster.

Data

The ghost roster, stats, abilities, and boss→set references are seeded from data/*.json, which are generated from the source spreadsheet by scripts/extract_ghosts.py. Three ghost types (red / yellow / blue) and four rarity tiers drive the colour-wheel and damage mechanics.

To regenerate the JSON from a spreadsheet:

python3 scripts/extract_ghosts.py path/to/Ghost_Data.xlsx data

Setup

Requires Node 18+ and build-essential on Ubuntu (so better-sqlite3 can compile its native module):

sudo apt-get install -y build-essential
npm install
cp .env.example .env        # then edit JWT_SECRET and admin creds
npm run seed                # creates the DB, seeds ghosts/sets, bootstraps admin
npm start

The app listens on PORT (default 3000) over plain HTTP — nginx terminates HTTPS in front of it. app.set('trust proxy', 1) is set so secure cookies work behind the proxy.

Re-running npm run seed is safe: it only seeds ghost/set data when the ghosts table is empty, so it won't clobber admin edits. It always ensures an admin user exists.

nginx

See deploy/nginx.conf.example for a reverse-proxy block. Camera and gyro APIs require a secure context, so the site must be served over HTTPS (which nginx already handles for you).

API quick reference

Public:

  • GET /api/scan/:code — set roster + boss for a scan code
  • GET /api/freehunt?n=&type= — rarity-weighted random spawns
  • GET /api/ghosts?type=&rarity=&boss= — public ghost index (enabled only)
  • GET /api/abilities — ability reference

Auth:

  • POST /auth/login · POST /auth/logout · GET /auth/me · POST /auth/change-password

Admin (JWT required):

  • GET/POST /api/admin/ghosts, PATCH/DELETE /api/admin/ghosts/:id, POST /api/admin/ghosts/:id/image
  • GET/POST /api/admin/sets, PATCH/DELETE /api/admin/sets/:id, PUT /api/admin/sets/:id/roster

Project layout

server.js              Express app + static hosting
db/index.js            SQLite schema + connection
routes/                auth, public api, admin api, auth middleware
scripts/extract_ghosts.py   xlsx -> data/*.json
scripts/seed.js        seed DB from data/*.json + bootstrap admin
data/                  ghosts.json, abilities.json, sets.json
public/                index.html (game), admin.html, css/, js/
uploads/               uploaded ghost billboards (gitignored)
S
Description
Fan-made AR ghost-hunting web app — tribute to LEGO Hidden Side AR mechanics. Node/Express + SQLite + Three.js. Not affiliated with the LEGO Group.
Readme 286 KiB
Languages
JavaScript 58.7%
CSS 19.4%
HTML 15.5%
Python 6.4%