Revamped middle statline (ranking instead of arbitrary percentage)

This commit is contained in:
George Powell
2026-01-28 15:04:29 -05:00
parent 6365cfb363
commit 0ee3d8a4d0
3 changed files with 656 additions and 623 deletions

View File

@@ -1,222 +1,233 @@
<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";
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;
}
interface StatsData {
solveRank: number;
guessRank: number;
totalSolves: number;
averageGuesses: number;
tiedCount: number;
}
interface WeightedMessage {
text: string;
weight: number;
}
interface WeightedMessage {
text: string;
weight: number;
}
let {
grade,
statsData,
correctBookId,
handleShare,
copyToClipboard,
copied = $bindable(false),
statsSubmitted,
guessCount,
reference,
onChapterGuessCompleted,
} = $props();
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);
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 },
];
// 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! 🗣️";
}
}
// 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;
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;
}
}
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;
}
// Fallback to first message if something goes wrong
return congratulationsMessages[0].text;
}
// Generate the congratulations message
let congratulationsMessage = $derived(getRandomCongratulationsMessage());
// 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>
<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>
<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>
{#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}
<!-- S++ Bonus Challenge for first try -->
{#if guessCount === 1}
<ChapterGuess
{reference}
bookId={correctBookId}
onCompleted={onChapterGuessCompleted}
/>
{/if}
<CountdownTimer />
<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">
#{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>
<!-- 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">
#{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">
{Math.round(
((statsData.totalSolves - statsData.guessRank + 1) /
statsData.totalSolves) *
100
)}%
</div>
<div class="text-sm sm:text-sm opacity-90 mt-1">
You ranked {toOrdinal(statsData.guessRank)} of {statsData.totalSolves}
total solves
</div>
</div>
<!-- Guess Rank Column -->
<div class="flex flex-col">
<div class="text-3xl sm:text-4xl font-black">
{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"}`
: ""}
</div>
</div>
<!-- Average Column -->
<div class="flex flex-col">
<div class="text-3xl sm:text-4xl font-black">
{statsData.averageGuesses}
</div>
<div class="text-sm sm:text-sm opacity-90 mt-1">
People guessed correctly 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}
<!-- Average Column -->
<div class="flex flex-col">
<div class="text-3xl sm:text-4xl font-black">
{statsData.averageGuesses}
</div>
<div class="text-sm sm:text-sm opacity-90 mt-1">
People guessed correctly 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);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
.fade-in {
animation: fadeIn 0.5s ease-out;
}
</style>