Replaces 7 inline collapsible tables in the global stats page with a
reusable CollapsibleTable component. Adds mode tab toggle (Rolling 30d /
Calendar) into the component. Fixes show more/less which was broken due
to mode-based expanded tracking when no modes were provided.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
$env/dynamic/private requires env vars to be present at build time.
Bun.env reads them at runtime, which is correct for runtime secrets.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Protected by CRON_SECRET bearer token. Fetches today's verse in
America/New_York timezone and POSTs it to DISCORD_DAILY_WEBHOOK.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GuessesTable now accepts a `minimized` prop instead of deriving collapse from `isWon`, giving the parent control over timing
- Delay collapsing guesses grid until win animations complete (1800ms), skipped for already-completed puzzles
- Replace plain progress link on win screen with a styled green button matching other CTAs
- Progress page: remove redundant subtitle and nav button from header, add book status legend, add axis labels to guess history chart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new /progress route showing a personalized Bible knowledge dashboard
with stat cards, book mastery grid, 30-day activity calendar, skill growth
chart, streak milestones, and section insights. Links added from WinScreen
(logged-in users) and DevButtons.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 12-week Weekly Active Users table to global stats with WoW change %
- Fix 7-day and 30-day retention to measure return on exactly day N (not any day within the window)
- Remove "Avg Guesses Today" stat card
- Update retention description to clarify exact-day measurement
- Add bibdle logos (SVG, square PNG, circle PNG) and new favicon.png
- Wire favicon.png as the site favicon via app.html link tag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reverse new player return rate table (most recent day at top)
- Reverse 7- and 30-day retention tables (most recent cohort at top)
- Rename "Day Rate" column to "Return Rate"
- Clarify "Last 14 Days" heading to "Last 14 Days — Completions"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Overall return rate: % of all-time players who played more than once
- New player return rate: 7-day rolling avg of daily first-timer return rates, with velocity vs prior 7 days
- 7-day and 30-day retention over time: per-cohort-day retention series
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>