mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-02-04 10:54:44 -05:00
i forhor
This commit is contained in:
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.css": "tailwindcss"
|
"*.css": "tailwindcss"
|
||||||
|
},
|
||||||
|
"workbench.colorCustomizations": {
|
||||||
|
"titleBar.activeBackground": "#7e498b",
|
||||||
|
"settings.headerBorder": "#fff"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
25
todo.md
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user