diff --git a/.claude/handoffs/2026-05-19-181212-deployed-as-chess-sethpc-xyz-duplicate.md b/.claude/handoffs/2026-05-19-181212-deployed-as-chess-sethpc-xyz-duplicate.md
new file mode 100644
index 0000000..b21efca
--- /dev/null
+++ b/.claude/handoffs/2026-05-19-181212-deployed-as-chess-sethpc-xyz-duplicate.md
@@ -0,0 +1,272 @@
+# Handoff: Duplicate Chess deployed under chess.sethpc.xyz/duplicate/
+
+## Session Metadata
+- Created: 2026-05-19 18:12:12
+- Project: /home/claude/bin/duplicate_chess
+- Branch: main (all work committed + pushed)
+- Session duration: ~30 minutes (after a fresh-session handoff load + a course
+ correction — the user's "deploy the new game mode" originally read like a
+ blind_chess deploy; clarified mid-session to mean duplicate_chess as a 3rd
+ game-mode card on chess.sethpc.xyz).
+
+### Recent Commits (for context)
+- `cab556e` docs: record live deploy under chess.sethpc.xyz/duplicate/
+- `a581393` fix: page title (was leftover Vite scaffold 'vite-tmp')
+- `8039a8b` build: set vite base to /duplicate/ for sub-path hosting
+- `d2adf7f` docs: session handoff — duplicate chess v1 built and merged
+- `5047ad7` docs: update CLAUDE.md — v1 implemented and merged
+
+Companion change in the sibling repo: `blind_chess @ fef6dcf`
+("feat(client): third landing card linking to duplicate chess") + `33a7cef`
+(docs note in blind_chess CLAUDE.md).
+
+## Handoff Chain
+
+- **Continues from**: [2026-05-19-060141-duplicate-chess-v1-built.md](./2026-05-19-060141-duplicate-chess-v1-built.md)
+ — v1 was code-complete and merged but local-only.
+- **Supersedes**: None.
+
+## Current State Summary
+
+`duplicate_chess` v1 is **live at https://chess.sethpc.xyz/duplicate/** as of
+2026-05-19. It is served as a static sub-app from `/var/www/duplicate-chess/` on
+Caddy CT 600 (192.168.0.185), via a `handle_path /duplicate/*` block inside the
+existing `chess.sethpc.xyz` Caddy site. Vite is built with `base: '/duplicate/'`
+so every asset URL carries the `/duplicate/` prefix and stays inside the static
+handler instead of falling through to the blind_chess Fastify backend on CT 690.
+
+In the sibling `blind_chess` repo, the landing page (`Landing.svelte`) now shows
+a 3rd card — "Duplicate Chess (under development)" — beneath the existing
+friend/AI cards, linking to `/duplicate/`. That client was rebuilt and rsynced
+to CT 690; `blind-chess.service` was restarted to pick up the new bundle.
+
+End-to-end curl evidence:
+- `https://chess.sethpc.xyz/duplicate/` → 200, served `index.html` (title now
+ `duplicate chess`, no longer `vite-tmp`).
+- `https://chess.sethpc.xyz/duplicate/assets/index-Cu-6IMcr.js` → 200, 87103 b.
+- `https://chess.sethpc.xyz/duplicate/assets/index-p72E9BrX.css` → 200, 4291 b.
+- `https://chess.sethpc.xyz/` → 200, blind_chess landing; the JS bundle contains
+ the strings "Duplicate Chess", "under development", "/duplicate/" and the new
+ CSS classes `.card-link`, `.badge`, `.open-cue`.
+- `https://chess.sethpc.xyz/api/health` → `{"ok":true,"activeGames":0,"uptime":...}`.
+
+**Not yet verified by a human in a real browser** — Andrew (Seth's dad, the
+inventor) is the intended remote tester. v1 interactive testing was already
+pending from the prior handoff.
+
+## Codebase Understanding
+
+### Architecture Overview
+
+Three independent deploy units share the `chess.sethpc.xyz` origin:
+
+```
+chess.sethpc.xyz {
+ encode gzip zstd
+ handle_path /duplicate/* { # ← new, this session
+ root * /var/www/duplicate-chess # CT 600 (Caddy)
+ try_files {path} /index.html
+ file_server
+ }
+ handle {
+ reverse_proxy 192.168.0.245:3000 # CT 690 (blind_chess Fastify)
+ }
+}
+```
+
+`handle_path` strips the `/duplicate` prefix before serving, so the on-disk
+layout under `/var/www/duplicate-chess/` is at the root (`/index.html`,
+`/assets/...`, `/favicon.svg`, `/icons.svg`). With `vite base='/duplicate/'`,
+in-bundle asset URLs are `/duplicate/assets/...` — they re-enter the
+`handle_path`, get stripped, and resolve. Symmetric and audit-friendly.
+
+### Critical Files
+
+| File | Purpose | Relevance |
+|------|---------|-----------|
+| `vite.config.ts` | Sets `base: '/duplicate/'` | Required for sub-path hosting; default `/` would 404 on assets |
+| `index.html` | Page title fix — `
duplicate chess` | Was `vite-tmp` from the Vite scaffold |
+| `CLAUDE.md` | Operations + Live URL | Resume-time reference |
+| `DECISIONS.md` | Architectural decision recorded (supersedes the "deferred" entry) | The "why" for sub-path over subdomain or Fastify static mount |
+| Sibling: `blind_chess/packages/client/src/lib/Landing.svelte` | 3rd card + `.card-link`/`.badge`/`.open-cue` CSS | The visible entry point on chess.sethpc.xyz |
+| Caddy: `/etc/caddy/Caddyfile` (lines ~1112–1124) on CT 600 | `handle_path /duplicate/*` sub-block | Backup at `/etc/caddy/Caddyfile.bak.duplicate-chess-1779228542` |
+
+### Key Patterns Discovered
+
+- **Vite `base` is build-time**, not runtime. It is baked into `index.html` and
+ bundled asset URLs. Switching it requires a rebuild.
+- **CT 600 has no rsync.** Used `tar -cf - . | ssh ... 'cd ... && tar -xf -'`
+ to push the dist. Could `apt install rsync` later for ergonomics.
+- **Caddy `handle_path` vs `handle`**: `handle_path` strips the matched prefix
+ before downstream directives; `handle` does not. With `base='/duplicate/'` in
+ Vite, both forms work, but `handle_path` lets the on-disk layout live at the
+ root of its own dir — cleaner than mirroring the prefix in the filesystem.
+
+## Work Completed
+
+### Tasks Finished
+
+- [x] `vite.config.ts` set to `base: '/duplicate/'`, rebuilt, committed.
+- [x] `index.html` title fixed from `vite-tmp` to `duplicate chess`, rebuilt.
+- [x] blind_chess Landing.svelte: added 3rd "Duplicate Chess (under development)"
+ card with `.card-link`, `.badge`, `.open-cue` styles. Built, typechecked
+ clean (0 errors / 0 warnings), committed, pushed.
+- [x] Provisioned `/var/www/duplicate-chess/` on CT 600; tar-piped both builds.
+- [x] Backed up Caddyfile, added `handle_path /duplicate/*` sub-block via an
+ idempotent Python `text.replace` script (failed if the old block was
+ missing or the new block was already present). `caddy validate` →
+ `Valid configuration`. `systemctl reload caddy` → active.
+- [x] Rsynced blind_chess client dist to `/opt/blind-chess/client/dist/` on
+ CT 690, chowned to `blindchess:blindchess`, restarted the service. The
+ restart dropped 1 in-memory game (per pre-accepted MVP policy).
+- [x] Documentation:
+ - `~/bin/CLAUDE.md` projects table updated with the live URL.
+ - `duplicate_chess/CLAUDE.md` got the Operations section + new phase line.
+ - `duplicate_chess/DECISIONS.md` "deployment deferred" entry superseded.
+ - `blind_chess/CLAUDE.md` deploy line notes the `/duplicate/*` Caddy block.
+
+### Files Modified
+
+| File | Changes | Rationale |
+|------|---------|-----------|
+| `vite.config.ts` | `base: '/duplicate/'` + 3-line comment | Sub-path hosting |
+| `index.html` | title → `duplicate chess` | Vite scaffold leftover |
+| `CLAUDE.md` | live URL, Operations section | Resume-time reference |
+| `DECISIONS.md` | supersede the deferred-deploy decision | Architectural record |
+| `~/bin/CLAUDE.md` | projects-table row updated | Loaded every session |
+| `blind_chess/packages/client/src/lib/Landing.svelte` | 3rd card + CSS | Entry point |
+| `blind_chess/CLAUDE.md` | deploy line mentions /duplicate/* | Resume-time reference |
+| `/etc/caddy/Caddyfile` on CT 600 | added `handle_path /duplicate/*` block | Routing |
+
+### Decisions Made
+
+| Decision | Options Considered | Rationale |
+|----------|-------------------|-----------|
+| Serve duplicate as `chess.sethpc.xyz/duplicate/` sub-path | (a) separate subdomain like `duplicate.sethpc.xyz` (b) Fastify static mount on CT 690 | Sub-path keeps the user's mental model "one site, three modes"; Caddy file_server is the right tool; isolation from blind_chess server means redeploys don't drop blind games. |
+| Built with Vite `base: '/duplicate/'` | (a) `base: '/'` with Caddy stripping prefix + serving | Building with the right base is simpler and survives any matcher refactor. |
+| 3rd landing card as a plain `` (not a button calling an API) | (a) iframe inside the SPA (b) source-merge duplicate's components into blind_chess client | Plain anchor is honest about the architecture, no cross-origin/iframe pitfalls; source-merge is excessive for "under development". |
+| Landing card visibly tagged "under development" | (a) silent/equivalent | Sets expectations for Andrew; signals it's not at parity with blind/vanilla. |
+
+## Pending Work
+
+## Immediate Next Steps
+
+1. **Send Andrew the URL: https://chess.sethpc.xyz/duplicate/** — and the
+ blind_chess landing https://chess.sethpc.xyz/ where he can see both as
+ "modes". Wait for his feedback on the duplicate sandbox UX.
+2. **Manual browser test** of both the new 3rd landing card AND duplicate's
+ interactive flow (still pending from the prior handoff): play a real game,
+ click a piece, confirm green/grey triple-highlight, move applies to both of
+ the player's boards, etc.
+3. (If Andrew's testing surfaces issues) The known v1 open items in the prior
+ handoff still apply — scrub semantics, provisional endgame rules. Andrew's
+ ruling matters for those.
+
+### Blockers/Open Questions
+
+- [ ] **Was the right model chosen for "under development" framing?** Seth said
+ "incorporate as a 3rd game mode 'under development'." I interpreted that
+ as a 3rd top-level card on the existing two-card landing (since "mode"
+ currently means a radio inside each card, and duplicate is 4-player so
+ can't fit that radio). The card is a plain link out to `/duplicate/`,
+ not embedded. If Seth wanted an iframe or a deeper integration, redo.
+- [ ] All open items from the prior handoff remain open (scrub semantics, the
+ PROVISIONAL endgame rules, the trust-on-`player` deserialize, …).
+
+### Deferred Items
+
+- **`apt install rsync` on CT 600** — would let future deploys use `rsync`
+ instead of `tar -cf - | ssh tar -xf -`. The tar idiom worked but is uglier.
+- **Caddyfile cleanup** — `caddy validate` warned about pre-existing
+ `Unnecessary header_up X-Forwarded-For/Host` in *other* blocks (not ours)
+ and an `unformatted` notice. Not introduced this session; safe to ignore.
+- **Visual polish** on the 3rd landing card — only verified via curl + JS/CSS
+ string grep; not yet eyeballed in a real browser.
+- **Open the duplicate sub-app from a deeper path** like `/duplicate/foo` —
+ Caddy's `try_files {path} /index.html` falls back to index.html for any
+ unmatched path, so any future client-side routing works without changes.
+
+## Context for Resuming Agent
+
+## Important Context
+
+- **Two repos were touched.** `duplicate_chess` got the vite base + title +
+ docs. `blind_chess` got the new landing card. Both pushed to `main` on
+ `git.sethpc.xyz/Seth/...`.
+- **Two services were touched** at deploy time:
+ - Caddy on CT 600 (192.168.0.185) — reloaded after Caddyfile edit (graceful).
+ - blind-chess on CT 690 (192.168.0.245) — full restart, dropped 1 in-memory
+ game per pre-accepted MVP policy.
+- **`chess.local` (the LAN-only blind_chess instance on VDJ-RIG) was NOT
+ updated** this session. It still serves the previous blind_chess client (no
+ 3rd landing card) and has no `/duplicate/` route. The Caddy edit was on
+ CT 600 only. If Seth wants parity on chess.local, redeploy the blind_chess
+ client there and decide whether duplicate is reachable from the LAN at all
+ (no Caddy block currently exists for `chess.local/duplicate/*`).
+- **The phrase "new game mode" caused initial confusion.** I (mistakenly)
+ spent the opening few tool calls trying to figure out what new thing in
+ `blind_chess` should be deployed before asking — none of the recent
+ blind_chess commits described a new mode. Seth clarified the target was
+ `duplicate_chess`. Future sessions: if a deploy ask seems to mismatch the
+ current repo's state, look at the sibling `duplicate_chess` repo too.
+
+### Assumptions Made
+
+- That "3rd game mode under development" meant a third top-level landing card,
+ not an in-blind_chess radio addition (duplicate is 4-player — wouldn't fit
+ the existing 2-player radio without a much bigger rework).
+- That a plain anchor link to `/duplicate/` was the right shape (vs iframe).
+- That dropping the 1 active in-memory blind_chess game on restart was OK
+ (matches the pre-accepted MVP policy in DECISIONS.md).
+
+### Potential Gotchas
+
+- **`vite-tmp` was the page title** at first deploy (`a581393` fixed it, but
+ there was a ~2-minute window where it shipped). If Andrew tests during a
+ redeploy window, mention it.
+- **Caddy `handle_path` strips the prefix** — anyone reading `/var/www/duplicate-chess/`
+ layout might expect a `duplicate/` subdir; there isn't one (files live at
+ the root of that dir).
+- **Files in `/var/www/duplicate-chess/` are owned by uid/gid 1001**
+ (the `claude` uid on steel141, passed through via tar), readable to all.
+ Worked because Caddy needs only read; chown to `caddy:caddy` or `root:root`
+ would be tidier.
+- **`localhost` on VDJ-RIG (where chess.local lives) hits a Caddy 502** —
+ documented in the prior handoff. Doesn't affect this deploy (we touched CT
+ 600's Caddy, not the rig's).
+
+## Environment State
+
+### Tools/Services Used
+
+- `gitea` CLI for pushes (both repos).
+- `ssh root@192.168.0.185` (Caddy CT 600) — Caddyfile edit + Caddy reload.
+- `ssh root@192.168.0.245` (CT 690) — client dist rsync + systemctl restart.
+- `tar -cf - | ssh ... tar -xf -` for content transfer (no rsync on CT 600).
+- `pnpm` (workspace in blind_chess, single-pkg in duplicate_chess).
+
+### Active Processes
+
+- `caddy.service` on CT 600 — reloaded.
+- `blind-chess.service` on CT 690 — restarted (1 active game dropped).
+- No dev servers left running locally.
+
+### Environment Variables
+
+- None added or required.
+
+## Related Resources
+
+- Caddyfile backup: `/etc/caddy/Caddyfile.bak.duplicate-chess-1779228542` on CT 600.
+- Local backups: `duplicate_chess/.backup/vite.config.ts.*.bak` and
+ `blind_chess/packages/client/src/lib/.backup/Landing.svelte.*.bak`.
+- Live URLs:
+ - https://chess.sethpc.xyz/ — blind_chess landing (3rd card visible).
+ - https://chess.sethpc.xyz/duplicate/ — duplicate_chess sandbox.
+ - https://chess.sethpc.xyz/api/health — blind_chess API health.
+- Sibling-repo companion commit: `blind_chess @ fef6dcf` (landing card)
+ + `33a7cef` (CLAUDE.md doc note).
+
+---
+
+**Security Reminder**: No credentials or secrets in this handoff.