- Add a weekly vs monthly players chart to Pulse, counted over whole
completed weeks (Mon-Sun) and calendar months instead of rolling windows.
- Add a Projection section below Pulse that estimates future weekly players
three ways (flow, regression, seasonal) with a shaded likely range.
- Pull the public community stats into one shared component used by both the
public page and the admin console, and show yesterday's verse on the
public page.
- Show "Resurrected" instead of "Churned" in the segment mix chart, since
churned only ever grows and flattens the rest.
- Make full-width charts fill their container and line the hover cursor up
correctly; show 3 months of new players per day.
- Hide the verse mix lab for now.
Replace the linear count/max alpha with quantile buckets (GitHub-style)
so a single outlier day no longer flattens the rest of the graph, and
render best/median/worst day labels inside their cells.
- Add buildCalendarScale utility with empty-bucket handling.
- Make CalendarHeatmap size, gap, and annotations configurable; align
weeks to Monday.
- Expand the community calendar to 90 days, add play totals/averages and
all-time best day, and format large numbers with locale separators.
A verse date is a player's local calendar day, spanning D-1 10:00Z
(UTC+14) through D+1 12:00Z (UTC-12). The old UTC end-of-day cutoff both
dropped late-night finishes in negative timezones and could persist a
partial day.
- Scope snapshot loads by the verse date column; use completedBefore only
to pin an in-progress point-in-time build.
- Gate persistence on isDayClosed (D+1 12:00Z); open days are computed
live and never written.
- Build the cron snapshot for latestClosedDate instead of EST yesterday,
and read the same date for the community page.
- Add all-time best single day to snapshot totals.
- Bump PAYLOAD_SCHEMA_VERSION to 4 and extend snapshot/window tests.
- Cap /feed.xml at the current America/New_York calendar day so
pre-scheduled future verses (and stray 2124 rows) can no longer
surface at the top of the feed
- Emit DST-aware RFC 822 pubDates (correct -04:00/-05:00 offset) instead
of a hardcoded EST offset, and parse calendar days as UTC so titles
no longer shift by one day on UTC hosts
- Add content-addressed ETag + Last-Modified with If-None-Match /
If-Modified-Since conditional handling
- Add atom:link rel=self, content:encoded, channel pubDate/generator/image
- Move RSS auto-discovery <link> into app.html: the root route sets
ssr=false, so the layout <svelte:head> link never reached the raw HTML
and feed readers could not discover /feed.xml
- Extract feed helpers into $lib/server/rss and add unit tests
Adds Google OAuth alongside existing Apple and email/password auth. Follows the same patterns as Apple Sign-In: state cookie for CSRF, anonymousId migration, and user linking by email. Key differences: Google callback is a GET redirect (sameSite: lax) and uses a static client secret instead of a signed JWT.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Achievements system:
- Add src/lib/server/milestones.ts with full achievement definitions and
calculation logic (16 achievements: streaks, book set completions,
community milestones like Overachiever/Procrastinator/Outlier, and fun
ones like Prodigal Son, Extra Credit, Is This A Joke To You?)
- Wire calculateMilestones() into the progress page server load
- Replace the old ad-hoc milestone cards with a proper achievements grid
(3/4 col, uniform min-height cards, larger text)
- Change "With God, All Things Are Possible" from "every game solved in 1"
to "solve in 1 guess for each of the 66 books at least once"
Game page hint overlay:
- After a correct testament/section/first-letter match, display a subtle
text hint below the verse prompt (e.g. "It is in the Old Testament.")
- Hints fade in 2.8s after a guess (after the row flip animation)
- Hints are only shown to new players (fewer than 3 tracked wins) to
avoid being patronising to experienced players
Progress page:
- Hide Skill Growth chart with {#if false && showChart} pending rework
- Fix book tier colour scheme: explored=blue, mastered=purple, perfect=emerald
(was amber/emerald — now consistent across grid, legend, and stat cards)
- Simplify GuessesTable row colour: remove proximity gradient, use flat red
for wrong guesses
- Add "Come back tomorrow!" encouragement text in CountdownTimer for new
players (fewer than 3 wins)
- Fix GamePrompt text colour to always be gray-100
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>