mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-04-05 17:33:31 -04:00
Fixed instructions, added color border based on closeness between guess
and target
This commit is contained in:
41
src/lib/components/GamePrompt.svelte
Normal file
41
src/lib/components/GamePrompt.svelte
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { guessCount }: { guessCount: number } = $props();
|
||||||
|
|
||||||
|
let promptText = $state("What book of the Bible is this verse from?");
|
||||||
|
let visible = $state(true);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
let fadeOutId: ReturnType<typeof setTimeout>;
|
||||||
|
let fadeInId: ReturnType<typeof setTimeout>;
|
||||||
|
let changeId: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
|
function animateTo(newText: string, delay = 0) {
|
||||||
|
fadeOutId = setTimeout(() => {
|
||||||
|
visible = false;
|
||||||
|
changeId = setTimeout(() => {
|
||||||
|
promptText = newText;
|
||||||
|
visible = true;
|
||||||
|
}, 300);
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (guessCount === 0) {
|
||||||
|
animateTo("What book of the Bible is this verse from?");
|
||||||
|
} else {
|
||||||
|
animateTo("Guess again", 2100);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(fadeOutId);
|
||||||
|
clearTimeout(fadeInId);
|
||||||
|
clearTimeout(changeId);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p
|
||||||
|
class="big-text text-center mb-6 px-4"
|
||||||
|
style="transition: opacity 0.3s ease; opacity: {visible ? 1 : 0};"
|
||||||
|
>
|
||||||
|
{promptText}
|
||||||
|
</p>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { bibleBooks } from "$lib/types/bible";
|
import { bibleBooks } from "$lib/types/bible";
|
||||||
import { getFirstLetter, type Guess } from "$lib/utils/game";
|
import { getFirstLetter, type Guess } from "$lib/utils/game";
|
||||||
import Container from "./Container.svelte";
|
|
||||||
|
|
||||||
let {
|
let {
|
||||||
guesses,
|
guesses,
|
||||||
@@ -16,6 +15,19 @@
|
|||||||
return "bg-red-500 border-red-600";
|
return "bg-red-500 border-red-600";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBookBoxStyle(guess: Guess): string {
|
||||||
|
if (guess.book.id === correctBookId) {
|
||||||
|
return "background-color: #22c55e; border-color: #16a34a;";
|
||||||
|
}
|
||||||
|
const correctBook = bibleBooks.find((b) => b.id === correctBookId);
|
||||||
|
if (!correctBook)
|
||||||
|
return "background-color: #ef4444; border-color: #dc2626;";
|
||||||
|
const t = Math.abs(guess.book.order - correctBook.order) / 65;
|
||||||
|
const hue = 120 * Math.pow(1 - t, 3);
|
||||||
|
const lightness = 55 - (hue / 120) * 15;
|
||||||
|
return `background-color: #ef4444; border-color: hsl(${hue}, 80%, ${lightness}%);`;
|
||||||
|
}
|
||||||
|
|
||||||
function getBoxContent(
|
function getBoxContent(
|
||||||
guess: Guess,
|
guess: Guess,
|
||||||
column: "book" | "firstLetter" | "testament" | "section",
|
column: "book" | "firstLetter" | "testament" | "section",
|
||||||
@@ -64,17 +76,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if !hasGuesses}
|
{#if hasGuesses}
|
||||||
<Container class="p-6 text-center">
|
|
||||||
<h2 class="font-triodion text-xl italic mb-3 text-gray-800 dark:text-gray-100">
|
|
||||||
Instructions
|
|
||||||
</h2>
|
|
||||||
<p class="text-gray-700 dark:text-gray-300 leading-relaxed italic">
|
|
||||||
Guess what book of the bible you think the verse is from. You will
|
|
||||||
get clues to help you after each guess.
|
|
||||||
</p>
|
|
||||||
</Container>
|
|
||||||
{:else}
|
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<!-- Column Headers -->
|
<!-- Column Headers -->
|
||||||
<div
|
<div
|
||||||
@@ -146,10 +148,9 @@
|
|||||||
|
|
||||||
<!-- Book Column -->
|
<!-- Book Column -->
|
||||||
<div
|
<div
|
||||||
class="w-1/4 shrink-0 h-16 sm:h-20 md:h-24 border-4 border-opacity-100 rounded-lg flex items-center justify-center text-white font-bold text-base sm:text-lg md:text-xl shadow-lg animate-flip-in {getBoxColor(
|
class="w-1/4 shrink-0 h-16 sm:h-20 md:h-24 border-4 rounded-lg flex items-center justify-center text-white font-bold text-base sm:text-lg md:text-xl shadow-lg animate-flip-in"
|
||||||
guess.book.id === correctBookId,
|
style="animation-delay: {rowIndex * 1000 +
|
||||||
)}"
|
3 * 500}ms; {getBookBoxStyle(guess)}"
|
||||||
style="animation-delay: {rowIndex * 1000 + 3 * 500}ms"
|
|
||||||
>
|
>
|
||||||
<span class="text-center leading-tight px-1 text-shadow-lg"
|
<span class="text-center leading-tight px-1 text-shadow-lg"
|
||||||
>{getBoxContent(guess, "book")}</span
|
>{getBoxContent(guess, "book")}</span
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { bibleBooks, type BibleBook, type BibleSection, type Testament } from "$lib/types/bible";
|
import {
|
||||||
|
bibleBooks,
|
||||||
|
type BibleBook,
|
||||||
|
type BibleSection,
|
||||||
|
type Testament,
|
||||||
|
} from "$lib/types/bible";
|
||||||
import { SvelteSet } from "svelte/reactivity";
|
import { SvelteSet } from "svelte/reactivity";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -17,13 +22,13 @@
|
|||||||
type DisplayMode = "simple" | "testament" | "sections";
|
type DisplayMode = "simple" | "testament" | "sections";
|
||||||
|
|
||||||
const displayMode = $derived<DisplayMode>(
|
const displayMode = $derived<DisplayMode>(
|
||||||
guessCount >= 9 ? "sections" : guessCount >= 3 ? "testament" : "simple"
|
guessCount >= 9 ? "sections" : guessCount >= 3 ? "testament" : "simple",
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredBooks = $derived(
|
const filteredBooks = $derived(
|
||||||
bibleBooks.filter((book) =>
|
bibleBooks.filter((book) =>
|
||||||
book.name.toLowerCase().includes(searchQuery.toLowerCase())
|
book.name.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
type SimpleGroup = { books: BibleBook[] };
|
type SimpleGroup = { books: BibleBook[] };
|
||||||
@@ -44,7 +49,7 @@
|
|||||||
|
|
||||||
const simpleGroup = $derived.by<SimpleGroup>(() => {
|
const simpleGroup = $derived.by<SimpleGroup>(() => {
|
||||||
const sorted = [...filteredBooks].sort((a, b) =>
|
const sorted = [...filteredBooks].sort((a, b) =>
|
||||||
a.name.localeCompare(b.name)
|
a.name.localeCompare(b.name),
|
||||||
);
|
);
|
||||||
return { books: sorted };
|
return { books: sorted };
|
||||||
});
|
});
|
||||||
@@ -58,10 +63,18 @@
|
|||||||
.sort((a, b) => a.name.localeCompare(b.name));
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
const groups: TestamentGroup[] = [];
|
const groups: TestamentGroup[] = [];
|
||||||
if (old.length > 0) {
|
if (old.length > 0) {
|
||||||
groups.push({ testament: "old", label: "Old Testament", books: old });
|
groups.push({
|
||||||
|
testament: "old",
|
||||||
|
label: "Old Testament",
|
||||||
|
books: old,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (newT.length > 0) {
|
if (newT.length > 0) {
|
||||||
groups.push({ testament: "new", label: "New Testament", books: newT });
|
groups.push({
|
||||||
|
testament: "new",
|
||||||
|
label: "New Testament",
|
||||||
|
books: newT,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return groups;
|
return groups;
|
||||||
});
|
});
|
||||||
@@ -69,13 +82,17 @@
|
|||||||
const sectionGroups = $derived.by<SectionGroup[]>(() => {
|
const sectionGroups = $derived.by<SectionGroup[]>(() => {
|
||||||
// Build an ordered list of (testament, section) pairs by iterating bibleBooks once
|
// Build an ordered list of (testament, section) pairs by iterating bibleBooks once
|
||||||
const seenKeys: Record<string, true> = {};
|
const seenKeys: Record<string, true> = {};
|
||||||
const orderedPairs: { testament: Testament; section: BibleSection }[] = [];
|
const orderedPairs: { testament: Testament; section: BibleSection }[] =
|
||||||
|
[];
|
||||||
|
|
||||||
for (const book of bibleBooks) {
|
for (const book of bibleBooks) {
|
||||||
const key = `${book.testament}:${book.section}`;
|
const key = `${book.testament}:${book.section}`;
|
||||||
if (!seenKeys[key]) {
|
if (!seenKeys[key]) {
|
||||||
seenKeys[key] = true;
|
seenKeys[key] = true;
|
||||||
orderedPairs.push({ testament: book.testament, section: book.section });
|
orderedPairs.push({
|
||||||
|
testament: book.testament,
|
||||||
|
section: book.section,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +101,9 @@
|
|||||||
|
|
||||||
for (const pair of orderedPairs) {
|
for (const pair of orderedPairs) {
|
||||||
const books = filteredBooks.filter(
|
const books = filteredBooks.filter(
|
||||||
(b) => b.testament === pair.testament && b.section === pair.section
|
(b) =>
|
||||||
|
b.testament === pair.testament &&
|
||||||
|
b.section === pair.section,
|
||||||
);
|
);
|
||||||
if (books.length === 0) continue;
|
if (books.length === 0) continue;
|
||||||
|
|
||||||
@@ -94,7 +113,9 @@
|
|||||||
groups.push({
|
groups.push({
|
||||||
testament: pair.testament,
|
testament: pair.testament,
|
||||||
testamentLabel:
|
testamentLabel:
|
||||||
pair.testament === "old" ? "Old Testament" : "New Testament",
|
pair.testament === "old"
|
||||||
|
? "Old Testament"
|
||||||
|
: "New Testament",
|
||||||
showTestamentHeader,
|
showTestamentHeader,
|
||||||
section: pair.section,
|
section: pair.section,
|
||||||
books,
|
books,
|
||||||
@@ -122,7 +143,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const showBanner = $derived(guessCount >= 3);
|
// const showBanner = $derived(guessCount >= 3);
|
||||||
|
const showBanner = false;
|
||||||
const bannerIsIndigo = $derived(guessCount >= 9);
|
const bannerIsIndigo = $derived(guessCount >= 9);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -206,7 +228,9 @@
|
|||||||
tabindex={guessedIds.has(book.id) ? -1 : 0}
|
tabindex={guessedIds.has(book.id) ? -1 : 0}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="font-semibold dark:text-gray-100 {guessedIds.has(book.id)
|
class="font-semibold dark:text-gray-100 {guessedIds.has(
|
||||||
|
book.id,
|
||||||
|
)
|
||||||
? 'line-through text-gray-400 dark:text-gray-500'
|
? 'line-through text-gray-400 dark:text-gray-500'
|
||||||
: ''}"
|
: ''}"
|
||||||
>
|
>
|
||||||
@@ -226,21 +250,30 @@
|
|||||||
>
|
>
|
||||||
{group.label}
|
{group.label}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex-1 h-px bg-gray-200 dark:bg-gray-600"></div>
|
<div
|
||||||
|
class="flex-1 h-px bg-gray-200 dark:bg-gray-600"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
{#each group.books as book (book.id)}
|
{#each group.books as book (book.id)}
|
||||||
<li role="option" aria-selected={guessedIds.has(book.id)}>
|
<li
|
||||||
|
role="option"
|
||||||
|
aria-selected={guessedIds.has(book.id)}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
class="w-full px-5 py-4 text-left border-b border-gray-100 dark:border-gray-700 last:border-b-0 flex items-center transition-all dark:text-gray-200
|
class="w-full px-5 py-4 text-left border-b border-gray-100 dark:border-gray-700 last:border-b-0 flex items-center transition-all dark:text-gray-200
|
||||||
{guessedIds.has(book.id)
|
{guessedIds.has(book.id)
|
||||||
? 'opacity-50 cursor-not-allowed pointer-events-none'
|
? 'opacity-50 cursor-not-allowed pointer-events-none'
|
||||||
: 'hover:bg-blue-50 dark:hover:bg-blue-900/40 hover:text-blue-700 dark:hover:text-blue-300'}"
|
: 'hover:bg-blue-50 dark:hover:bg-blue-900/40 hover:text-blue-700 dark:hover:text-blue-300'}"
|
||||||
onclick={() => submitGuess(book.id)}
|
onclick={() => submitGuess(book.id)}
|
||||||
tabindex={guessedIds.has(book.id) ? -1 : 0}
|
tabindex={guessedIds.has(book.id)
|
||||||
|
? -1
|
||||||
|
: 0}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="font-semibold {guessedIds.has(book.id)
|
class="font-semibold {guessedIds.has(
|
||||||
|
book.id,
|
||||||
|
)
|
||||||
? 'line-through text-gray-400 dark:text-gray-500'
|
? 'line-through text-gray-400 dark:text-gray-500'
|
||||||
: ''}"
|
: ''}"
|
||||||
>
|
>
|
||||||
@@ -264,7 +297,9 @@
|
|||||||
>
|
>
|
||||||
{group.testamentLabel}
|
{group.testamentLabel}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex-1 h-px bg-gray-200 dark:bg-gray-600"></div>
|
<div
|
||||||
|
class="flex-1 h-px bg-gray-200 dark:bg-gray-600"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div
|
<div
|
||||||
@@ -275,21 +310,30 @@
|
|||||||
>
|
>
|
||||||
{group.section}
|
{group.section}
|
||||||
</span>
|
</span>
|
||||||
<div class="flex-1 h-px bg-gray-100 dark:bg-gray-600"></div>
|
<div
|
||||||
|
class="flex-1 h-px bg-gray-100 dark:bg-gray-600"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
{#each group.books as book (book.id)}
|
{#each group.books as book (book.id)}
|
||||||
<li role="option" aria-selected={guessedIds.has(book.id)}>
|
<li
|
||||||
|
role="option"
|
||||||
|
aria-selected={guessedIds.has(book.id)}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
class="w-full px-5 py-4 text-left border-b border-gray-100 dark:border-gray-700 last:border-b-0 flex items-center transition-all dark:text-gray-200
|
class="w-full px-5 py-4 text-left border-b border-gray-100 dark:border-gray-700 last:border-b-0 flex items-center transition-all dark:text-gray-200
|
||||||
{guessedIds.has(book.id)
|
{guessedIds.has(book.id)
|
||||||
? 'opacity-50 cursor-not-allowed pointer-events-none'
|
? 'opacity-50 cursor-not-allowed pointer-events-none'
|
||||||
: 'hover:bg-blue-50 dark:hover:bg-blue-900/40 hover:text-blue-700 dark:hover:text-blue-300'}"
|
: 'hover:bg-blue-50 dark:hover:bg-blue-900/40 hover:text-blue-700 dark:hover:text-blue-300'}"
|
||||||
onclick={() => submitGuess(book.id)}
|
onclick={() => submitGuess(book.id)}
|
||||||
tabindex={guessedIds.has(book.id) ? -1 : 0}
|
tabindex={guessedIds.has(book.id)
|
||||||
|
? -1
|
||||||
|
: 0}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="font-semibold {guessedIds.has(book.id)
|
class="font-semibold {guessedIds.has(
|
||||||
|
book.id,
|
||||||
|
)
|
||||||
? 'line-through text-gray-400 dark:text-gray-500'
|
? 'line-through text-gray-400 dark:text-gray-500'
|
||||||
: ''}"
|
: ''}"
|
||||||
>
|
>
|
||||||
@@ -304,6 +348,8 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
{:else if searchQuery}
|
{:else if searchQuery}
|
||||||
<p class="mt-4 text-center text-gray-500 dark:text-gray-400 p-8">No books found</p>
|
<p class="mt-4 text-center text-gray-500 dark:text-gray-400 p-8">
|
||||||
|
No books found
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import WinScreen from "$lib/components/WinScreen.svelte";
|
import WinScreen from "$lib/components/WinScreen.svelte";
|
||||||
import Credits from "$lib/components/Credits.svelte";
|
import Credits from "$lib/components/Credits.svelte";
|
||||||
|
|
||||||
|
import GamePrompt from "$lib/components/GamePrompt.svelte";
|
||||||
import DevButtons from "$lib/components/DevButtons.svelte";
|
import DevButtons from "$lib/components/DevButtons.svelte";
|
||||||
import AuthModal from "$lib/components/AuthModal.svelte";
|
import AuthModal from "$lib/components/AuthModal.svelte";
|
||||||
|
|
||||||
@@ -297,6 +298,8 @@
|
|||||||
|
|
||||||
{#if !isWon}
|
{#if !isWon}
|
||||||
<div class="animate-fade-in-up animate-delay-400">
|
<div class="animate-fade-in-up animate-delay-400">
|
||||||
|
<GamePrompt guessCount={persistence.guesses.length} />
|
||||||
|
|
||||||
<SearchInput
|
<SearchInput
|
||||||
bind:searchQuery
|
bind:searchQuery
|
||||||
{guessedIds}
|
{guessedIds}
|
||||||
@@ -335,8 +338,6 @@
|
|||||||
<Credits />
|
<Credits />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{#if isDev}
|
{#if isDev}
|
||||||
<div class="mt-8 flex flex-col items-stretch md:items-center gap-3">
|
<div class="mt-8 flex flex-col items-stretch md:items-center gap-3">
|
||||||
@@ -373,7 +374,11 @@
|
|||||||
<div>Daily Verse Date: {dailyVerse.date}</div>
|
<div>Daily Verse Date: {dailyVerse.date}</div>
|
||||||
<div>Streak: {streak}</div>
|
<div>Streak: {streak}</div>
|
||||||
</div>
|
</div>
|
||||||
<DevButtons anonymousId={persistence.anonymousId} {user} onSignIn={() => (authModalOpen = true)} />
|
<DevButtons
|
||||||
|
anonymousId={persistence.anonymousId}
|
||||||
|
{user}
|
||||||
|
onSignIn={() => (authModalOpen = true)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user