Consolidates the dev-only stats and auth buttons into the DevButtons
component, passing user and onSignIn as props. Also comments out the
Twitter link in SocialLinks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
prompt on win screen, and layout/theme improvements
## New features
- **About page** (`src/routes/about/`): New static about page rendered
from `static/about.md` using the `marked` library (added as a
dependency). Includes the project backstory content.
- **XML sitemap** (`src/routes/sitemap.xml/`): Dynamic sitemap
endpoint for SEO, registered in `static/robots.txt` via `Sitemap:`
directive.
- **Apple Sign In prompt on win screen** (`WinScreen.svelte`): When
the game is won and the user is not logged in, a "Sign in to save
your streak & see your stats" prompt with an Apple Sign In button is
shown below the share card. Passes `anonymousId` so stats migrate on
sign-up. Driven by new `isLoggedIn` and `anonymousId` props, passed
from `+page.svelte`.
## Refactoring
- **`SocialLinks` component**
(`src/lib/components/SocialLinks.svelte`): Extracted the Bluesky,
Twitter/X, and email social link icons from `Credits.svelte` into a
reusable component. `Credits.svelte` now imports and renders
`<SocialLinks />`.
- **`ThemeToggle` component**
(`src/lib/components/ThemeToggle.svelte`): New component for
toggling light/dark mode, persisted to `localStorage`. Currently
rendered but hidden (`hidden` class) in `+page.svelte` —
infrastructure is in place for future use.
## Layout changes
- **`+layout.svelte`**: Moved the page title/header (`<h1>` with
`TitleAnimation`) and the gradient background wrapper from
`+page.svelte` into the root layout, so it applies across all
routes. Also removed the `browser` guard around the analytics script
injection (it's
already inside `onMount` which is client-only). Added `<meta
name="description">`.
- **`+page.svelte`**: Removed the title/header and gradient wrapper
(now in layout). Minor formatting cleanup (reformatted `SearchInput`
props, moved `currentDate` derived state earlier). `ThemeToggle`
import swapped in place of `TitleAnimation` (which moved to layout).
## Styling
- **`layout.css`**: Added `@custom-variant dark` for class-based dark
mode toggling (supports `.dark` class on `<html>`). Added explicit
`html.dark` / `html.light` rules alongside the existing
`prefers-color-scheme` media query, so the `ThemeToggle` component
can
override the system preference. Added background transition
animation.
146 tests covering evaluateGuess, grading, ordinals, bible data
integrity, section counts, share text generation, and stat
string helpers. Also fixes toOrdinal for 111-113 (was using
>= 11 && <= 13 instead of % 100 check).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A streak requires at least 2 consecutive days. Return 0 when the
count is less than 2 so the streak counter is not displayed after
completing only today's puzzle.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows book names only (A-Z) for the first 3 guesses, reveals Old/New
Testament groupings after 3 guesses, and full section-level groupings
in canonical Bible order after 9 guesses. Adds a status banner above
the search bar to inform players when new structure becomes visible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Load Rybbit script via app.html (recommended SvelteKit approach)
- Mirror all Umami custom events (First guess, Guessed correctly, Share, Copy to Clipboard, social link clicks) with rybbit.event()
- Identify logged-in users with name/email traits; anonymous users by stable UUID
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Refactored the daily verse system to properly handle users across different
timezones. Previously, the server used a fixed timezone (America/New_York),
causing users in other timezones to see incorrect verses near midnight.
Key changes:
**Server-side refactoring:**
- Extract `getVerseForDate()` into `src/lib/server/daily-verse.ts` for reuse
- Page load now uses UTC date for initial SSR (fast initial render)
- New `/api/daily-verse` POST endpoint accepts client-calculated date
- Server no longer calculates dates; uses client-provided date directly
**Client-side timezone handling:**
- Client calculates local date using browser's timezone on mount
- If server date doesn't match local date, fetches correct verse via API
- Changed verse data from `$derived` to `$state` to fix reactivity issues
- Mutating props was causing updates to fail; now uses local state
- Added effect to reload page when user returns to stale tab on new day
**Stats page improvements:**
- Accept `tz` query parameter for accurate streak calculations
- Use client's local date when determining "today" for current streaks
- Prevents timezone-based streak miscalculations
**Developer experience:**
- Added debug panel showing client local time vs daily verse date
- Added console logging for timezone fetch process
- Comprehensive test suite for timezone handling and streak logic
**UI improvements:**
- Share text uses 📜 emoji for logged-in users, 📖 for anonymous
- Stats link now includes timezone parameter for accurate display
This ensures users worldwide see the correct daily verse for their local
date, and streaks are calculated based on their timezone, not server time.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>