mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-04-06 01:43:32 -04:00
Added streak counter
This commit is contained in:
@@ -1,10 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { fade, fly } from "svelte/transition";
|
||||
import {
|
||||
getBookById,
|
||||
toOrdinal,
|
||||
getNextGradeMessage,
|
||||
} from "$lib/utils/game";
|
||||
import { getBookById, toOrdinal } from "$lib/utils/game";
|
||||
import { onMount } from "svelte";
|
||||
import Container from "./Container.svelte";
|
||||
import CountdownTimer from "./CountdownTimer.svelte";
|
||||
@@ -25,7 +21,6 @@
|
||||
}
|
||||
|
||||
let {
|
||||
grade,
|
||||
statsData,
|
||||
correctBookId,
|
||||
handleShare,
|
||||
@@ -36,6 +31,19 @@
|
||||
reference,
|
||||
onChapterGuessCompleted,
|
||||
shareText,
|
||||
streak = 0,
|
||||
}: {
|
||||
statsData: StatsData | null;
|
||||
correctBookId: string;
|
||||
handleShare: () => void;
|
||||
copyToClipboard: () => void;
|
||||
copied: boolean;
|
||||
statsSubmitted: boolean;
|
||||
guessCount: number;
|
||||
reference: string;
|
||||
onChapterGuessCompleted: () => void;
|
||||
shareText: string;
|
||||
streak?: number;
|
||||
} = $props();
|
||||
|
||||
let bookName = $derived(getBookById(correctBookId)?.name ?? "");
|
||||
@@ -96,50 +104,15 @@
|
||||
<p class="text-lg sm:text-xl md:text-2xl mt-2">
|
||||
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>
|
||||
|
||||
{#if guessCount !== 1}
|
||||
<p class="pt-6 big-text text-gray-700!">
|
||||
{getNextGradeMessage(guessCount)}
|
||||
{#if streak > 1}
|
||||
<p class="big-text text-orange-500! text-sm! mt-4">
|
||||
🔥 {streak} day streak!
|
||||
</p>
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
<div class="share-card" in:fly={{ y: 40, duration: 400, delay: 600 }}>
|
||||
<div class="big-text font-black! text-center">Share your result</div>
|
||||
<div class="share-buttons">
|
||||
{#if hasWebShare}
|
||||
<button
|
||||
onclick={handleShare}
|
||||
data-umami-event="Share"
|
||||
class="share-btn primary"
|
||||
>
|
||||
📤 Click to share
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
onclick={() => {
|
||||
copyToClipboard();
|
||||
copySuccess = true;
|
||||
setTimeout(() => {
|
||||
copySuccess = false;
|
||||
}, 3000);
|
||||
}}
|
||||
data-umami-event="Copy to Clipboard"
|
||||
class={`share-btn primary ${copySuccess ? "success" : ""}`}
|
||||
>
|
||||
{copySuccess ? "✅ Copied!" : "📋 Copy to clipboard"}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="chat-window">
|
||||
<p class="bubble">{shareText}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- S++ Bonus Challenge for first try -->
|
||||
{#if guessCount === 1}
|
||||
<ChapterGuess
|
||||
@@ -217,6 +190,38 @@
|
||||
<div class="text-sm opacity-80">Submitting stats...</div>
|
||||
</Container>
|
||||
{/if}
|
||||
|
||||
<div class="share-card" in:fly={{ y: 40, duration: 400, delay: 600 }}>
|
||||
<div class="big-text font-black! text-center">Share your result</div>
|
||||
<div class="share-buttons">
|
||||
{#if hasWebShare}
|
||||
<button
|
||||
onclick={handleShare}
|
||||
data-umami-event="Share"
|
||||
class="share-btn primary"
|
||||
>
|
||||
📤 Click to share
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
onclick={() => {
|
||||
copyToClipboard();
|
||||
copySuccess = true;
|
||||
setTimeout(() => {
|
||||
copySuccess = false;
|
||||
}, 3000);
|
||||
}}
|
||||
data-umami-event="Copy to Clipboard"
|
||||
class={`share-btn primary ${copySuccess ? "success" : ""}`}
|
||||
>
|
||||
{copySuccess ? "✅ Copied!" : "📋 Copy to clipboard"}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="chat-window">
|
||||
<p class="bubble">{shareText}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@ -237,8 +242,9 @@
|
||||
|
||||
/* ── Share card ── */
|
||||
.share-card {
|
||||
background: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 1.25rem;
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
@@ -246,6 +252,17 @@
|
||||
gap: 0.6rem;
|
||||
width: 100%;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.share-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='1'/%3E%3C/svg%3E");
|
||||
opacity: 0.04;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.share-card-label {
|
||||
@@ -260,8 +277,7 @@
|
||||
/* ── Chat window ── */
|
||||
.chat-window {
|
||||
--sent-color: #0b93f6;
|
||||
--bg: white;
|
||||
|
||||
--bg: oklch(93.996% 0.03041 300.209);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0 1.5rem 0;
|
||||
|
||||
@@ -4,13 +4,12 @@ export function generateShareText(params: {
|
||||
guesses: Guess[];
|
||||
correctBookId: string;
|
||||
dailyVerseDate: string;
|
||||
grade: string;
|
||||
chapterCorrect: boolean;
|
||||
isLoggedIn: boolean;
|
||||
userStreak?: number;
|
||||
streak?: number;
|
||||
origin: string;
|
||||
}): string {
|
||||
const { guesses, correctBookId, dailyVerseDate, grade, chapterCorrect, isLoggedIn, userStreak, origin } = params;
|
||||
const { guesses, correctBookId, dailyVerseDate, chapterCorrect, isLoggedIn, streak, origin } = params;
|
||||
|
||||
const emojis = guesses
|
||||
.slice()
|
||||
@@ -35,15 +34,14 @@ export function generateShareText(params: {
|
||||
|
||||
const bookEmoji = isLoggedIn ? "📜" : "📖";
|
||||
|
||||
const guessWord = guesses.length === 1 ? "guess" : "guesses";
|
||||
const streakPart = streak !== undefined && streak > 1 ? `, ${streak} days 🔥` : "";
|
||||
|
||||
const lines = [
|
||||
`${bookEmoji} Bibdle | ${formattedDate} ${bookEmoji}`,
|
||||
`${grade} (${guesses.length} ${guesses.length === 1 ? "guess" : "guesses"})`,
|
||||
`${guesses.length} ${guessWord}${streakPart}`,
|
||||
];
|
||||
|
||||
if (isLoggedIn && userStreak !== undefined) {
|
||||
lines.push(`🔥 ${userStreak} day streak`);
|
||||
}
|
||||
|
||||
lines.push(
|
||||
`${emojis}${guesses.length === 1 && chapterCorrect ? " ⭐" : ""}`,
|
||||
origin,
|
||||
|
||||
7
src/lib/utils/streak.ts
Normal file
7
src/lib/utils/streak.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export async function fetchStreak(anonymousId: string, localDate: string): Promise<number> {
|
||||
const params = new URLSearchParams({ anonymousId, localDate });
|
||||
const res = await fetch(`/api/streak?${params}`);
|
||||
if (!res.ok) return 0;
|
||||
const data = await res.json();
|
||||
return typeof data.streak === 'number' ? data.streak : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user