added first-guess and correct-guess umami event tracking

This commit is contained in:
George Powell
2025-12-27 11:42:15 -05:00
parent 54c7e3cdbb
commit c71170e6b3
3 changed files with 392 additions and 385 deletions

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { fade } from "svelte/transition"; import { fade } from "svelte/transition";
import { getBookById, toOrdinal, getNextGradeMessage } from "$lib/utils/game"; import { getBookById, toOrdinal, getNextGradeMessage } from "$lib/utils/game";
import { onMount } from "svelte";
interface StatsData { interface StatsData {
solveRank: number; solveRank: number;
@@ -33,10 +34,10 @@
// List of congratulations messages with weights // List of congratulations messages with weights
const congratulationsMessages: WeightedMessage[] = [ const congratulationsMessages: WeightedMessage[] = [
{ text: "🎉 Congratulations! 🎉", weight: 1000 }, { text: "Congratulations!", weight: 10 },
{ text: "You got it!", weight: 10 }, { text: "You got it!", weight: 1000 },
{ text: "🎉 Yup 🎉", weight: 5 }, { text: "Yup,", weight: 100 },
{ text: "👍🏻 Very nice! 👍🏻", weight: 1 }, { text: "Very nice!", weight: 1 },
]; ];
// Function to select a random message based on weights // Function to select a random message based on weights
@@ -45,9 +46,9 @@
if (guessCount === 1) { if (guessCount === 1) {
const n = Math.random(); const n = Math.random();
if (n < 0.99) { if (n < 0.99) {
return "🤯 First try! 🤯"; return "🌟 First try! 🌟";
} else { } else {
return " Axios"; return "🗣 Axios! 🗣";
} }
} }
@@ -70,18 +71,25 @@
// Generate the congratulations message // Generate the congratulations message
let congratulationsMessage = $derived(getRandomCongratulationsMessage()); let congratulationsMessage = $derived(getRandomCongratulationsMessage());
onMount(() => {
if (typeof window !== "undefined" && (window as any).umami) {
(window as any).umami.track("guessed-correctly", {
totalGuesses: guessCount,
});
}
});
</script> </script>
<div <div
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 fade-in" 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 fade-in"
> >
<h2 class="text-2xl sm:text-4xl font-black mb-4 drop-shadow-lg"> <!-- <h2 class="text-2xl sm:text-4xl font-black mb-4 drop-shadow-lg">
{congratulationsMessage} {congratulationsMessage}
</h2> </h2> -->
<p class="text-lg sm:text-xl md:text-2xl"> <p class="text-xl sm:text-3xl md:text-4xl">
The verse is from <span class="font-black text-xl sm:text-2xl md:text-3xl" {congratulationsMessage} The verse is from
>{bookName}</span <span class="font-black text-xl sm:text-2xl md:text-3xl">{bookName}</span>.
>
</p> </p>
<p <p
class="text-2xl font-bold mt-6 p-2 mx-2 bg-black/20 rounded-lg inline-block" class="text-2xl font-bold mt-6 p-2 mx-2 bg-black/20 rounded-lg inline-block"

View File

@@ -1,421 +1,421 @@
<script lang="ts"> <script lang="ts">
import { bibleBooks, type BibleBook } from "$lib/types/bible"; import { bibleBooks, type BibleBook } from "$lib/types/bible";
import type { PageProps } from "./$types"; import type { PageProps } from "./$types";
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import VerseDisplay from "$lib/components/VerseDisplay.svelte"; import VerseDisplay from "$lib/components/VerseDisplay.svelte";
import SearchInput from "$lib/components/SearchInput.svelte"; import SearchInput from "$lib/components/SearchInput.svelte";
import GuessesTable from "$lib/components/GuessesTable.svelte"; import GuessesTable from "$lib/components/GuessesTable.svelte";
import CountdownTimer from "$lib/components/CountdownTimer.svelte"; import CountdownTimer from "$lib/components/CountdownTimer.svelte";
import WinScreen from "$lib/components/WinScreen.svelte"; import WinScreen from "$lib/components/WinScreen.svelte";
import Feedback from "$lib/components/Feedback.svelte"; import Feedback from "$lib/components/Feedback.svelte";
import TitleAnimation from "$lib/components/TitleAnimation.svelte"; import TitleAnimation from "$lib/components/TitleAnimation.svelte";
import { getGrade } from "$lib/utils/game"; import { getGrade } from "$lib/utils/game";
interface Guess { interface Guess {
book: BibleBook; book: BibleBook;
testamentMatch: boolean; testamentMatch: boolean;
sectionMatch: boolean; sectionMatch: boolean;
adjacent: boolean; adjacent: boolean;
} }
let { data }: PageProps = $props(); let { data }: PageProps = $props();
let dailyVerse = $derived(data.dailyVerse); let dailyVerse = $derived(data.dailyVerse);
let correctBookId = $derived(data.correctBookId); let correctBookId = $derived(data.correctBookId);
let guesses = $state<Guess[]>([]); let guesses = $state<Guess[]>([]);
let searchQuery = $state(""); let searchQuery = $state("");
let copied = $state(false); let copied = $state(false);
let isDev = $state(false); let isDev = $state(false);
let anonymousId = $state(""); let anonymousId = $state("");
let statsSubmitted = $state(false); let statsSubmitted = $state(false);
let statsData = $state<{ let statsData = $state<{
solveRank: number; solveRank: number;
guessRank: number; guessRank: number;
totalSolves: number; totalSolves: number;
averageGuesses: number; averageGuesses: number;
} | null>(null); } | null>(null);
let guessedIds = $derived(new Set(guesses.map((g) => g.book.id))); let guessedIds = $derived(new Set(guesses.map((g) => g.book.id)));
const currentDate = $derived( const currentDate = $derived(
new Date().toLocaleDateString("en-US", { new Date().toLocaleDateString("en-US", {
weekday: "long", weekday: "long",
year: "numeric", year: "numeric",
month: "long", month: "long",
day: "numeric", day: "numeric",
}), })
); );
let isWon = $derived(guesses.some((g) => g.book.id === correctBookId)); let isWon = $derived(guesses.some((g) => g.book.id === correctBookId));
let grade = $derived( let grade = $derived(
isWon isWon
? getGrade( ? getGrade(guesses.length, getBookById(correctBookId)?.popularity ?? 0)
guesses.length, : ""
getBookById(correctBookId)?.popularity ?? 0, );
)
: "",
);
function getBookById(id: string): BibleBook | undefined { function getBookById(id: string): BibleBook | undefined {
return bibleBooks.find((b) => b.id === id); return bibleBooks.find((b) => b.id === id);
} }
function isAdjacent(id1: string, id2: string): boolean { function isAdjacent(id1: string, id2: string): boolean {
const b1 = getBookById(id1); const b1 = getBookById(id1);
const b2 = getBookById(id2); const b2 = getBookById(id2);
return !!(b1 && b2 && Math.abs(b1.order - b2.order) === 1); return !!(b1 && b2 && Math.abs(b1.order - b2.order) === 1);
} }
function submitGuess(bookId: string) { function submitGuess(bookId: string) {
if (guesses.some((g) => g.book.id === bookId)) return; if (guesses.some((g) => g.book.id === bookId)) return;
const book = getBookById(bookId); const book = getBookById(bookId);
if (!book) return; if (!book) return;
const correctBook = getBookById(correctBookId); const correctBook = getBookById(correctBookId);
if (!correctBook) return; if (!correctBook) return;
const testamentMatch = book.testament === correctBook.testament; const testamentMatch = book.testament === correctBook.testament;
const sectionMatch = book.section === correctBook.section; const sectionMatch = book.section === correctBook.section;
const adjacent = isAdjacent(book.id, correctBookId); const adjacent = isAdjacent(book.id, correctBookId);
console.log( console.log(
`Guess: ${book.name} (order ${book.order}), Correct: ${correctBook.name} (order ${correctBook.order}), Adjacent: ${adjacent}`, `Guess: ${book.name} (order ${book.order}), Correct: ${correctBook.name} (order ${correctBook.order}), Adjacent: ${adjacent}`
); );
guesses = [ if (guesses.length === 0 && browser && (window as any).umami) {
{ (window as any).umami.track("first-guess");
book, }
testamentMatch,
sectionMatch,
adjacent,
},
...guesses,
];
searchQuery = ""; guesses = [
} {
book,
testamentMatch,
sectionMatch,
adjacent,
},
...guesses,
];
function generateUUID(): string { searchQuery = "";
// Try native randomUUID if available }
if (typeof window.crypto.randomUUID === "function") {
return window.crypto.randomUUID();
}
// Fallback UUID v4 generator for older browsers function generateUUID(): string {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { // Try native randomUUID if available
const r = if (typeof window.crypto.randomUUID === "function") {
window.crypto.getRandomValues(new Uint8Array(1))[0] % 16 | 0; return window.crypto.randomUUID();
const v = c === "x" ? r : (r & 0x3) | 0x8; }
return v.toString(16);
});
}
function getOrCreateAnonymousId(): string { // Fallback UUID v4 generator for older browsers
if (!browser) return ""; return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
const key = "bibdle-anonymous-id"; const r = window.crypto.getRandomValues(new Uint8Array(1))[0] % 16 | 0;
let id = localStorage.getItem(key); const v = c === "x" ? r : (r & 0x3) | 0x8;
if (!id) { return v.toString(16);
id = generateUUID(); });
localStorage.setItem(key, id); }
}
return id;
}
// Initialize anonymous ID function getOrCreateAnonymousId(): string {
$effect(() => { if (!browser) return "";
if (!browser) return; const key = "bibdle-anonymous-id";
anonymousId = getOrCreateAnonymousId(); let id = localStorage.getItem(key);
const statsKey = `bibdle-stats-submitted-${dailyVerse.date}`; if (!id) {
statsSubmitted = localStorage.getItem(statsKey) === "true"; id = generateUUID();
}); localStorage.setItem(key, id);
}
return id;
}
$effect(() => { // Initialize anonymous ID
if (!browser) return; $effect(() => {
isDev = window.location.host === "localhost:5173"; if (!browser) return;
}); anonymousId = getOrCreateAnonymousId();
const statsKey = `bibdle-stats-submitted-${dailyVerse.date}`;
statsSubmitted = localStorage.getItem(statsKey) === "true";
});
// Load saved guesses $effect(() => {
$effect(() => { if (!browser) return;
if (!browser) return; isDev = window.location.host === "localhost:5173";
});
const key = `bibdle-guesses-${dailyVerse.date}`; // Load saved guesses
const saved = localStorage.getItem(key); $effect(() => {
if (saved) { if (!browser) return;
let savedIds: string[] = JSON.parse(saved);
savedIds = Array.from(new Set(savedIds));
guesses = savedIds.map((bookId: string) => {
const book = getBookById(bookId)!;
const correctBook = getBookById(correctBookId)!;
const testamentMatch = book.testament === correctBook.testament;
const sectionMatch = book.section === correctBook.section;
const adjacent = isAdjacent(bookId, correctBookId);
return {
book,
testamentMatch,
sectionMatch,
adjacent,
};
});
}
});
$effect(() => { const key = `bibdle-guesses-${dailyVerse.date}`;
if (!browser) return; const saved = localStorage.getItem(key);
localStorage.setItem( if (saved) {
`bibdle-guesses-${dailyVerse.date}`, let savedIds: string[] = JSON.parse(saved);
JSON.stringify(guesses.map((g) => g.book.id)), savedIds = Array.from(new Set(savedIds));
); guesses = savedIds.map((bookId: string) => {
}); const book = getBookById(bookId)!;
const correctBook = getBookById(correctBookId)!;
const testamentMatch = book.testament === correctBook.testament;
const sectionMatch = book.section === correctBook.section;
const adjacent = isAdjacent(bookId, correctBookId);
return {
book,
testamentMatch,
sectionMatch,
adjacent,
};
});
}
});
// Auto-submit stats when user wins $effect(() => {
$effect(() => { if (!browser) return;
console.log("Stats effect triggered:", { localStorage.setItem(
browser, `bibdle-guesses-${dailyVerse.date}`,
isWon, JSON.stringify(guesses.map((g) => g.book.id))
anonymousId, );
statsSubmitted, });
statsData,
});
if (!browser || !isWon || !anonymousId) { // Auto-submit stats when user wins
console.log("Basic conditions not met"); $effect(() => {
return; console.log("Stats effect triggered:", {
} browser,
isWon,
anonymousId,
statsSubmitted,
statsData,
});
if (statsSubmitted && !statsData) { if (!browser || !isWon || !anonymousId) {
console.log("Fetching existing stats..."); console.log("Basic conditions not met");
return;
}
(async () => { if (statsSubmitted && !statsData) {
try { console.log("Fetching existing stats...");
const response = await fetch(
`/api/submit-completion?anonymousId=${anonymousId}&date=${dailyVerse.date}`,
);
const result = await response.json();
console.log("Stats response:", result);
if (result.success && result.stats) { (async () => {
console.log("Setting stats data:", result.stats); try {
statsData = result.stats; const response = await fetch(
localStorage.setItem( `/api/submit-completion?anonymousId=${anonymousId}&date=${dailyVerse.date}`
`bibdle-stats-submitted-${dailyVerse.date}`, );
"true", const result = await response.json();
); console.log("Stats response:", result);
} else if (result.error) {
console.error("Server error:", result.error);
} else {
console.error("Unexpected response format:", result);
}
} catch (err) {
console.error("Stats fetch failed:", err);
}
})();
return; if (result.success && result.stats) {
} console.log("Setting stats data:", result.stats);
statsData = result.stats;
localStorage.setItem(
`bibdle-stats-submitted-${dailyVerse.date}`,
"true"
);
} else if (result.error) {
console.error("Server error:", result.error);
} else {
console.error("Unexpected response format:", result);
}
} catch (err) {
console.error("Stats fetch failed:", err);
}
})();
console.log("Submitting stats..."); return;
}
async function submitStats() { console.log("Submitting stats...");
try {
const payload = {
anonymousId,
date: dailyVerse.date,
guessCount: guesses.length,
};
console.log("Sending POST request with:", payload); async function submitStats() {
try {
const payload = {
anonymousId,
date: dailyVerse.date,
guessCount: guesses.length,
};
const response = await fetch("/api/submit-completion", { console.log("Sending POST request with:", payload);
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const result = await response.json(); const response = await fetch("/api/submit-completion", {
console.log("Stats response:", result); method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (result.success && result.stats) { const result = await response.json();
console.log("Setting stats data:", result.stats); console.log("Stats response:", result);
statsData = result.stats;
statsSubmitted = true;
localStorage.setItem(
`bibdle-stats-submitted-${dailyVerse.date}`,
"true",
);
} else if (result.error) {
console.error("Server error:", result.error);
} else {
console.error("Unexpected response format:", result);
}
} catch (err) {
console.error("Stats submission failed:", err);
}
}
submitStats(); if (result.success && result.stats) {
}); console.log("Setting stats data:", result.stats);
statsData = result.stats;
statsSubmitted = true;
localStorage.setItem(
`bibdle-stats-submitted-${dailyVerse.date}`,
"true"
);
} else if (result.error) {
console.error("Server error:", result.error);
} else {
console.error("Unexpected response format:", result);
}
} catch (err) {
console.error("Stats submission failed:", err);
}
}
function generateShareText(): string { submitStats();
const emojis = guesses });
.slice()
.reverse()
.map((guess) => {
if (guess.book.id === correctBookId) return "✅";
if (guess.adjacent) return "‼️";
if (guess.sectionMatch) return "🟩";
if (guess.testamentMatch) return "🟧";
return "🟥";
})
.join("");
const dateFormatter = new Intl.DateTimeFormat("en-US", { function generateShareText(): string {
month: "short", const emojis = guesses
day: "numeric", .slice()
year: "numeric", .reverse()
}); .map((guess) => {
const formattedDate = dateFormatter.format( if (guess.book.id === correctBookId) return "✅";
new Date(`${dailyVerse.date}T00:00:00`), if (guess.adjacent) return "‼️";
); if (guess.sectionMatch) return "🟩";
const siteUrl = window.location.origin; if (guess.testamentMatch) return "🟧";
return [ return "🟥";
`📖 Bibdle | ${formattedDate} 📖`, })
`${grade} (${guesses.length} ${guesses.length == 1 ? "guess" : "guesses"})`, .join("");
`${emojis}`,
siteUrl,
].join("\n");
}
async function share() { const dateFormatter = new Intl.DateTimeFormat("en-US", {
if (!browser) return; month: "short",
day: "numeric",
year: "numeric",
});
const formattedDate = dateFormatter.format(
new Date(`${dailyVerse.date}T00:00:00`)
);
const siteUrl = window.location.origin;
return [
`📖 Bibdle | ${formattedDate} 📖`,
`${grade} (${guesses.length} ${guesses.length == 1 ? "guess" : "guesses"})`,
`${emojis}`,
siteUrl,
].join("\n");
}
const shareText = generateShareText(); async function share() {
if (!browser) return;
try { const shareText = generateShareText();
if ("share" in navigator) {
await (navigator as any).share({ text: shareText });
} else {
await (navigator as any).clipboard.writeText(shareText);
}
} catch (err) {
console.error("Share failed:", err);
throw err;
}
}
async function copyToClipboard() { try {
if (!browser) return; if ("share" in navigator) {
await (navigator as any).share({ text: shareText });
} else {
await (navigator as any).clipboard.writeText(shareText);
}
} catch (err) {
console.error("Share failed:", err);
throw err;
}
}
const shareText = generateShareText(); async function copyToClipboard() {
if (!browser) return;
try { const shareText = generateShareText();
await (navigator as any).clipboard.writeText(shareText);
copied = true;
setTimeout(() => {
copied = false;
}, 5000);
} catch (err) {
console.error("Copy to clipboard failed:", err);
throw err;
}
}
function handleShare() { try {
if (copied || !browser) return; await (navigator as any).clipboard.writeText(shareText);
const useClipboard = !("share" in navigator); copied = true;
if (useClipboard) { setTimeout(() => {
copied = true; copied = false;
} }, 5000);
share() } catch (err) {
.then(() => { console.error("Copy to clipboard failed:", err);
if (useClipboard) { throw err;
setTimeout(() => { }
copied = false; }
}, 5000);
}
})
.catch(() => {
if (useClipboard) {
copied = false;
}
});
}
function clearLocalStorage() { function handleShare() {
if (!browser) return; if (copied || !browser) return;
// Clear all bibdle-related localStorage items const useClipboard = !("share" in navigator);
const keysToRemove: string[] = []; if (useClipboard) {
for (let i = 0; i < localStorage.length; i++) { copied = true;
const key = localStorage.key(i); }
if (key && key.startsWith("bibdle-")) { share()
keysToRemove.push(key); .then(() => {
} if (useClipboard) {
} setTimeout(() => {
keysToRemove.forEach((key) => localStorage.removeItem(key)); copied = false;
// Reload the page to reset state }, 5000);
window.location.reload(); }
} })
.catch(() => {
if (useClipboard) {
copied = false;
}
});
}
function clearLocalStorage() {
if (!browser) return;
// Clear all bibdle-related localStorage items
const keysToRemove: string[] = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith("bibdle-")) {
keysToRemove.push(key);
}
}
keysToRemove.forEach((key) => localStorage.removeItem(key));
// Reload the page to reset state
window.location.reload();
}
</script> </script>
<svelte:head> <svelte:head>
<title>Bibdle &mdash; A daily bible game{isDev ? " (dev)" : ""}</title> <title>Bibdle &mdash; A daily bible game{isDev ? " (dev)" : ""}</title>
<meta <meta
name="description" name="description"
content="Guess which book of the Bible a verse comes from." content="Guess which book of the Bible a verse comes from."
/> />
</svelte:head> </svelte:head>
<div class="min-h-dvh md:bg-linear-to-br md:from-blue-50 md:to-indigo-200 py-8"> <div class="min-h-dvh md:bg-linear-to-br md:from-blue-50 md:to-indigo-200 py-8">
<div class="w-full max-w-3xl mx-auto px-4"> <div class="w-full max-w-3xl mx-auto px-4">
<h1 <h1
class="text-3xl md:text-4xl font-bold text-center uppercase text-gray-600 drop-shadow-2xl tracking-widest p-4" class="text-3xl md:text-4xl font-bold text-center uppercase text-gray-600 drop-shadow-2xl tracking-widest p-4"
> >
<TitleAnimation /> <TitleAnimation />
<div class="font-normal"></div> <div class="font-normal"></div>
</h1> </h1>
<div class="text-center mb-8"> <div class="text-center mb-8">
<span class="big-text" <span class="big-text"
>{isDev ? "Dev Edition | " : ""}{currentDate}</span >{isDev ? "Dev Edition | " : ""}{currentDate}</span
> >
</div> </div>
<VerseDisplay {data} {isWon} /> <VerseDisplay {data} {isWon} />
{#if !isWon} {#if !isWon}
<SearchInput bind:searchQuery {guessedIds} {submitGuess} /> <SearchInput bind:searchQuery {guessedIds} {submitGuess} />
{:else} {:else}
<WinScreen <WinScreen
{grade} {grade}
{statsData} {statsData}
{correctBookId} {correctBookId}
{handleShare} {handleShare}
{copyToClipboard} {copyToClipboard}
bind:copied bind:copied
{statsSubmitted} {statsSubmitted}
guessCount={guesses.length} guessCount={guesses.length}
/> />
<CountdownTimer /> <CountdownTimer />
{/if} {/if}
<GuessesTable {guesses} {correctBookId} /> <GuessesTable {guesses} {correctBookId} />
{#if isWon} {#if isWon}
<Feedback /> <Feedback />
{/if} {/if}
{#if isDev} {#if isDev}
<button <button
onclick={clearLocalStorage} onclick={clearLocalStorage}
class="mt-4 px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg text-sm font-bold transition-colors" class="mt-4 px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg text-sm font-bold transition-colors"
> >
Clear LocalStorage Clear LocalStorage
</button> </button>
{/if} {/if}
</div> </div>
</div> </div>

29
todo.md
View File

@@ -1,43 +1,38 @@
# in progress # in progress
- root menu: classic / imposter mode / impossible mode (complete today's classic and imposter modes to unlock) - root menu: classic / imposter mode / impossible mode (complete today's classic and imposter modes to unlock)
# todo # todo
- impossible mode (1904 greek bible) three guesses only. - impossible mode (1904 greek bible) three guesses only.
- share both classic and impossible mode with both buttons
- share both classic and impossible mode with both buttons
- add imposter mode - add imposter mode
- instructions - instructions
- classic mode: identify what book the verse is from (e.g. Genesis, John, Revelations...) in as few guesses as possible.
- imposter mode: out of four options, identify the verse that is not in the Bible - classic mode: identify what book the verse is from (e.g. Genesis, John, Revelations...) in as few guesses as possible.
- impossible mode: identify which book of the bible the verse is from in less than three guesses. - imposter mode: out of four options, identify the verse that is not in the Bible
- impossible mode: identify which book of the bible the verse is from in less than three guesses.
- add login + saved stats + streak etc. - add login + saved stats + streak etc.
- add deuterocanonical books - add deuterocanonical books
<!-- Login features --> <!-- Login features -->
- Practice mode: Unlimited verses - Practice mode: Unlimited verses
- Create public or private leaderboards - Create public or private leaderboards
- Passport book with badges: - Passport book with awards:
- Guess each Gospel first try - Guess each Gospel first try
- "Guessed all Gospels", "Perfect week", "Old Testament expert" - "Guessed all Gospels", "Perfect week", "Old Testament expert"
- Theologian: Guess each book first try - Theologian: Guess each book first try
- If chapter is 6 and verse 7, earn award "Six seven"
- difficult mode (guess old or new testament, first try _only_) (???) - difficult mode (guess old or new testament, first try _only_) (???)
# places to send
- linkedin post
- ocf discord server ✅
- nick makiej ✅
# About this game # About this game
As a young camper at the Metropolis of Boston Camp, I remember His Eminence Metropolitan Methodios would visit every Sunday. He was often surrounded by important people for his entire time there, so I never gathered the courage to introduce myself, but his homilies during Liturgy always stood out to me. In some ways, they differed year after year, but a majority of his message remained strikingly familiar. "Take ten minutes to read the Bible every day," he asked. "Just ten minutes. Go somewhere quiet, turn off the TV (then iPod, then cell phone), and read in peace and quiet." As a young camper at the Metropolis of Boston Camp, I remember His Eminence Metropolitan Methodios would visit every Sunday. He was often surrounded by important people for his entire time there, so I never gathered the courage to introduce myself, but his homilies during Liturgy always stood out to me. In some ways, they differed year after year, but a majority of his message remained strikingly familiar. "Take ten minutes to read the Bible every day," he asked. "Just ten minutes. Go somewhere quiet, turn off the TV (then iPod, then cell phone), and read in peace and quiet."
@@ -52,6 +47,10 @@ I created Bibdle from a combination of two things. The first is my lifelong desi
# done # done
## december 27th
- add event log to submitting first-guess or correct-guess to umami (to make bounce rate more accurate)
## december 26th ## december 26th
- created embeddings for every bible verse (verse similarity finder) - created embeddings for every bible verse (verse similarity finder)