mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-08-22 22:32:28 -04:00
Added donation button
This commit is contained in:
@@ -1,199 +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 2 / Svelte 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, First letter, 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 2 with Svelte 5 (uses runes: `$state`, `$derived`, `$effect`, `$props`)
|
|
||||||
- **Styling**: Tailwind CSS 4
|
|
||||||
- **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
|
|
||||||
- **Deployment**: `@sveltejs/adapter-node` run under Bun, managed by a systemd service (`bibdle.service`)
|
|
||||||
- **ML** (currently disabled): `@xenova/transformers` verse embeddings for a similarity search route
|
|
||||||
|
|
||||||
The package version is `3.0.0alpha`.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
- `/api/streak` walks backwards from `localDate` through the `dailyCompletions` records, counting each completed day. It stops at the first missing day. It's called from the win screen, so today is typically completed.
|
|
||||||
- `/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.
|
|
||||||
- A streak of 1 (completed only today, with no prior consecutive days) is **not displayed** — `/api/streak` returns `0` for any streak < 2, and the minimum shown streak is 2.
|
|
||||||
- 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), `googleId` (unique), `isPrivate`
|
|
||||||
- **session**: `id` (SHA-256 hash of token), `userId` (FK), `expiresAt`
|
|
||||||
- **dailyVerses** (table `daily_verses`): cached daily verse — `date` (unique), `bookId`, `verseText`, `reference`, `createdAt`
|
|
||||||
- **dailyCompletions** (table `daily_completions`): one row per player/date — `anonymousId`, `date`, `guessCount`, `guesses` (JSON array of book IDs; nullable), `completedAt`. Unique on `(anonymousId, date)` to prevent duplicate submissions.
|
|
||||||
|
|
||||||
Sessions expire after 30 days and auto-renew when < 15 days remain.
|
|
||||||
|
|
||||||
**Identity model:** logged-in users' `anonymousId` *is* their `user.id` — `createUser()` inserts the user with `id = anonymousId` so existing stats carry over. Anonymous users get a client-generated UUID stored in `localStorage` (`bibdle-anonymous-id`).
|
|
||||||
|
|
||||||
### Bible Data (`src/lib/types/bible.ts`)
|
|
||||||
|
|
||||||
The `bibleBooks` array contains all 66 Bible books with metadata:
|
|
||||||
- `testament`: `old` | `new`
|
|
||||||
- `section`: `Law`, `History`, `Wisdom`, `Major Prophets`, `Minor Prophets`, `Gospels`, `Pauline Epistles`, `General Epistles`, `Apocalyptic`
|
|
||||||
- `order` (1-66, used for adjacency detection)
|
|
||||||
|
|
||||||
### 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 (`src/lib/server/xml-bible.ts`, wrapped by `src/lib/server/bible-api.ts`) and stores it permanently. The client calls `POST /api/daily-verse` with its local date; `src/routes/+page.server.ts` only loads `user`/`session` (the verse is fetched client-side because `+page.ts` sets `ssr = false`).
|
|
||||||
|
|
||||||
### Game Logic
|
|
||||||
|
|
||||||
Core logic lives in `src/lib/utils/game.ts` and the reactive store `src/lib/stores/game-persistence.svelte.ts`; `src/routes/+page.svelte` wires them into the UI.
|
|
||||||
|
|
||||||
**State Management:**
|
|
||||||
- `guesses` array stored in `localStorage` keyed by date: `bibdle-guesses-${date}`
|
|
||||||
- Each `Guess` tracks: `book`, `testamentMatch`, `sectionMatch`, `adjacent`, `firstLetterMatch`
|
|
||||||
- `evaluateGuess()` includes a special case: numbered Epistles (e.g. "1 John") match on first letter against any other numbered Epistle
|
|
||||||
- `isWon` derived from whether any guess matches the correct book
|
|
||||||
- `getGrade()` maps guess count to a letter grade (S+ → C)
|
|
||||||
|
|
||||||
**Hint System, for share grid:**
|
|
||||||
- ✅ Exact match | 🟩 Section match | 🟧 Testament match | ‼️ Adjacent book | 🟥 No match
|
|
||||||
|
|
||||||
### Authentication System (`src/lib/server/auth.ts`)
|
|
||||||
|
|
||||||
- Token generation: base64url-encoded random bytes; stored as SHA-256 hash in DB. Cookie name: `auth-session`.
|
|
||||||
- Anonymous users: identified by a client-generated UUID in `localStorage`; stats migrate on sign-up via `migrateAnonymousStats()` (re-attributes `dailyCompletions` rows from the anonymous ID to the new user ID; overlapping dates are dropped).
|
|
||||||
- Three sign-in methods: email/password (argon2id via `Bun.password`), Apple Sign-In (`src/lib/server/apple-auth.ts`, `appleId` field), and Google Sign-In (`src/lib/server/google-auth.ts`, `googleId` field). The SvelteKit CSRF config trusts `https://appleid.apple.com` for the cross-origin `form_post` callback.
|
|
||||||
|
|
||||||
### Stats & Streak (`src/routes/stats/`, `src/routes/progress/`)
|
|
||||||
|
|
||||||
- `/stats` and `/progress` require auth; the server load returns `requiresAuth: true` for unauthenticated visitors, and the page renders a sign-in modal.
|
|
||||||
- The current streak is fetched from the server via `GET /api/streak?anonymousId=X&localDate=Y` (the server never uses its own clock).
|
|
||||||
- Streak walk-back: counts consecutive days backwards from `localDate` through `dailyCompletions`; stops at the first missing day. Single-day streaks are reported as `0` — the minimum displayed streak is 2.
|
|
||||||
- `/api/streak` walks from `localDate` only (it's called after a win, so today is completed). `/api/streak-percentile`, which ranks all players, anchors on today-if-played-else-yesterday so mid-day streaks aren't zeroed.
|
|
||||||
- Achievements/milestones are computed server-side in `src/lib/server/milestones.ts`.
|
|
||||||
|
|
||||||
## API Endpoints
|
|
||||||
|
|
||||||
- `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, percentile. Unique on `(anonymousId, date)`.
|
|
||||||
- `GET /api/streak?anonymousId=X&localDate=Y` — Current streak for a player
|
|
||||||
- `GET /api/streak-percentile?streak=N&localDate=Y` — Streak percentile ranking across all players
|
|
||||||
- `GET /api/stats` — Aggregated stats for the `/global` dashboard
|
|
||||||
- `GET /api/imposter` — Generate 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) and `GET /sitemap.xml` (SEO).
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
- `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/routes/imposter/`, `/random`, `/greek-random`, `/similarity` — Alternate game/debug modes
|
|
||||||
- `src/routes/about/`, `/global/`, `/progress/`, `/stats/`, `/dev/` — Supporting pages
|
|
||||||
- `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
|
|
||||||
- `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/types/bible.ts` — 66-book metadata and TypeScript types
|
|
||||||
- `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
|
|
||||||
|
|
||||||
## 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)
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user