URL: https://mkdocs.justinsforge.com/memory/general/reference_justinkrystal_games_hub/
justinkrystal.com Games Hub + Michael 23rd Birthday Page¶
Built 2026-07-02. A Vite + Phaser 3 + TypeScript arcade that builds to static files served by the existing nginx container on media-server (CT102). First game: "Michael The Angler" (a.k.a. Set the Hook), a reaction fishing game (bass +3 / birthday cake +5 / rabid squirrel -2). Plan: justinkrystal-games-hub-2026-07-02.
Layout¶
- Game source (build only, not served):
sites/justinkrystal.com/games-src/(Vite project).vite.config.tssetsbase: '/games/'and outputs to../landing/games/. Entries:index.html(hub) +fishing.html(the game). Pixel art is generated procedurally in Phaser (src/fishing/pixel.ts), no external assets. Fishing scenesrc/fishing/FishingScene.ts; leaderboard clientsrc/fishing/leaderboard.ts; name-entry + board overlaysrc/fishing/overlay.ts. - Built output (served):
sites/justinkrystal.com/landing/games/(committed; deploy rebuilds it). - Birthday page:
sites/justinkrystal.com/landing/michaels23rd/index.htmlatjustinkrystal.com/michaels23rd. Announcement banner (top, not sticky), floating hero (hero.png), Immich photo gallery (photos/01..06.jpg,POSmap pins per-photo crop e.g. football photo03.jpgtocenter top), and the game via an auto-sizing iframe (fitGameFrame()reads the same-origin game height so the leaderboard below the cabinet is never clipped;scrolling="no"; cabinet fills the 552px embed = 15% bigger than the 480px base). OG/iMessage share cardshare.jpg(1200x630) wired viaog:image/twitter:card. Home-page tile in the Misc section oflanding/index.html. - Source images: originals live at
/mnt/workspace/MichaelsBirthday/(Main Pic.pnghero,immich-*/gallery,share pic/OG). Processed into the page folder with ImageMagickconvert(gallery capped 1400px q82; hero 560px; share fit to 1200x630 on#0d0d0f).
Leaderboard backend (extends hub-api Flask)¶
sites/justinkrystal.com/hub-api/app.py (Flask, already the family-hub API). sqlite at /data/leaderboard.db in the hub-data volume.
- POST /api/games/score : body {game, name, score, sig}. Anti-cheat: per-IP rate limit (RATE_LIMIT_PER_MINUTE=10), score sanity cap, name clamp, HMAC-sha256 sig with HUB_SECRET_KEY verified via hmac.compare_digest. KNOWN_GAMES = {"fishing"}.
- GET /api/games/leaderboard?game=fishing&limit=10 : top N {name, score, ts} + total. Unknown game -> 400.
- In prod nginx serves /api same-origin; in Vite dev, vite.config.ts proxies /api to http://127.0.0.1:8085.
Dev workflow (watch live)¶
cd sites/justinkrystal.com/games-src && npm install && npm run dev -> Vite on 0.0.0.0:5180 with HMR. Watch at http://192.168.86.50:5180/fishing.html (Console LAN). Birthday page preview: python3 -m http.server 8091 in landing/ -> http://192.168.86.50:8091/michaels23rd. npm run build = tsc --noEmit && vite build.
Deploy¶
sites/justinkrystal.com/deploy.sh (unchanged pipeline + a prepended games build). It runs npm ci && npm run build in games-src (skips with a loud warn if npm absent), then tars landing/ + hub-api/, scp to finn, pct push to CT102, docker compose up -d. tar extract uses --no-same-owner --no-same-permissions; a couple of harmless utime/mode warnings on the bind-mount top dir remain (extraction still succeeds, chain still returns 0). Verify on the origin, not the public URL: http://192.168.86.74:8085/michaels23rd/ (Cloudflare 403s scripted/bot requests to justinkrystal.com; real browsers are fine).
Adding another game¶
New <name>.html entry in games-src/, add it to rollupOptions.input, build a scene under src/<name>/, add a tile to games-src/index.html, register the game in KNOWN_GAMES if it uses the leaderboard.