diff --git a/scripts/test-share-text.ts b/scripts/test-share-text.ts new file mode 100644 index 0000000..ef6709c --- /dev/null +++ b/scripts/test-share-text.ts @@ -0,0 +1,41 @@ +import { fetchRandomVerse } from '../src/lib/server/bible-api'; +import { generateShareText } from '../src/lib/utils/share'; +import { bibleBooks } from '../src/lib/types/bible'; + +const NUM_VERSES = 10; + +for (let i = 0; i < NUM_VERSES; i++) { + const verse = await fetchRandomVerse(); + + // Build a fake "solved in N guesses" scenario with some wrong guesses first + const correctBook = bibleBooks.find((b) => b.id === verse.bookId)!; + const wrongBook = bibleBooks.find((b) => b.id !== verse.bookId)!; + const guessCount = Math.floor(Math.random() * 5) + 1; + const guesses = [ + ...Array(guessCount - 1).fill(null).map(() => ({ + book: wrongBook, + testamentMatch: wrongBook.testament === correctBook.testament, + sectionMatch: wrongBook.section === correctBook.section, + adjacent: Math.abs(wrongBook.order - correctBook.order) === 1, + })), + { book: correctBook, testamentMatch: true, sectionMatch: true, adjacent: false }, + ]; + + const fakeStreak = Math.random() > 0.5 ? Math.floor(Math.random() * 14) + 2 : 0; + + const shareText = generateShareText({ + guesses, + correctBookId: verse.bookId, + dailyVerseDate: new Date().toISOString().slice(0, 10), + chapterCorrect: guessCount === 1 && Math.random() > 0.5, + isLoggedIn: Math.random() > 0.5, + streak: fakeStreak > 0 ? fakeStreak : undefined, + origin: 'https://bibdle.com', + verseText: verse.verseText, + }); + + console.log(`\n── Verse ${i + 1}: ${verse.reference} ──`); + console.log(`RAW: ${verse.verseText}`); + console.log('─'.repeat(40)); + console.log(shareText); +} diff --git a/scripts/test-verse-snippets.ts b/scripts/test-verse-snippets.ts new file mode 100644 index 0000000..eb055b5 --- /dev/null +++ b/scripts/test-verse-snippets.ts @@ -0,0 +1,10 @@ +import { fetchRandomVerse } from '../src/lib/server/bible-api'; +import { getVerseSnippet } from '../src/lib/utils/share'; + +const NUM_VERSES = 10; + +for (let i = 0; i < NUM_VERSES; i++) { + const verse = await fetchRandomVerse(); + + console.log(getVerseSnippet(verse.verseText)); +} diff --git a/src/lib/components/CountdownTimer.svelte b/src/lib/components/CountdownTimer.svelte index 66dbc64..ec79e44 100644 --- a/src/lib/components/CountdownTimer.svelte +++ b/src/lib/components/CountdownTimer.svelte @@ -50,9 +50,9 @@ }); -
+
{#if newVerseReady}

+ let { + streak, + streakPercentile = null, + }: { + streak: number; + streakPercentile?: number | null; + } = $props(); + + +

+

+ {streak} +

+

+ day{streak === 1 ? "" : "s"}
in a row +

+ {#if streakPercentile !== null && streakPercentile <= 50} +

+ Top {streakPercentile}% +

+ {/if} +
diff --git a/src/lib/components/VerseDisplay.svelte b/src/lib/components/VerseDisplay.svelte index b73b569..8cd3439 100644 --- a/src/lib/components/VerseDisplay.svelte +++ b/src/lib/components/VerseDisplay.svelte @@ -80,19 +80,6 @@ > {displayReference}

-
- -
{/if}
diff --git a/src/lib/components/WinScreen.svelte b/src/lib/components/WinScreen.svelte index aaa9e63..b669e63 100644 --- a/src/lib/components/WinScreen.svelte +++ b/src/lib/components/WinScreen.svelte @@ -3,6 +3,7 @@ import { getBookById, toOrdinal } from "$lib/utils/game"; import Container from "./Container.svelte"; import CountdownTimer from "./CountdownTimer.svelte"; + import StreakCounter from "./StreakCounter.svelte"; import ChapterGuess from "./ChapterGuess.svelte"; interface StatsData { @@ -53,6 +54,7 @@ ); let copySuccess = $state(false); let bubbleCopied = $state(false); + let copyTracked = $state(false); // List of congratulations messages with weights const congratulationsMessages: WeightedMessage[] = [ @@ -97,37 +99,25 @@
-

- {congratulationsMessage} The verse is from
- {bookName}. -

-

- You guessed correctly after {guessCount} - {guessCount === 1 ? "guess" : "guesses"}. -

- - {#if streak > 1} -
-

- 🔥 {streak} days in a row! +

+

+ {congratulationsMessage} The verse is from
+ {bookName}. +

+

+ You guessed correctly after {guessCount} + {guessCount === 1 ? "guess" : "guesses"}. +

+ {#if streak >= 7} +

+ Thank you for making Bibdle part of your daily routine!

- {#if streak >= 7} -

- Thank you for making Bibdle part of your daily routine! -

- {/if} - {#if streakPercentile !== null} -

- {streakPercentile <= 50 - ? "Only " - : ""}{streakPercentile}% of players have a streak of {streak} - or greater. -

- {/if} -
- {/if} + {/if} +
@@ -139,7 +129,16 @@ /> {/if} - +
+
+ +
+ {#if streak > 0} +
+ +
+ {/if} +
{#if statsData} @@ -225,7 +224,12 @@ (window as any).rybbit?.event("Share"); handleShare(); } else { - (window as any).rybbit?.event("Copy to Clipboard"); + if (!copyTracked) { + (window as any).rybbit?.event( + "Copy to Clipboard", + ); + copyTracked = true; + } copyToClipboard(); copySuccess = true; setTimeout(() => { @@ -251,7 +255,10 @@ aria-label="Copy to clipboard" data-umami-event="Copy to Clipboard" onclick={() => { - (window as any).rybbit?.event("Copy to Clipboard"); + if (!copyTracked) { + (window as any).rybbit?.event("Copy to Clipboard"); + copyTracked = true; + } copyToClipboard(); bubbleCopied = true; setTimeout(() => { diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index c628083..0e705a5 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -1,5 +1,4 @@ import { integer, sqliteTable, text, index, unique } from 'drizzle-orm/sqlite-core'; -import { sql } from 'drizzle-orm'; export const user = sqliteTable('user', { id: text('id').primaryKey(), diff --git a/src/lib/utils/share.ts b/src/lib/utils/share.ts index 786ff19..d319915 100644 --- a/src/lib/utils/share.ts +++ b/src/lib/utils/share.ts @@ -1,5 +1,43 @@ +import type { SHA512_256 } from '@oslojs/crypto/sha2'; import type { Guess } from './game'; +export function getVerseSnippet(verseText: string): string { + const words = verseText.trim().split(/\s+/); + const slice = words.slice(0, 25); + const text = slice.join(' '); + + // Returns character index immediately after the Nth word (1-indexed) + function posAfterWord(n: number): number { + let pos = 0; + for (let w = 0; w < Math.min(n, slice.length); w++) { + if (w > 0) pos++; // space between words + pos += slice[w].length; + } + return pos; + } + + const start = posAfterWord(10); + const end = posAfterWord(25); + + // Find first punctuation mark between words 10 and 25 + const range = text.substring(start, end); + const match = range.match(/[,;:.!?—–-]/); + + function withClosedQuotes(snippet: string): string { + const opens = (snippet.match(/\u201C/g) ?? []).length; + const closes = (snippet.match(/\u201D/g) ?? []).length; + const closeQuote = opens > closes ? '\u201D' : ''; + return `\u201C${snippet}...${closeQuote}\u201D`; + } + + if (match && match.index !== undefined) { + const cutPos = start + match.index; + return withClosedQuotes(text.substring(0, cutPos).trimEnd()); + } + + return withClosedQuotes(text); +} + export function generateShareText(params: { guesses: Guess[]; correctBookId: string; @@ -8,8 +46,9 @@ export function generateShareText(params: { isLoggedIn: boolean; streak?: number; origin: string; + verseText: string; }): string { - const { guesses, correctBookId, dailyVerseDate, chapterCorrect, isLoggedIn, streak, origin } = params; + const { guesses, correctBookId, dailyVerseDate, chapterCorrect, isLoggedIn, streak, origin, verseText } = params; const emojis = guesses .slice() @@ -35,17 +74,15 @@ export function generateShareText(params: { const bookEmoji = isLoggedIn ? "📜" : "📖"; const guessWord = guesses.length === 1 ? "guess" : "guesses"; - const streakPart = streak !== undefined && streak > 1 ? `, ${streak} days 🔥` : ""; + const streakPart = streak !== undefined && streak > 1 ? ` (${streak} days🔥)` : ""; + const chapterStar = guesses.length === 1 && chapterCorrect ? " ⭐" : ""; const lines = [ `${bookEmoji} Bibdle | ${formattedDate} ${bookEmoji}`, - `${guesses.length} ${guessWord}${streakPart}`, - ]; - - lines.push( - `${emojis}${guesses.length === 1 && chapterCorrect ? " ⭐" : ""}`, + getVerseSnippet(verseText), + `${emojis}${chapterStar} ${guesses.length} ${guessWord}${streakPart}`, origin, - ); + ]; return lines.join("\n"); } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 3ee9cb9..0579921 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -240,6 +240,7 @@ isLoggedIn: !!user, streak, origin: window.location.origin, + verseText: dailyVerse.verseText, }); }