mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-04-05 17:33:31 -04:00
added some nice animation details
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { browser } from "$app/environment";
|
||||
import { fade } from "svelte/transition";
|
||||
import type { PageData } from "../../routes/$types.js"; // Approximate type; adjust if needed
|
||||
import Container from "./Container.svelte";
|
||||
|
||||
@@ -20,19 +22,52 @@
|
||||
.replace(/^([a-z])/, (c) => c.toUpperCase())
|
||||
.replace(/[,:;-—]$/, "...")
|
||||
);
|
||||
|
||||
let showReference = $state(false);
|
||||
|
||||
// Delay showing reference until GuessesTable animation completes
|
||||
$effect(() => {
|
||||
if (!isWon) {
|
||||
showReference = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user already won today (page reload case)
|
||||
const winTrackedKey = `bibdle-win-tracked-${dailyVerse.date}`;
|
||||
const alreadyWonToday = browser && localStorage.getItem(winTrackedKey) === "true";
|
||||
|
||||
if (alreadyWonToday) {
|
||||
// User already won and is refreshing - show immediately
|
||||
showReference = true;
|
||||
} else {
|
||||
// User just won this session - delay for animation
|
||||
const animationDelay = 1800;
|
||||
const timeoutId = setTimeout(() => {
|
||||
showReference = true;
|
||||
}, animationDelay);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Container class="w-full p-8 sm:p-12 bg-white/70">
|
||||
<Container class="w-full p-8 sm:p-12 bg-white/70 overflow-hidden">
|
||||
<blockquote
|
||||
class="text-xl sm:text-2xl font-triodion leading-relaxed text-gray-700 text-center"
|
||||
>
|
||||
{displayVerseText}
|
||||
</blockquote>
|
||||
{#if isWon}
|
||||
<p
|
||||
class="text-center text-lg! big-text text-green-600! font-bold mt-8 bg-white/70 rounded-xl px-4 py-2"
|
||||
>
|
||||
{displayReference}
|
||||
</p>
|
||||
{/if}
|
||||
<div
|
||||
class="transition-all duration-500 ease-in-out overflow-hidden"
|
||||
style="max-height: {showReference ? '200px' : '0px'};"
|
||||
>
|
||||
{#if showReference}
|
||||
<p
|
||||
transition:fade={{ duration: 400 }}
|
||||
class="text-center text-lg! big-text text-green-600! font-bold mt-8 bg-white/70 rounded-xl px-4 py-2"
|
||||
>
|
||||
{displayReference}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</Container>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import DevButtons from "$lib/components/DevButtons.svelte";
|
||||
import AuthModal from "$lib/components/AuthModal.svelte";
|
||||
import { getGrade } from "$lib/utils/game";
|
||||
import { enhance } from '$app/forms';
|
||||
import { enhance } from "$app/forms";
|
||||
|
||||
interface Guess {
|
||||
book: BibleBook;
|
||||
@@ -64,6 +64,7 @@
|
||||
);
|
||||
|
||||
let isWon = $derived(guesses.some((g) => g.book.id === correctBookId));
|
||||
let showWinScreen = $state(false);
|
||||
let grade = $derived(
|
||||
isWon
|
||||
? guesses.length === 1 && chapterCorrect
|
||||
@@ -183,18 +184,25 @@
|
||||
$effect(() => {
|
||||
if (!browser) return;
|
||||
|
||||
const localDate = new Date().toLocaleDateString('en-CA');
|
||||
console.log('Date check:', { localDate, verseDate: dailyVerse.date, match: dailyVerse.date === localDate });
|
||||
const localDate = new Date().toLocaleDateString("en-CA");
|
||||
console.log("Date check:", {
|
||||
localDate,
|
||||
verseDate: dailyVerse.date,
|
||||
match: dailyVerse.date === localDate,
|
||||
});
|
||||
|
||||
if (dailyVerse.date === localDate) return;
|
||||
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
console.log('Fetching timezone-correct verse:', { localDate, timezone });
|
||||
console.log("Fetching timezone-correct verse:", {
|
||||
localDate,
|
||||
timezone,
|
||||
});
|
||||
|
||||
fetch('/api/daily-verse', {
|
||||
method: 'POST',
|
||||
fetch("/api/daily-verse", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
date: localDate,
|
||||
@@ -203,30 +211,36 @@
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((result) => {
|
||||
console.log('Received verse data:', result);
|
||||
console.log("Received verse data:", result);
|
||||
dailyVerse = result.dailyVerse;
|
||||
correctBookId = result.correctBookId;
|
||||
correctBook = result.correctBook;
|
||||
})
|
||||
.catch((err) => console.error('Failed to fetch timezone-correct verse:', err));
|
||||
.catch((err) =>
|
||||
console.error("Failed to fetch timezone-correct verse:", err),
|
||||
);
|
||||
});
|
||||
|
||||
// Reload when the user returns to a stale tab on a new calendar day
|
||||
$effect(() => {
|
||||
if (!browser) return;
|
||||
|
||||
const loadedDate = new Date().toLocaleDateString('en-CA');
|
||||
const loadedDate = new Date().toLocaleDateString("en-CA");
|
||||
|
||||
function onVisibilityChange() {
|
||||
if (document.hidden) return;
|
||||
const now = new Date().toLocaleDateString('en-CA');
|
||||
const now = new Date().toLocaleDateString("en-CA");
|
||||
if (now !== loadedDate) {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||
return () => document.removeEventListener('visibilitychange', onVisibilityChange);
|
||||
document.addEventListener("visibilitychange", onVisibilityChange);
|
||||
return () =>
|
||||
document.removeEventListener(
|
||||
"visibilitychange",
|
||||
onVisibilityChange,
|
||||
);
|
||||
});
|
||||
|
||||
// Initialize anonymous ID
|
||||
@@ -366,7 +380,7 @@
|
||||
async function submitStats() {
|
||||
try {
|
||||
const payload = {
|
||||
anonymousId: anonymousId, // Already set correctly in $effect above
|
||||
anonymousId: anonymousId, // Already set correctly in $effect above
|
||||
date: dailyVerse.date,
|
||||
guessCount: guesses.length,
|
||||
};
|
||||
@@ -405,6 +419,33 @@
|
||||
submitStats();
|
||||
});
|
||||
|
||||
// Delay showing win screen until GuessesTable animation completes
|
||||
$effect(() => {
|
||||
if (!isWon) {
|
||||
showWinScreen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user already won today (page reload case)
|
||||
const winTrackedKey = `bibdle-win-tracked-${dailyVerse.date}`;
|
||||
const alreadyWonToday =
|
||||
browser && localStorage.getItem(winTrackedKey) === "true";
|
||||
|
||||
if (alreadyWonToday) {
|
||||
// User already won and is refreshing - show immediately
|
||||
showWinScreen = true;
|
||||
} else {
|
||||
// User just won this session - delay for animation
|
||||
// Animation timing: last column starts at 1500ms, animation takes 600ms
|
||||
const animationDelay = 1800;
|
||||
const timeoutId = setTimeout(() => {
|
||||
showWinScreen = true;
|
||||
}, animationDelay);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!browser || !isWon) return;
|
||||
const key = `bibdle-win-tracked-${dailyVerse.date}`;
|
||||
@@ -455,7 +496,7 @@
|
||||
|
||||
lines.push(
|
||||
`${emojis}${guesses.length === 1 && chapterCorrect ? " ⭐" : ""}`,
|
||||
siteUrl
|
||||
siteUrl,
|
||||
);
|
||||
|
||||
return lines.join("\n");
|
||||
@@ -548,7 +589,7 @@
|
||||
<div class="animate-fade-in-up animate-delay-400">
|
||||
<SearchInput bind:searchQuery {guessedIds} {submitGuess} />
|
||||
</div>
|
||||
{:else}
|
||||
{:else if showWinScreen}
|
||||
<div class="animate-fade-in-up animate-delay-400">
|
||||
<WinScreen
|
||||
{grade}
|
||||
@@ -592,14 +633,23 @@
|
||||
<div class="mt-8 flex flex-col items-stretch md:items-center gap-3">
|
||||
<div class="flex flex-col md:flex-row gap-3">
|
||||
<a
|
||||
href="/stats?{user ? `userId=${user.id}` : `anonymousId=${anonymousId}`}&tz={encodeURIComponent(Intl.DateTimeFormat().resolvedOptions().timeZone)}"
|
||||
href="/stats?{user
|
||||
? `userId=${user.id}`
|
||||
: `anonymousId=${anonymousId}`}&tz={encodeURIComponent(
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
)}"
|
||||
class="inline-flex items-center justify-center px-4 py-2 bg-amber-600 text-white rounded-lg hover:bg-amber-700 transition-colors text-sm font-medium shadow-md"
|
||||
>
|
||||
📊 View Stats
|
||||
</a>
|
||||
|
||||
{#if user}
|
||||
<form method="POST" action="/auth/logout" use:enhance class="w-full md:w-auto">
|
||||
<form
|
||||
method="POST"
|
||||
action="/auth/logout"
|
||||
use:enhance
|
||||
class="w-full md:w-auto"
|
||||
>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center justify-center w-full px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors text-sm font-medium shadow-md"
|
||||
@@ -609,22 +659,43 @@
|
||||
</form>
|
||||
{:else}
|
||||
<button
|
||||
onclick={() => authModalOpen = true}
|
||||
onclick={() => (authModalOpen = true)}
|
||||
class="inline-flex items-center justify-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium shadow-md"
|
||||
>
|
||||
🔐 Sign In
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
{#if isDev}
|
||||
<div class="text-xs text-gray-600 bg-gray-100 px-3 py-2 rounded border">
|
||||
<div
|
||||
class="text-xs text-gray-600 bg-gray-100 px-3 py-2 rounded border"
|
||||
>
|
||||
<div><strong>Debug Info:</strong></div>
|
||||
<div>User: {user ? `${user.email} (ID: ${user.id})` : 'Not signed in'}</div>
|
||||
<div>Session: {session ? `Expires ${session.expiresAt.toLocaleDateString()}` : 'No session'}</div>
|
||||
<div>Anonymous ID: {anonymousId || 'Not set'}</div>
|
||||
<div>Client Local Time: {new Date().toLocaleString('en-US', { timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, timeZoneName: 'short' })}</div>
|
||||
<div>Client Local Date: {new Date().toLocaleDateString('en-CA')}</div>
|
||||
<div>
|
||||
User: {user
|
||||
? `${user.email} (ID: ${user.id})`
|
||||
: "Not signed in"}
|
||||
</div>
|
||||
<div>
|
||||
Session: {session
|
||||
? `Expires ${session.expiresAt.toLocaleDateString()}`
|
||||
: "No session"}
|
||||
</div>
|
||||
<div>Anonymous ID: {anonymousId || "Not set"}</div>
|
||||
<div>
|
||||
Client Local Time: {new Date().toLocaleString("en-US", {
|
||||
timeZone:
|
||||
Intl.DateTimeFormat().resolvedOptions()
|
||||
.timeZone,
|
||||
timeZoneName: "short",
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
Client Local Date: {new Date().toLocaleDateString(
|
||||
"en-CA",
|
||||
)}
|
||||
</div>
|
||||
<div>Daily Verse Date: {dailyVerse.date}</div>
|
||||
</div>
|
||||
<DevButtons />
|
||||
|
||||
Reference in New Issue
Block a user