v2.1: stylistic updates & countdown timer

This commit is contained in:
George Powell
2025-12-21 18:24:38 -05:00
parent 0d50ff5f27
commit 5999221b8f
14 changed files with 274 additions and 22 deletions

View File

@@ -1,12 +1,7 @@
import type { Handle } from '@sveltejs/kit';
import * as auth from '$lib/server/auth';
import { redirect } from '@sveltejs/kit';
const handleAuth: Handle = async ({ event, resolve }) => {
if (event.url.hostname === 'bibdle.orthodox.cafe') {
throw redirect(301, `https://bibdle.com${event.url.pathname}${event.url.search}${event.url.hash}`);
}
const sessionToken = event.cookies.get(auth.sessionCookieName);
if (!sessionToken) {

View File

@@ -0,0 +1,90 @@
<script lang="ts">
import { onMount, onDestroy } from "svelte";
let timeUntilNext = $state("");
let intervalId: number | null = null;
function calculateTimeUntilFivePM(): string {
const now = new Date();
const target = new Date(now);
// Set target to 5:00 PM today
target.setHours(17, 0, 0, 0);
// If it's already past 5:00 PM, set target to tomorrow 5:00 PM
if (now.getTime() >= target.getTime()) {
target.setDate(target.getDate() + 1);
}
const diff = target.getTime() - now.getTime();
if (diff <= 0) {
return "00:00:00";
}
const hours = Math.floor(diff / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
return `${hours.toString().padStart(2, "0")}h ${minutes
.toString()
.padStart(2, "0")}m ${seconds.toString().padStart(2, "0")}s`;
}
function calculateTimeUntilMidnight(): string {
const now = new Date();
const target = new Date(now);
// Set target to midnight today
target.setHours(0, 0, 0, 0);
// If it's already past midnight, set target to tomorrow midnight
if (now.getTime() >= target.getTime()) {
target.setDate(target.getDate() + 1);
}
const diff = target.getTime() - now.getTime();
if (diff <= 0) {
return "00:00:00";
}
const hours = Math.floor(diff / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
return `${hours.toString().padStart(2, "0")}h ${minutes
.toString()
.padStart(2, "0")}m ${seconds.toString().padStart(2, "0")}s`;
}
function updateTimer() {
timeUntilNext = calculateTimeUntilMidnight();
}
onMount(() => {
updateTimer();
intervalId = window.setInterval(updateTimer, 1000);
});
onDestroy(() => {
if (intervalId) {
clearInterval(intervalId);
}
});
</script>
<div class="text-center py-12">
<div
class="inline-flex flex-col items-center bg-white/50 backdrop-blur-sm px-8 py-4 rounded-2xl border border-white/50 shadow-sm"
>
<p
class="text-xs uppercase tracking-[0.2em] text-gray-500 font-bold mb-2"
>
Next Verse In
</p>
<p class="text-4xl font-triodion font-black text-gray-800 tabular-nums">
{timeUntilNext}
</p>
</div>
</div>

View File

@@ -2,7 +2,7 @@
import { fade } from "svelte/transition";
</script>
<div
<!-- <div
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 }}
>
@@ -12,4 +12,17 @@
class="font-semibold text-blue-600 hover:text-blue-800 underline"
>george@snail.city</a
>
</div> -->
<div class="text-center py-12">
<div
class="inline-flex w-full flex-col items-center bg-white/50 backdrop-blur-sm px-8 py-4 rounded-2xl border border-white/50 shadow-sm"
>
<p class="text-xs uppercase tracking-[0.2em] text-gray-500 font-bold">
A project by George Powell & Silent Summit Co.
</p>
<!-- <p class="text-4xl font-triodion font-black text-gray-800 tabular-nums">
</p> -->
</div>
</div>

View File

@@ -38,7 +38,10 @@
<tbody>
{#each guesses as guess (guess.book.id)}
<tr
class="border-b border-gray-100 hover:bg-gray-50 transition-colors"
class="border-b border-gray-100 transition-colors {guess
.book.id === correctBookId
? 'bg-green-200 animate-shine'
: 'hover:bg-gray-50'}"
>
<td
class="p-3 sm:p-4 md:p-6 text-sm sm:text-base font-bold md:text-lg"
@@ -64,3 +67,25 @@
</tbody>
</table>
</div>
<style>
@keyframes shine {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
.animate-shine {
background: linear-gradient(
110deg,
#dcffe7 45%,
#f1fff5 50%,
#dcffe7 55%
);
background-size: 200% 100%;
animation: shine 5s infinite;
}
</style>

View File

@@ -5,9 +5,9 @@
let dailyVerse = $derived(data.dailyVerse);
</script>
<div class="bg-white rounded-2xl shadow-xl p-8 sm:p-12 mb-8 sm:mb-12 w-full">
<div class="bg-gray-50 rounded-2xl shadow-xl p-8 sm:p-12 mb-8 sm:mb-12 w-full">
<blockquote
class="text-xl sm:text-2xl leading-relaxed text-gray-700 italic text-center"
class="text-xl sm:text-2xl font-triodion leading-relaxed text-gray-700 text-center"
>
{dailyVerse.verseText}
</blockquote>

View File

@@ -29,6 +29,7 @@
let hasWebShare = $derived(
typeof navigator !== "undefined" && "share" in navigator,
);
let copySuccess = $state(false);
// List of congratulations messages with weights
const congratulationsMessages: WeightedMessage[] = [
@@ -72,8 +73,7 @@
</script>
<div
class="mb-12 p-8 sm:p-12 w-full bg-linear-to-r from-green-400 to-green-600 text-white rounded-2xl shadow-2xl text-center"
in:fade={{ delay: 500 }}
class="p-8 sm:p-12 w-full bg-linear-to-r from-green-400 to-green-600 text-white rounded-2xl shadow-2xl text-center"
>
<h2 class="text-2xl sm:text-4xl font-black mb-4 drop-shadow-lg">
{congratulationsMessage}
@@ -98,11 +98,21 @@
📤 Share
</button>
<button
onclick={copyToClipboard}
onclick={() => {
copyToClipboard();
copySuccess = true;
setTimeout(() => {
copySuccess = false;
}, 3000);
}}
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"
class={`mt-4 text-2xl font-bold p-2 rounded-lg inline-block transition-all shadow-lg mx-2 cursor-pointer border-none appearance-none ${
copySuccess
? "bg-green-400/50 hover:bg-green-500/60"
: "bg-white/20 hover:bg-white/30"
}`}
>
📋 Copy to clipboard
{copySuccess ? "✅ Copied!" : "📋 Copy to clipboard"}
</button>
{:else}
<button

View File

@@ -9,13 +9,16 @@ import type { DailyVerse } from '$lib/server/db/schema';
import crypto from 'node:crypto';
async function getTodayVerse(): Promise<DailyVerse> {
// Get the current date (server-side)
const dateStr = new Date().toLocaleDateString('en-CA', { timeZone: 'America/New_York' });
// If there's an existing verse for the current date, return it
const existing = await db.select().from(dailyVerses).where(eq(dailyVerses.date, dateStr)).limit(1);
if (existing.length > 0) {
return existing[0];
}
// Otherwise get a new random verse
const apiVerse = await fetchRandomVerse();
const createdAt = sql`${Math.floor(Date.now() / 1000)}`;

View File

@@ -7,6 +7,7 @@
import VerseDisplay from "$lib/components/VerseDisplay.svelte";
import SearchInput from "$lib/components/SearchInput.svelte";
import GuessesTable from "$lib/components/GuessesTable.svelte";
import CountdownTimer from "$lib/components/CountdownTimer.svelte";
import WinScreen from "$lib/components/WinScreen.svelte";
import Feedback from "$lib/components/Feedback.svelte";
import { getGrade } from "$lib/utils/game";
@@ -348,7 +349,7 @@
class="pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)] w-full max-w-3xl mx-auto px-4"
>
<h1
class="text-3xl md:text-4xl font-bold text-center text-gray-800 p-8 sm:p-12 drop-shadow-lg"
class="text-3xl md:text-4xl font-bold text-center uppercase text-gray-600 drop-shadow-2xl tracking-widest p-8 sm:p-12"
>
Bibdle <span class="font-normal">{isDev ? "dev" : ""}</span>
</h1>
@@ -368,6 +369,7 @@
{statsSubmitted}
guessCount={guesses.length}
/>
<CountdownTimer />
{/if}
<GuessesTable {guesses} {correctBookId} />

View File

@@ -1,2 +1,7 @@
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=PT+Serif:ital,wght@0,400;0,700;1,400;1,700&family=Triodion&family=Young+Serif&display=swap');
@import 'tailwindcss';
@plugin '@tailwindcss/typography';
@theme {
--font-triodion: "PT Serif", serif;
}