mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-04-05 17:33:31 -04:00
246 lines
6.3 KiB
Svelte
246 lines
6.3 KiB
Svelte
<script lang="ts">
|
|
import { fade } from "svelte/transition";
|
|
import {
|
|
getBookById,
|
|
toOrdinal,
|
|
getNextGradeMessage,
|
|
} from "$lib/utils/game";
|
|
import { onMount } from "svelte";
|
|
import Container from "./Container.svelte";
|
|
import CountdownTimer from "./CountdownTimer.svelte";
|
|
import ChapterGuess from "./ChapterGuess.svelte";
|
|
|
|
interface StatsData {
|
|
solveRank: number;
|
|
guessRank: number;
|
|
totalSolves: number;
|
|
averageGuesses: number;
|
|
tiedCount: number;
|
|
percentile: number;
|
|
}
|
|
|
|
interface WeightedMessage {
|
|
text: string;
|
|
weight: number;
|
|
}
|
|
|
|
let {
|
|
grade,
|
|
statsData,
|
|
correctBookId,
|
|
handleShare,
|
|
copyToClipboard,
|
|
copied = $bindable(false),
|
|
statsSubmitted,
|
|
guessCount,
|
|
reference,
|
|
onChapterGuessCompleted,
|
|
} = $props();
|
|
|
|
let bookName = $derived(getBookById(correctBookId)?.name ?? "");
|
|
let hasWebShare = $derived(
|
|
typeof navigator !== "undefined" && "share" in navigator,
|
|
);
|
|
let copySuccess = $state(false);
|
|
|
|
// List of congratulations messages with weights
|
|
const congratulationsMessages: WeightedMessage[] = [
|
|
{ text: "Congratulations!", weight: 10 },
|
|
{ text: "You got it!", weight: 1000 },
|
|
{ text: "Yup.", weight: 100 },
|
|
{ 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.99) {
|
|
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>
|
|
|
|
<div class="flex flex-col gap-6">
|
|
<Container
|
|
class="w-full p-8 sm:p-12 bg-linear-to-r from-green-400/10 to-green-600/30 text-gray-800 shadow-2xl text-center fade-in"
|
|
>
|
|
<p class="text-2xl sm:text-3xl md:text-4xl leading-relaxed">
|
|
{congratulationsMessage} The verse is from
|
|
<span class="font-black text-3xl md:text-4xl">{bookName}</span>.
|
|
</p>
|
|
<p class="text-lg sm:text-xl md:text-2xl mt-4">
|
|
You guessed correctly after {guessCount}
|
|
{guessCount === 1 ? "guess" : "guesses"}.
|
|
<span class="font-bold bg-white/40 rounded px-1.5 py-0.75"
|
|
>{grade}</span
|
|
>
|
|
</p>
|
|
|
|
<div class="flex justify-center mt-6">
|
|
{#if hasWebShare}
|
|
<!-- mobile and arc in production -->
|
|
<button
|
|
onclick={handleShare}
|
|
data-umami-event="Share"
|
|
class="text-2xl font-bold p-4 bg-white/70 hover:bg-white/80 rounded-xl inline-block transition-all shadow-lg mx-2 cursor-pointer border-none appearance-none"
|
|
>
|
|
📤 Share
|
|
</button>
|
|
<button
|
|
onclick={() => {
|
|
copyToClipboard();
|
|
copySuccess = true;
|
|
setTimeout(() => {
|
|
copySuccess = false;
|
|
}, 3000);
|
|
}}
|
|
data-umami-event="Copy to Clipboard"
|
|
class={`text-2xl font-bold p-4 rounded-lg inline-block transition-all shadow-lg mx-2 cursor-pointer border-none appearance-none ${
|
|
copySuccess
|
|
? "bg-white/30"
|
|
: "bg-white/70 hover:bg-white/80"
|
|
}`}
|
|
>
|
|
{copySuccess ? "✅ Copied!" : "📋 Copy"}
|
|
</button>
|
|
{:else}
|
|
<!-- dev mode and desktop browsers -->
|
|
<button
|
|
onclick={handleShare}
|
|
data-umami-event="Copy to Clipboard"
|
|
class={`text-2xl font-bold p-4 ${
|
|
copied ? "bg-white/30" : "bg-white/70 hover:bg-white/80"
|
|
} rounded-xl inline-block transition-all shadow-lg mx-2 cursor-pointer border-none appearance-none`}
|
|
>
|
|
{copied ? "✅ Copied!" : "📋 Share"}
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
|
|
{#if guessCount !== 1}
|
|
<p class="pt-6 big-text text-gray-700!">
|
|
{getNextGradeMessage(guessCount)}
|
|
</p>
|
|
{/if}
|
|
</Container>
|
|
|
|
<!-- S++ Bonus Challenge for first try -->
|
|
{#if guessCount === 1}
|
|
<ChapterGuess
|
|
{reference}
|
|
bookId={correctBookId}
|
|
onCompleted={onChapterGuessCompleted}
|
|
/>
|
|
{/if}
|
|
|
|
<CountdownTimer />
|
|
|
|
<!-- Statistics Display -->
|
|
{#if statsData}
|
|
<Container
|
|
class="w-full p-4 bg-white/50 backdrop-blur-sm text-gray-800 shadow-lg text-center"
|
|
>
|
|
<div
|
|
class="grid grid-cols-3 gap-4 gap-x-8 text-center"
|
|
in:fade={{ delay: 800 }}
|
|
>
|
|
<!-- Solve Rank Column -->
|
|
<div class="flex flex-col">
|
|
<div
|
|
class="text-3xl sm:text-4xl font-black border-b border-gray-300 pb-2"
|
|
>
|
|
#{statsData.solveRank}
|
|
</div>
|
|
<div class="text-sm sm:text-sm opacity-90 mt-1">
|
|
You were the {toOrdinal(statsData.solveRank)} person to solve
|
|
today
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Guess Rank Column -->
|
|
<div class="flex flex-col">
|
|
<div
|
|
class="text-3xl sm:text-4xl font-black border-b border-gray-300 pb-2"
|
|
>
|
|
{toOrdinal(statsData.guessRank)}
|
|
</div>
|
|
<div class="text-sm sm:text-sm opacity-90 mt-1">
|
|
You ranked {toOrdinal(statsData.guessRank)} of {statsData.totalSolves}
|
|
{statsData.totalSolves === 1
|
|
? "solve"
|
|
: "solves"}{statsData.tiedCount > 0
|
|
? `, tied with ${statsData.tiedCount} ${statsData.tiedCount === 1 ? "other" : "others"}`
|
|
: ""}.<br />
|
|
{#if statsData.percentile <= 25}
|
|
<span class="font-bold">
|
|
(Top {statsData.percentile}%)
|
|
</span>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Average Column -->
|
|
<div class="flex flex-col">
|
|
<div
|
|
class="text-3xl sm:text-4xl font-black border-b border-gray-300 pb-2"
|
|
>
|
|
{statsData.averageGuesses}
|
|
</div>
|
|
<div class="text-sm sm:text-sm opacity-90 mt-1">
|
|
People solved after {statsData.averageGuesses}
|
|
{statsData.averageGuesses === 1 ? "guess" : "guesses"} on
|
|
average
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Container>
|
|
{:else if !statsSubmitted}
|
|
<Container
|
|
class="w-full p-6 bg-white/50 backdrop-blur-sm text-gray-800 shadow-lg text-center"
|
|
>
|
|
<div class="text-sm opacity-80">Submitting stats...</div>
|
|
</Container>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.fade-in {
|
|
animation: fadeIn 0.5s ease-out;
|
|
}
|
|
</style>
|