This commit is contained in:
George Powell
2025-12-19 03:36:59 -05:00
parent 68a946a0a0
commit 0d50ff5f27
5 changed files with 145 additions and 34 deletions

View File

@@ -1,5 +1,9 @@
{ {
"files.associations": { "files.associations": {
"*.css": "tailwindcss" "*.css": "tailwindcss"
},
"workbench.colorCustomizations": {
"titleBar.activeBackground": "#7e498b",
"settings.headerBorder": "#fff"
} }
} }

View File

@@ -3,7 +3,7 @@
</script> </script>
<div <div
class="mt-12 p-4 bg-linear-to-r from-blue-50 to-indigo-50 rounded-2xl shadow-md text-center text-sm md:text-base text-gray-600" class="my-12 p-4 bg-linear-to-r from-blue-50 to-indigo-50 rounded-2xl shadow-md text-center text-sm md:text-base text-gray-600"
in:fade={{ delay: 1500, duration: 1000 }} in:fade={{ delay: 1500, duration: 1000 }}
> >
Thank you so much for playing! Feel free to email me directly with feedback: Thank you so much for playing! Feel free to email me directly with feedback:

View File

@@ -9,16 +9,66 @@
averageGuesses: number; averageGuesses: number;
} }
interface WeightedMessage {
text: string;
weight: number;
}
let { let {
grade, grade,
statsData, statsData,
correctBookId, correctBookId,
handleShare, handleShare,
copyToClipboard,
copied = $bindable(false), copied = $bindable(false),
statsSubmitted, statsSubmitted,
guessCount,
} = $props(); } = $props();
let bookName = $derived(getBookById(correctBookId)?.name ?? ""); let bookName = $derived(getBookById(correctBookId)?.name ?? "");
let hasWebShare = $derived(
typeof navigator !== "undefined" && "share" in navigator,
);
// List of congratulations messages with weights
const congratulationsMessages: WeightedMessage[] = [
{ text: "🎉 Congratulations! 🎉", weight: 1000 },
{ text: "⭐ You got it! ⭐", weight: 10 },
{ text: "🎉 Yup 🎉", weight: 5 },
{ text: "👍🏻 Very nice! 👍🏻", weight: 1 },
];
// Function to select a random message based on weights
function getRandomCongratulationsMessage(): string {
// Special case for first try success
if (guessCount === 1) {
const n = Math.random();
if (n < 0.95) {
return "🤯 First try! 🤯";
} else {
return "‼️ Axios ‼️";
}
}
const totalWeight = congratulationsMessages.reduce(
(sum, msg) => sum + msg.weight,
0,
);
let random = Math.random() * totalWeight;
for (const message of congratulationsMessages) {
random -= message.weight;
if (random <= 0) {
return message.text;
}
}
// Fallback to first message if something goes wrong
return congratulationsMessages[0].text;
}
// Generate the congratulations message
let congratulationsMessage = $derived(getRandomCongratulationsMessage());
</script> </script>
<div <div
@@ -26,7 +76,7 @@
in:fade={{ delay: 500 }} in:fade={{ delay: 500 }}
> >
<h2 class="text-2xl sm:text-4xl font-black mb-4 drop-shadow-lg"> <h2 class="text-2xl sm:text-4xl font-black mb-4 drop-shadow-lg">
🎉 Congratulations! 🎉 {congratulationsMessage}
</h2> </h2>
<p class="text-lg sm:text-xl md:text-2xl"> <p class="text-lg sm:text-xl md:text-2xl">
The verse is from <span The verse is from <span
@@ -39,32 +89,48 @@
Your grade: {grade} Your grade: {grade}
</p> </p>
<button {#if hasWebShare}
onclick={handleShare} <button
data-umami-event="Share" onclick={handleShare}
class={`mt-4 text-2xl font-bold p-2 ${ data-umami-event="Share"
copied class="mt-4 text-2xl font-bold p-2 bg-white/20 hover:bg-white/30 rounded-lg inline-block transition-all shadow-lg mx-2 cursor-pointer border-none appearance-none"
? "bg-green-400/50 hover:bg-green-500/60" >
: "bg-white/20 hover:bg-white/30" 📤 Share
} rounded-lg inline-block transition-all shadow-lg mx-2 cursor-pointer border-none appearance-none`} </button>
> <button
{copied ? "Copied to clipboard!" : "📤 Share"} onclick={copyToClipboard}
</button> data-umami-event="Copy to Clipboard"
class="mt-4 text-2xl font-bold p-2 bg-white/20 hover:bg-white/30 rounded-lg inline-block transition-all shadow-lg mx-2 cursor-pointer border-none appearance-none"
>
📋 Copy to clipboard
</button>
{:else}
<button
onclick={handleShare}
data-umami-event="Share"
class={`mt-4 text-2xl font-bold p-2 ${
copied
? "bg-green-400/50 hover:bg-green-500/60"
: "bg-white/20 hover:bg-white/30"
} rounded-lg inline-block transition-all shadow-lg mx-2 cursor-pointer border-none appearance-none`}
>
{copied ? "Copied to clipboard!" : "📤 Share"}
</button>
{/if}
<!-- Statistics Display --> <!-- Statistics Display -->
{#if statsData} {#if statsData}
<div class="mt-6 space-y-2 text-lg" in:fade={{ delay: 800 }}> <div class="mt-6 space-y-2 text-lg" in:fade={{ delay: 800 }}>
<p class="font-semibold"> <p class="font-regular">
You were the {toOrdinal(statsData.solveRank)} person to solve today. You were the {toOrdinal(statsData.solveRank)} person to solve today.
</p> </p>
<p class="font-semibold"> <p class="font-regular">
You rank <span class="text-2xl font-black" You rank <span class="">{toOrdinal(statsData.guessRank)}</span> in
>{toOrdinal(statsData.guessRank)}</span guesses.
> in guesses.
</p> </p>
<p class="opacity-90"> <p class="opacity-90">
Average: <span class="font-semibold" Average: <span class="font-semibold"
>{statsData.averageGuesses.toFixed(1)}</span >{Math.ceil(statsData.averageGuesses)}</span
> guesses > guesses
</p> </p>
</div> </div>

View File

@@ -1,16 +1,8 @@
<script lang="ts"> <script lang="ts">
import { bibleBooks, type BibleBook } from "$lib/types/bible"; import { bibleBooks, type BibleBook } from "$lib/types/bible";
interface Guess {
book: BibleBook;
testamentMatch: boolean;
sectionMatch: boolean;
adjacent: boolean;
}
import type { PageProps } from "./$types"; import type { PageProps } from "./$types";
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import { fade } from "svelte/transition";
import VerseDisplay from "$lib/components/VerseDisplay.svelte"; import VerseDisplay from "$lib/components/VerseDisplay.svelte";
import SearchInput from "$lib/components/SearchInput.svelte"; import SearchInput from "$lib/components/SearchInput.svelte";
@@ -19,6 +11,13 @@
import Feedback from "$lib/components/Feedback.svelte"; import Feedback from "$lib/components/Feedback.svelte";
import { getGrade } from "$lib/utils/game"; import { getGrade } from "$lib/utils/game";
interface Guess {
book: BibleBook;
testamentMatch: boolean;
sectionMatch: boolean;
adjacent: boolean;
}
let { data }: PageProps = $props(); let { data }: PageProps = $props();
let dailyVerse = $derived(data.dailyVerse); let dailyVerse = $derived(data.dailyVerse);
@@ -254,9 +253,7 @@
submitStats(); submitStats();
}); });
async function share() { function generateShareText(): string {
if (!browser) return;
const emojis = guesses const emojis = guesses
.slice() .slice()
.reverse() .reverse()
@@ -278,12 +275,18 @@
new Date(`${dailyVerse.date}T00:00:00`), new Date(`${dailyVerse.date}T00:00:00`),
); );
const siteUrl = window.location.origin; const siteUrl = window.location.origin;
const shareText = [ return [
`📖 Bibdle | ${formattedDate} 📖`, `📖 Bibdle | ${formattedDate} 📖`,
`${grade} (${guesses.length} guesses)`, `${grade} (${guesses.length} guesses)`,
`${emojis}\n`, `${emojis}\n`,
siteUrl, siteUrl,
].join("\n"); ].join("\n");
}
async function share() {
if (!browser) return;
const shareText = generateShareText();
try { try {
if ("share" in navigator) { if ("share" in navigator) {
@@ -297,6 +300,23 @@
} }
} }
async function copyToClipboard() {
if (!browser) return;
const shareText = generateShareText();
try {
await (navigator as any).clipboard.writeText(shareText);
copied = true;
setTimeout(() => {
copied = false;
}, 5000);
} catch (err) {
console.error("Copy to clipboard failed:", err);
throw err;
}
}
function handleShare() { function handleShare() {
if (copied || !browser) return; if (copied || !browser) return;
const useClipboard = !("share" in navigator); const useClipboard = !("share" in navigator);
@@ -343,8 +363,10 @@
{statsData} {statsData}
{correctBookId} {correctBookId}
{handleShare} {handleShare}
{copyToClipboard}
bind:copied bind:copied
{statsSubmitted} {statsSubmitted}
guessCount={guesses.length}
/> />
{/if} {/if}

25
todo.md
View File

@@ -1,12 +1,31 @@
# todo # todo
## v2 ## v3
- avg guesses per bible verse updating daily (on completion: avg. guesses: 6) - HH:MM until next verse (sundown local time)
- you're the XXXth person to guess correctly today
- https://github.com/GitSquared/node-geolite2-redist
get approximate user location from IP
- https://www.npmjs.com/package/sunrise-sunset-js
get sunrise/sunset times by lat/longitude
- top XX% next to the "rank" stat on complete
-
# bibdle unlimited
- pay for unlimited bible verses daily,
- and extra features like multiple choice, custom groups, custom leaderboards, etc.
# done # done
- improve design (uniform column widths on desktop)
- moved to bibdle.com
- v2: avg guesses per bible verse updating daily (on completion: avg. guesses: 6)
- v2: you're the XXXth person to guess correctly today
- metadata - metadata
- favicon - favicon
- site title - site title