Refactor game logic into utility modules and add cross-device sync

Extracted game state management, share logic, and stats API calls into dedicated modules (game-persistence.svelte.ts, share.ts, stats-client.ts), and moved daily verse loading to client-side to fix timezone issues. Added a guesses column to daily_completions for cross-device state restoration for logged-in users, a new GET /api/stats endpoint, and a staging deploy script.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
George Powell
2026-02-18 13:25:40 -05:00
parent 2de4e9e2a7
commit e6081c28f1
17 changed files with 640 additions and 543 deletions

View File

@@ -1,20 +1,8 @@
<script lang="ts">
import { bibleBooks } from "$lib/types/bible";
import { getFirstLetter, type Guess } from "$lib/utils/game";
import Container from "./Container.svelte";
interface Guess {
book: {
id: string;
name: string;
testament: string;
section: string;
};
testamentMatch: boolean;
sectionMatch: boolean;
adjacent: boolean;
firstLetterMatch: boolean;
}
let {
guesses,
correctBookId,
@@ -28,11 +16,6 @@
return "bg-red-500 border-red-600";
}
function getFirstLetter(bookName: string): string {
const match = bookName.match(/[a-zA-Z]/);
return match ? match[0] : bookName[0];
}
function getBoxContent(
guess: Guess,
column: "book" | "firstLetter" | "testament" | "section",

View File

@@ -7,11 +7,11 @@
imposterIndex: number;
}
let data: ImposterData | null = null;
let clicked: boolean[] = [];
let gameOver = false;
let loading = true;
let error: string | null = null;
let data: ImposterData | null = $state(null);
let clicked: boolean[] = $state([]);
let gameOver = $state(false);
let loading = $state(true);
let error: string | null = $state(null);
async function loadGame() {
try {
@@ -92,7 +92,7 @@
{:else if error}
<div class="error">
<p>Error: {error}</p>
<button on:click={newGame}>Retry</button>
<button onclick={newGame}>Retry</button>
</div>
{:else if data}
<!-- <div class="instructions">
@@ -106,7 +106,7 @@
class:clicked={clicked[i]}
class:correct={clicked[i] && i === data.imposterIndex}
class:wrong={clicked[i] && i !== data.imposterIndex}
on:click={() => handleClick(i)}
onclick={() => handleClick(i)}
disabled={gameOver}
>
{formatVerse(verse)}
@@ -119,7 +119,7 @@
</div>
{#if gameOver}
<div class="result">
<button on:click={newGame}>New Game</button>
<button onclick={newGame}>New Game</button>
</div>
{/if}
{/if}