Fix mobile header layout, add resumable progress + coin wallet

Header nav was splitting into three lines on mobile (logo / nav /
logout floating right with a big gap) — applied the same order-last
wrap pattern already proven in top-tickets/project-claude, so nav
drops to its own scrollable row instead.

Bigger feature: player progress now survives closing the tab, for all
three templates, not just Clicker. Each Phaser scene reports its state
through the same registry-callback pattern already used for onFinish
(onProgress), and reads an initialState to resume instead of always
starting fresh. Autosave fires every 10 minutes for free; an instant
manual save costs coins.

New coin economy (src/lib/wallet/): a wallet + transaction ledger per
user, atomic grant/spend via db.transaction() so two concurrent spends
against a low balance can't both succeed (verified with a real race
test — exactly one wins, balance never goes negative). New accounts
get a 20-coin signup bonus so manual save is testable before any real
payment method exists.

Real-money purchases are scaffolded but deliberately not wired to a
provider yet — user hasn't picked one. /api/wallet/purchase creates a
pending order and returns a clean "not configured" error, same shape
as the AI/LDAP not-configured responses elsewhere in this project. The
payment abstraction (src/lib/payments/provider.ts) is the one seam a
real adapter needs to fill in later.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QcXH24ky6zjk2UyK5oZUPH
This commit is contained in:
ogrechkoandClaude Sonnet 5 committed 2026-07-31 21:56:49 +00:00
1 parent 3d5497fae9
commit e682c7df13
25 files changed
+1375 -49

No files matched your search

+9
View File
@@ -71,3 +71,12 @@ export const DEFAULT_MAZE_DEFINITION: MazeDefinition = {
grid: ["#######", "#S.*..#", "#.###.#", "#...*.#", "#.###.#", "#....E#", "#######"],
timeLimitSec: 0,
};
export const mazeProgressSchema = z.object({
playerRow: z.number().int().min(0),
playerCol: z.number().int().min(0),
collectedItemKeys: z.array(z.string()).default([]),
remainingMs: z.number().int().min(0),
});
export type MazeProgress = z.infer<typeof mazeProgressSchema>;