3 Commits

Author SHA1 Message Date
George Powell 99552c57ad Added donation button 2026-07-07 13:12:17 -04:00
George Powell efc9900de1 Updated documentation 2026-07-07 12:13:35 -04:00
George Powell e3ca264c54 new email link 2026-05-26 17:59:56 -04:00
6 changed files with 334 additions and 253 deletions
-165
View File
@@ -1,165 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Bibdle is a daily Bible verse guessing game built with SvelteKit 5. Players read a verse and try to guess which book of the Bible it comes from. The game provides feedback hints (Testament match, Section match, Adjacent book, etc.) similar to Wordle-style games. Progress is stored locally in the browser and a new verse is generated daily.
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
(Make sure you use the Svelte agent to execute these commands)
## Available MCP Tools:
### 1. list-sections
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
### 2. get-documentation
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
### 3. svelte-autofixer
Analyzes Svelte code and returns issues and suggestions.
You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
## Tech Stack
- **Framework**: SvelteKit 5 with Svelte 5 (uses runes: `$state`, `$derived`, `$effect`, `$props`)
- **Styling**: Tailwind CSS 4
- **Database**: SQLite with Drizzle ORM
- **Auth**: Session-based authentication using Bun's built-in cryptographically secure functions
- **Deployment**: Node.js adapter for production builds
- **ML**: `@xenova/transformers` for verse embeddings (initialized in server hook) (currently disabled, was a test for a cancelled project)
## Development Commands
```bash
# Start development server
bun run dev
# Type checking
bun run check
bun run check:watch
# Run tests
bun test
bun test --watch
bun test tests/timezone-handling.test.ts # Run a single test file
# Build for production
bun run build
# Preview production build
bun run preview
# Database operations
bun run db:push # Push schema changes directly (avoid in prod)
bun run db:generate # Generate migrations
bun run db:migrate # Run migrations
bun run db:studio # Open Drizzle Studio GUI
```
## Critical: Date/Time Handling
**Bibdle is played by users across many timezones worldwide. The verse shown to a player must always be the verse for the calendar date at *their* location — not the server's timezone, not UTC. A user in Tokyo on Wednesday must see Wednesday's verse, even if the server (or a user in New York) is still on Tuesday.**
**NEVER use server time or UTC time for user-facing date calculations.**
- Get today's date client-side: `new Date().toLocaleDateString("en-CA")``YYYY-MM-DD`
- Pass the date to the server as a query param or POST body (`localDate`)
- Server-side date arithmetic must use UTC methods on the client-provided date string: `new Date(dateStr + 'T00:00:00Z')` + `setUTCDate`/`getUTCDate`
- `src/routes/+page.ts` has `ssr = false` so the load runs client-side with the true local date
- Never set the user-facing URL to include their date as a parameter. It should always be passed to an API route behind the scenes if needed.
### Streak Calculation
A streak counts consecutive calendar days (in the user's local timezone) on which the user completed the puzzle. The rules:
- The client passes its local date (`localDate`) to the streak API. The server never uses its own clock.
- A streak is **active** if the user has completed today's puzzle *or* yesterday's puzzle (they still have time to play today).
- Walk backwards from `localDate` through the `dailyCompletions` records, counting each day that has a completion. Stop as soon as a day is missing.
- A streak of 1 (completed only today or only yesterday, with no prior consecutive days) is **not displayed** — the minimum shown streak is 2.
- "Yesterday" and all date arithmetic on the server must use UTC methods on the client-provided date string to avoid timezone drift: `new Date(localDate + 'T00:00:00Z')`, then `setUTCDate`/`getUTCDate`.
## Architecture
### Database Schema (`src/lib/server/db/schema.ts`)
- **user**: `id`, `firstName`, `lastName`, `email` (unique), `passwordHash`, `appleId` (unique), `isPrivate`
- **session**: `id` (SHA-256 hash of token), `userId` (FK), `expiresAt`
- **daily_verses**: Cached daily verses with book ID, verse text, reference, and date
- **dailyCompletions**: Game results per user/date with guess count, grade, book; unique on `(userId, date)`
Sessions expire after 30 days and auto-renew when < 15 days remain.
### Bible Data (`src/lib/types/bible.ts`)
The `bibleBooks` array contains all 66 Bible books with metadata:
- Testament (old/new), Section (Law, History, Wisdom, Prophets, Gospels, Epistles, Apocalyptic)
- Order (1-66, used for adjacency detection)
### Daily Verse System (`src/routes/+page.server.ts`)
`getTodayVerse()` checks the database for today's date, fetches a verse if missing, caches permanently, and returns verse with book metadata.
### Game Logic (`src/routes/+page.svelte`)
**State Management:**
- `guesses` array stored in localStorage keyed by date: `bibdle-guesses-${date}`
- Each guess tracks: book, testamentMatch, sectionMatch, adjacent
- `isWon` derived from whether any guess matches the correct book
**Hint System, for share grid:**
- ✅ Exact match | 🟩 Section match | 🟧 Testament match | ‼️ Adjacent book | 🟥 No match
### Authentication System (`src/lib/server/auth.ts`)
- Token generation: base64-encoded random bytes; stored as SHA-256 hash in DB
- Cookie name: `auth-session`
- Anonymous users: identified by a client-generated ID; stats migrate on sign-up via `migrateAnonymousStats()`
- Apple Sign-In supported via `appleId` field
### Stats & Streak (`src/routes/stats/`)
- Stats page requires auth; returns `requiresAuth: true` if unauthenticated
- Streak calculated client-side by calling `GET /api/streak?userId=X&localDate=Y`
- Streak walk-back: counts consecutive days backwards from `localDate` through completed dates
- Minimum displayed streak is 2 (single-day streaks suppressed)
## API Endpoints
- `POST /api/daily-verse` — Fetch verse for a specific date
- `POST /api/submit-completion` — Submit game result with stats
- `GET /api/streak?userId=X&localDate=Y` — Current streak for user
- `GET /api/streak-percentile` — Streak percentile ranking
## Key Files
- `src/routes/+page.svelte` — Main game UI and client-side logic
- `src/routes/+page.server.ts` / `+page.ts` — Server load (verse) + client load (`ssr: false`)
- `src/routes/stats/+page.svelte` / `+page.server.ts` — Stats UI and server calculations
- `src/lib/server/auth.ts` — Session management, password hashing, anonymous migration
- `src/lib/server/bible-api.ts` — Random verse fetching from local XML Bible
- `src/lib/server/bible.ts` — Bible book utility functions
- `src/lib/types/bible.ts` — Bible books data and TypeScript types
- `src/lib/server/db/schema.ts` — Drizzle ORM schema
- `src/hooks.server.ts` — Session validation hook; initializes ML embeddings
- `tests/` — Bun test suites (timezone, game, bible, stats, share, auth migration)
## Environment Variables
Required in `.env`:
- `DATABASE_URL` — Path to SQLite database file (e.g., `./local.db`)
## Deployment
Uses `@sveltejs/adapter-node`. See `bibdle.service` systemd configuration.
## A Note
The main developer of this project is still learning a lot about developing full-stack applications. If they ask you to do something, make sure they understand how it will be implemented before proceeding.
+162 -23
View File
@@ -1,38 +1,177 @@
# sv # Bibdle
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). A daily Bible verse guessing game. Each day a verse is shown and players try to identify which of the 66 books of the Bible it comes from, receiving Wordle-style feedback (Testament match, Section match, Adjacent book, First letter, etc.) after each guess. A new verse is generated daily and progress is tracked across timezones.
## Creating a project Live at [bibdle.com](https://bibdle.com).
If you're seeing this, you've probably already done this step. Congrats! ## Tech Stack
```sh - **Framework**: SvelteKit 2 with Svelte 5 (runes: `$state`, `$derived`, `$effect`, `$props`)
# create a new project in the current directory - **Styling**: Tailwind CSS 4
bunx sv create - **Database**: SQLite (`bun:sqlite`) with Drizzle ORM
- **Auth**: Session-based — email/password (argon2id via `Bun.password`), plus Apple and Google OAuth
- **Bible text**: Local NKJV XML (`EnglishNKJBible.xml`), parsed with `fast-xml-parser`; a Greek 1904 and Swedish 2000 translation are also bundled for alternate modes
- **ML** (currently disabled): `@xenova/transformers` verse embeddings for a similarity search route
- **Deployment**: `@sveltejs/adapter-node`, run under Bun, managed by a systemd service (see `bibdle.service`)
# create a new project in my-app ## Getting Started
bunx sv create my-app
```
## Developing ```bash
bun install
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: # Start the dev server (Vite, Bun runtime)
```sh
bun run dev bun run dev
# or start the server and open the app in a new browser tab # Type checking
bun run dev -- --open bun run check
```
## Building # Tests (Bun test)
bun test
bun test tests/timezone-handling.test.ts # single file
bun test --watch
To create a production version of your app: # Production build & preview
```sh
bun run build bun run build
bun run preview
``` ```
You can preview the production build with `bun run preview`. ### Database
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. ```bash
bun run db:push # push schema changes directly (avoid in prod)
bun run db:generate # generate migrations
bun run db:migrate # run migrations
bun run db:studio # open Drizzle Studio GUI
```
### Environment Variables
See `.env.example`. Required/used variables:
- `DATABASE_URL` — path to the SQLite database file (e.g. `prod.db`)
- `PUBLIC_SITE_URL` — canonical site URL
- `CRON_SECRET` — bearer token protecting the cron-only `/api/send-daily-verse` endpoint
- `DISCORD_DAILY_WEBHOOK` — webhook for posting the daily verse to Discord
- `AUTH_SECRET` — secret for Apple Sign-In
- `APPLE_ID` / `APPLE_TEAM_ID` / `APPLE_KEY_ID` / `APPLE_PRIVATE_KEY` — Apple Sign-In credentials
- `SMTP_USERNAME` / `SMTP_TOKEN` / `SMTP_SERVER` / `SMTP_PORT` — email (nodemailer)
## Architecture
### Database Schema (`src/lib/server/db/schema.ts`)
- **user** — `id`, `firstName`, `lastName`, `email` (unique), `passwordHash`, `appleId` (unique), `googleId` (unique), `isPrivate`
- **session** — `id` (SHA-256 hash of token), `userId` (FK), `expiresAt`
- **dailyVerses** — cached daily verse: `date` (unique), `bookId`, `verseText`, `reference`, `createdAt`
- **dailyCompletions** — one row per player/date: `anonymousId`, `date`, `guessCount`, `guesses` (JSON of book IDs, nullable), `completedAt`. Unique on `(anonymousId, date)` to prevent duplicate submissions.
Sessions expire after 30 days and auto-renew when fewer than 15 days remain.
### Bible Data (`src/lib/types/bible.ts`)
The `bibleBooks` array lists all 66 books with metadata:
- `testament`: `old` | `new`
- `section`: `Law`, `History`, `Wisdom`, `Major Prophets`, `Minor Prophets`, `Gospels`, `Pauline Epistles`, `General Epistles`, `Apocalyptic`
- `order` (166, used for adjacency detection)
### Game Logic
- `src/lib/utils/game.ts``evaluateGuess()` compares a guess to the target book and returns `testamentMatch`, `sectionMatch`, `adjacent`, and `firstLetterMatch` flags. `getGrade()` maps guess count to a letter grade (S+ down to C). Includes a special-case so that numbered Epistles (e.g. "1 John") match on first letter against any other numbered Epistle.
- `src/lib/stores/game-persistence.svelte.ts` — reactive store that keeps guesses and per-day flags in sync with `localStorage`, keyed by date (`bibdle-guesses-${date}`). Resolves the player identity (logged-in user ID, or a locally generated anonymous UUID) and restores state.
- `src/lib/utils/share.ts` — generates the share grid and text. Hint emojis: ✅ exact · 🟩 section · 🟧 testament · ‼️ adjacent · 🟥 no match.
### Daily Verse System
`src/lib/server/daily-verse.ts``getVerseForDate(date)`: returns the cached verse for a date if present, otherwise fetches a random verse from the local XML Bible and stores it permanently. The XML Bible is read and parsed in `src/lib/server/xml-bible.ts`; `src/lib/server/bible-api.ts` wraps it to produce a verse with a validated `bookId`, `reference`, and `verseText`.
### Authentication (`src/lib/server/auth.ts`)
- Token: base64url-encoded random bytes; stored as a SHA-256 hash in the DB. Cookie name: `auth-session`.
- Anonymous users are identified by a client-generated UUID in `localStorage`. On sign-up, `migrateAnonymousStats()` re-attributes the user's `dailyCompletions` rows from the anonymous ID to the new user ID (duplicates for overlapping dates are dropped).
- Apple Sign-In (`src/lib/server/apple-auth.ts`) and Google Sign-In (`src/lib/server/google-auth.ts`) are supported; the SvelteKit CSRF config trusts `https://appleid.apple.com` for the cross-origin `form_post` callback.
## Routes
### Pages
| Route | Description |
| --- | --- |
| `/` | Main game (classic mode). Verse display, search input, guesses table, win screen, streak/percentile. `+page.ts` sets `ssr = false` so the load runs client-side with the true local date, then POSTs that date to `/api/daily-verse`. |
| `/imposter` | Imposter Mode — four verses are shown; three come from one book and one from a different book. Pick the one that doesn't belong. |
| `/random` | Debug page showing a random verse from the NKJV. |
| `/greek-random` | Debug page showing a random verse in parallel Greek (1904) / English (NKJV). |
| `/similarity` | Search a sentence and return the most similar Bible verses via the (currently disabled) embeddings model. |
| `/about` | About page with the project's backstory and social links. |
| `/global` | Public stats dashboard: completions today/all-time, unique & weekly & monthly players, active streak distribution, 14-day completions trend, retention/return-rate metrics. |
| `/progress` | Personal progress page (requires auth): activity calendar, 66-book grid with mastery tiers, insights, and achievements/milestones. |
| `/stats` | Personal stats page (requires auth); returns `requiresAuth: true` for unauthenticated visitors and renders a sign-in modal. |
| `/dev` | Local-time / countdown debug page. |
### API Endpoints
| Endpoint | Description |
| --- | --- |
| `POST /api/daily-verse` | Fetch (and cache) the verse for a given `YYYY-MM-DD` date. |
| `POST /api/submit-completion` | Submit a game result (`anonymousId`, `date`, `guessCount`, `guesses`); returns solve rank, guess rank, total solves, average guesses, ties, and percentile. Unique on `(anonymousId, date)`. |
| `GET /api/streak?anonymousId=X&localDate=Y` | Current streak: walks backwards from the client's local date through completed dates, counting consecutive days. Single-day streaks are reported as 0 (minimum displayed streak is 2). |
| `GET /api/streak-percentile?streak=N&localDate=Y` | Streak percentile ranking computed across all players' current streaks. |
| `GET /api/stats` | Aggregated stats used by the `/global` dashboard. |
| `GET /api/imposter` | Generates a four-verse imposter-mode round. |
| `POST /api/similar-verses` | Semantic verse search via embeddings. |
| `POST /api/send-daily-verse` | Cron-only (bearer `CRON_SECRET`); posts today's verse to the Discord webhook. |
| `POST /api/dev/seed-history` | Dev seeding helper. |
### Other Endpoints
- `GET /feed.xml` — RSS feed of daily verses.
- `GET /sitemap.xml` — XML sitemap for SEO.
## Critical: Date/Time Handling
Bibdle is played across many timezones. The verse shown must always be the verse for the calendar date at **the player's location** — never the server's timezone, never UTC.
- The client computes today's date with `new Date().toLocaleDateString("en-CA")` (`YYYY-MM-DD`) and sends it to the server.
- Server-side date arithmetic always uses UTC methods on the client-provided date string (`new Date(dateStr + 'T00:00:00Z')` + `setUTCDate`/`getUTCDate`) to avoid timezone drift — see `/api/streak` and `/api/send-daily-verse`.
- `/` has `ssr = false` so the load runs client-side with the real local date.
- The main page also reloads itself if the tab regains focus on a new calendar day.
- The user's local date is never placed in the URL; it is only ever sent to API routes.
### Streak Calculation
A streak counts consecutive calendar days (in the player's local timezone) on which the puzzle was completed:
- The client passes `localDate`; the server never uses its own clock.
- `/api/streak` walks backwards from `localDate` through `dailyCompletions`, counting each completed day; stops at the first missing day. It's called from the win screen, so today is typically completed. Single-day streaks are reported as `0` — the minimum displayed streak is 2.
- `/api/streak-percentile` (which ranks all players) anchors on today-if-played-else-yesterday, so a player's streak isn't zeroed mid-day before they've had a chance to complete today's puzzle.
## Key Files
| File | Purpose |
| --- | --- |
| `src/routes/+page.svelte` | Main game UI and client-side logic |
| `src/routes/+page.server.ts` / `+page.ts` | Server load (user/session) + client load (`ssr: false`, fetches the daily verse) |
| `src/routes/+layout.svelte` | App shell, title animation, theme toggle, analytics injection |
| `src/lib/server/auth.ts` | Session management, password hashing, anonymous→user migration |
| `src/lib/server/apple-auth.ts`, `google-auth.ts` | OAuth providers |
| `src/lib/server/daily-verse.ts` | Per-date verse caching/lookup |
| `src/lib/server/xml-bible.ts` | Local XML Bible parsing (NKJV / Greek / Swedish) |
| `src/lib/server/bible-api.ts` | Random verse fetching on top of the XML parser |
| `src/lib/server/bible.ts` | Bible book utility functions |
| `src/lib/server/milestones.ts` | Achievement/milestone calculation (set-completion, streak, etc.) |
| `src/lib/types/bible.ts` | 66-book metadata and TypeScript types |
| `src/lib/utils/game.ts` | Guess evaluation and grading |
| `src/lib/utils/share.ts` | Share grid/text generation |
| `src/lib/utils/streak.ts`, `stats-client.ts`, `stats.ts` | Client-side streak/stats fetching and formatting |
| `src/lib/stores/game-persistence.svelte.ts` | Reactive localStorage-backed game state |
| `src/lib/server/db/schema.ts` | Drizzle ORM schema |
| `src/hooks.server.ts` | Session validation hook; (commented-out) embeddings init |
| `tests/` | Bun test suites: timezone, game, bible, stats, share, sign-in migration |
## Deployment
Production uses `@sveltejs/adapter-node` run under Bun via a systemd service. `deploy.sh` pulls latest, installs deps, builds, and restarts `bibdle.service` (which runs `bun --bun build/index.js` on port 5173 with `DATABASE_URL=prod.db`). See `bibdle.service` for the unit file.
## Background
Bibdle was created as a small, daily nudge to read the Bible — inspired by the Wordle story of a personal project that grew organically through word of mouth. The full backstory lives on the `/about` page.
+1 -1
View File
@@ -33,7 +33,7 @@
<div class="w-0.5 h-8 bg-gray-400 dark:bg-gray-600"></div> --> <div class="w-0.5 h-8 bg-gray-400 dark:bg-gray-600"></div> -->
<a <a
href="mailto:george+bibdle@silentsummit.co" href="mailto:george@snail.city"
class="inline-flex hover:opacity-80 transition-opacity" class="inline-flex hover:opacity-80 transition-opacity"
aria-label="Send email" aria-label="Send email"
data-umami-event="Email clicked" data-umami-event="Email clicked"
+163 -36
View File
@@ -68,6 +68,7 @@
let copyTracked = $state(false); let copyTracked = $state(false);
let showSnippetOption = $state(false); let showSnippetOption = $state(false);
let includeSnippet = $state(false); let includeSnippet = $state(false);
let showProgressDropdown = $state(false);
let effectiveShareText = $derived( let effectiveShareText = $derived(
includeSnippet includeSnippet
@@ -333,17 +334,32 @@
{#if isLoggedIn} {#if isLoggedIn}
<div class="signin-prompt"> <div class="signin-prompt">
<div class="rainbow-glow w-full">
<a <a
href="/progress" href="/progress"
class="flex flex-col items-center justify-center gap-1 w-full p-4 mb-2 bg-white dark:bg-gray-900 border-2 border-black/40 dark:border-white/40 rounded-2xl shadow-sm text-gray-800 dark:text-gray-100 text-base font-semibold no-underline transition-transform duration-100 hover:-translate-y-px active:scale-[0.98]" class="progress-btn w-full"
data-umami-event="See your progress (logged in)"
> >
📈 See your progress <span>📈 See your progress</span>
<svg class="progress-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="m9 18 6-6-6-6" />
</svg>
</a> </a>
</div> </div>
</div>
{:else} {:else}
<div class="signin-prompt"> <div class="signin-prompt">
<button
type="button"
class="progress-btn w-full"
aria-expanded={showProgressDropdown}
onclick={() => (showProgressDropdown = !showProgressDropdown)}
data-umami-event="See your progress (logged out)"
>
<span>📈 See your progress</span>
<svg class="progress-chev" class:open={showProgressDropdown} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="m6 9 6 6 6-6" />
</svg>
</button>
{#if showProgressDropdown}
<p class="signin-text text-gray-800 dark:text-gray-300"> <p class="signin-text text-gray-800 dark:text-gray-300">
Create an account (or sign in) to track your progress Create an account (or sign in) to track your progress
</p> </p>
@@ -398,8 +414,52 @@
Sign in with Google Sign in with Google
</button> </button>
</form> </form>
{/if}
</div> </div>
{/if} {/if}
<div class="signin-prompt">
<a
href="https://discord.gg/yWQXbGK8SD"
target="_blank"
rel="noopener noreferrer"
class="discord-btn w-full"
data-umami-event="Join the BIBDLE Discord"
>
<svg
class="discord-icon"
viewBox="0 0 127.14 96.36"
fill="currentColor"
aria-hidden="true"
>
<path
d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"
/>
</svg>
<span>Join the BIBDLE Discord!</span>
<svg class="progress-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="m9 18 6-6-6-6" />
</svg>
</a>
</div>
<div class="signin-prompt">
<a
href="https://donate.stripe.com/9B6aEZ9WgeZF3qYfF987K00"
target="_blank"
rel="noopener noreferrer"
class="progress-btn w-full"
data-umami-event="Support bibdle's development"
>
<span>❤️ Support bibdle's development</span>
<svg class="progress-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="m9 18 6-6-6-6" />
</svg>
</a>
<div class="big-text font-black! text-center text-gray-300! mt-2">
BIBDLE will be ad-free forever.
</div>
</div>
</div> </div>
<style> <style>
@@ -680,45 +740,59 @@
transform: translateX(16px); transform: translateX(16px);
} }
/* ── Apple Sign In prompt ── */ /* ── See your progress button (neobrutalist) ── */
.rainbow-glow { .progress-btn {
position: relative; display: flex;
border-radius: 1rem; align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.85rem 1.25rem;
background: #fff;
color: #111;
border: 2px solid #000;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 700;
text-decoration: none;
cursor: pointer;
box-shadow: 6px 6px 0 0 #000;
transition:
transform 80ms ease,
box-shadow 80ms ease;
} }
.rainbow-glow::before { .progress-btn:hover {
content: ""; transform: translate(-2px, -2px);
position: absolute; box-shadow: 8px 8px 0 0 #000;
inset: 0px;
border-radius: 1.25rem;
background: conic-gradient(
from var(--angle, 0deg),
#ff0080,
#ff8c00,
#ffd700,
#00ff88,
#00cfff,
#a855f7,
#ff0080
);
animation: rainbow-rotate 6s linear infinite;
filter: blur(8px);
opacity: 0.75;
z-index: -1;
} }
@property --angle { .progress-btn:active {
syntax: "<angle>"; transform: translate(2px, 2px);
initial-value: 0deg; box-shadow: 2px 2px 0 0 #000;
inherits: false;
} }
@keyframes rainbow-rotate { .progress-chev {
0% { width: 1.25rem;
--angle: 0deg; height: 1.25rem;
transition: transform 200ms ease;
} }
100% {
--angle: 360deg; .progress-chev.open {
transform: rotate(180deg);
}
@media (prefers-color-scheme: dark) {
.progress-btn {
background: #111827;
color: #f9fafb;
border-color: #fff;
box-shadow: 6px 6px 0 0 #fff;
}
.progress-btn:hover {
box-shadow: 8px 8px 0 0 #fff;
}
.progress-btn:active {
box-shadow: 2px 2px 0 0 #fff;
} }
} }
@@ -730,6 +804,59 @@
/*padding: 1rem 0 0.25rem;*/ /*padding: 1rem 0 0.25rem;*/
} }
/* ── Discord button (neobrutalist, discord purple) ── */
.discord-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.85rem 1.25rem;
background: #5865f2;
color: #fff;
border: 2px solid #000;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 700;
text-decoration: none;
cursor: pointer;
box-shadow: 6px 6px 0 0 #000;
transition:
transform 80ms ease,
box-shadow 80ms ease,
background-color 120ms ease;
}
.discord-btn:hover {
transform: translate(-2px, -2px);
box-shadow: 8px 8px 0 0 #000;
background: #4752c4;
}
.discord-btn:active {
transform: translate(2px, 2px);
box-shadow: 2px 2px 0 0 #000;
background: #3c45a5;
}
@media (prefers-color-scheme: dark) {
.discord-btn {
border-color: #fff;
box-shadow: 6px 6px 0 0 #fff;
}
.discord-btn:hover {
box-shadow: 8px 8px 0 0 #fff;
}
.discord-btn:active {
box-shadow: 2px 2px 0 0 #fff;
}
}
.discord-icon {
width: 1.25rem;
height: 1.25rem;
flex-shrink: 0;
}
.signin-text { .signin-text {
font-size: 0.85rem; font-size: 0.85rem;
text-align: center; text-align: center;
+1 -1
View File
@@ -37,7 +37,7 @@
title="Bibdle RSS Feed" title="Bibdle RSS Feed"
href="/feed.xml" href="/feed.xml"
/> />
<meta name="description" content="A daily Bible game" /> <meta name="description" content="A Wordle-inspired daily Bible game" />
</svelte:head> </svelte:head>
<div <div
-20
View File
@@ -452,26 +452,6 @@
<hr <hr
class="animate-fade-in-up animate-delay-800 border-gray-300 dark:border-gray-600" class="animate-fade-in-up animate-delay-800 border-gray-300 dark:border-gray-600"
/> />
<div class="animate-fade-in-up animate-delay-800">
<a
href="https://discord.gg/yWQXbGK8SD"
target="_blank"
rel="noopener noreferrer"
class="flex items-center justify-center gap-2 w-full px-5 py-2.5 bg-[#5865F2] hover:bg-[#4752C4] text-white font-semibold rounded-lg shadow-md transition-colors duration-200"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 127.14 96.36"
class="w-5 h-5 fill-white"
aria-hidden="true"
>
<path
d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"
/>
</svg>
Join the BIBDLE Discord!
</a>
</div>
<div class="animate-fade-in-up animate-delay-800"> <div class="animate-fade-in-up animate-delay-800">
<Credits /> <Credits />
</div> </div>