mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-04-06 01:43:32 -04:00
223 lines
5.0 KiB
Svelte
223 lines
5.0 KiB
Svelte
<script lang="ts">
|
|
import { fade } from "svelte/transition";
|
|
import { browser } from "$app/environment";
|
|
import Container from "./Container.svelte";
|
|
|
|
interface Props {
|
|
reference: string;
|
|
bookId: string;
|
|
onCompleted?: () => void;
|
|
}
|
|
|
|
let { reference, bookId, onCompleted }: Props = $props();
|
|
|
|
// Parse the chapter from the reference (e.g., "John 3:16" -> 3)
|
|
function parseChapterFromReference(ref: string): number {
|
|
const match = ref.match(/\s(\d+):/);
|
|
return match ? parseInt(match[1], 10) : 1;
|
|
}
|
|
|
|
// Get the number of chapters for a book
|
|
function getChapterCount(bookId: string): number {
|
|
const chapterCounts: Record<string, number> = {
|
|
GEN: 50,
|
|
EXO: 40,
|
|
LEV: 27,
|
|
NUM: 36,
|
|
DEU: 34,
|
|
JOS: 24,
|
|
JDG: 21,
|
|
RUT: 4,
|
|
"1SA": 31,
|
|
"2SA": 24,
|
|
"1KI": 22,
|
|
"2KI": 25,
|
|
"1CH": 29,
|
|
"2CH": 36,
|
|
EZR: 10,
|
|
NEH: 13,
|
|
EST: 10,
|
|
JOB: 42,
|
|
PSA: 150,
|
|
PRO: 31,
|
|
ECC: 12,
|
|
SNG: 8,
|
|
ISA: 66,
|
|
JER: 52,
|
|
LAM: 5,
|
|
EZK: 48,
|
|
DAN: 12,
|
|
HOS: 14,
|
|
JOL: 3,
|
|
AMO: 9,
|
|
OBA: 1,
|
|
JON: 4,
|
|
MIC: 7,
|
|
NAM: 3,
|
|
HAB: 3,
|
|
ZEP: 3,
|
|
HAG: 2,
|
|
ZEC: 14,
|
|
MAL: 4,
|
|
MAT: 28,
|
|
MRK: 16,
|
|
LUK: 24,
|
|
JHN: 21,
|
|
ACT: 28,
|
|
ROM: 16,
|
|
"1CO": 16,
|
|
"2CO": 13,
|
|
GAL: 6,
|
|
EPH: 6,
|
|
PHP: 4,
|
|
COL: 4,
|
|
"1TH": 5,
|
|
"2TH": 3,
|
|
"1TI": 6,
|
|
"2TI": 4,
|
|
TIT: 3,
|
|
PHM: 1,
|
|
HEB: 13,
|
|
JAS: 5,
|
|
"1PE": 5,
|
|
"2PE": 3,
|
|
"1JN": 5,
|
|
"2JN": 1,
|
|
"3JN": 1,
|
|
JUD: 1,
|
|
REV: 22,
|
|
};
|
|
return chapterCounts[bookId] || 1;
|
|
}
|
|
|
|
// Generate 4 random chapter options including the correct one
|
|
function generateChapterOptions(
|
|
correctChapter: number,
|
|
totalChapters: number,
|
|
): number[] {
|
|
const options = new Set<number>();
|
|
options.add(correctChapter);
|
|
|
|
if (totalChapters >= 4) {
|
|
while (options.size < 4) {
|
|
const randomChapter =
|
|
Math.floor(Math.random() * totalChapters) + 1;
|
|
options.add(randomChapter);
|
|
}
|
|
} else {
|
|
while (options.size < 4) {
|
|
const randomChapter = Math.floor(Math.random() * 10) + 1;
|
|
options.add(randomChapter);
|
|
}
|
|
}
|
|
return Array.from(options).sort(() => Math.random() - 0.5);
|
|
}
|
|
|
|
let correctChapter = $derived(parseChapterFromReference(reference));
|
|
let totalChapters = $derived(getChapterCount(bookId));
|
|
let chapterOptions = $state<number[]>([]);
|
|
|
|
$effect(() => {
|
|
if (chapterOptions.length === 0) {
|
|
chapterOptions = generateChapterOptions(
|
|
correctChapter,
|
|
totalChapters,
|
|
);
|
|
}
|
|
});
|
|
|
|
let selectedChapter = $state<number | null>(null);
|
|
let hasAnswered = $state(false);
|
|
|
|
// Load saved state from localStorage
|
|
$effect(() => {
|
|
if (!browser) return;
|
|
const key = `bibdle-chapter-guess-${reference}`;
|
|
const saved = localStorage.getItem(key);
|
|
if (saved) {
|
|
const data = JSON.parse(saved);
|
|
selectedChapter = data.selectedChapter;
|
|
hasAnswered = data.hasAnswered;
|
|
chapterOptions = data.chapterOptions ?? [];
|
|
}
|
|
});
|
|
|
|
// Save state to localStorage whenever options are generated or answer given
|
|
$effect(() => {
|
|
if (!browser || chapterOptions.length === 0) return;
|
|
const key = `bibdle-chapter-guess-${reference}`;
|
|
localStorage.setItem(
|
|
key,
|
|
JSON.stringify({ selectedChapter, hasAnswered, chapterOptions }),
|
|
);
|
|
});
|
|
|
|
function handleChapterSelect(chapter: number) {
|
|
if (hasAnswered) return;
|
|
selectedChapter = chapter;
|
|
hasAnswered = true;
|
|
if (onCompleted) {
|
|
onCompleted();
|
|
}
|
|
}
|
|
|
|
let isCorrect = $derived(
|
|
selectedChapter !== null && selectedChapter === correctChapter,
|
|
);
|
|
</script>
|
|
|
|
<Container
|
|
class="w-full p-3 sm:p-4 bg-linear-to-br from-yellow-100/80 to-amber-200/80 text-gray-800 shadow-md"
|
|
>
|
|
<div class="text-center">
|
|
<p class="font-bold mb-3 text-lg sm:text-xl">
|
|
Bonus Challenge
|
|
<span class="text-base sm:text-lg opacity-60 font-normal"
|
|
>— guess the chapter for an even higher grade</span
|
|
>
|
|
</p>
|
|
|
|
<div class="grid grid-cols-4 gap-2 justify-center mx-auto mb-3">
|
|
{#each chapterOptions as chapter (chapter)}
|
|
<button
|
|
onclick={() => handleChapterSelect(chapter)}
|
|
disabled={hasAnswered}
|
|
class={`
|
|
w-20 h-20 sm:w-24 sm:h-24 text-2xl sm:text-3xl font-bold rounded-xl
|
|
transition-all duration-300 border-2
|
|
${
|
|
hasAnswered
|
|
? chapter === correctChapter
|
|
? "bg-green-500 text-white border-green-600 shadow-lg"
|
|
: selectedChapter === chapter
|
|
? isCorrect
|
|
? "bg-green-500 text-white border-green-600 shadow-lg"
|
|
: "bg-red-400 text-white border-red-500"
|
|
: "bg-white/30 text-gray-400 border-gray-300 opacity-40"
|
|
: "bg-white/80 hover:bg-white text-gray-800 border-gray-300 hover:border-amber-400 hover:shadow-md cursor-pointer"
|
|
}
|
|
`}
|
|
>
|
|
{chapter}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
|
|
{#if hasAnswered}
|
|
<p
|
|
class="text-xl sm:text-2xl font-bold mb-2"
|
|
class:text-green-600={isCorrect}
|
|
class:text-red-600={!isCorrect}
|
|
>
|
|
{isCorrect ? "✓ Correct!" : "✗ Incorrect"}
|
|
</p>
|
|
<p class="text-sm opacity-80">
|
|
The verse is from chapter {correctChapter}
|
|
</p>
|
|
{#if isCorrect}
|
|
<p class="text-lg font-bold text-amber-600 mt-2">Grade: S++</p>
|
|
{/if}
|
|
{/if}
|
|
</div>
|
|
</Container>
|