From 677cae0125bc62811776e2380fe7f7f8b724c54e Mon Sep 17 00:00:00 2001 From: Zakaria Date: Thu, 14 May 2026 17:31:00 -0400 Subject: [PATCH] Fix: reload login state from superbase --- .DS_Store | Bin 6148 -> 10244 bytes CSS/styles.css | 79 +++++++++++++++------- fancy-wordle-modern.html | 1 + index.html | 1 + script.js | 138 +++++++++++++++++++++++++++++---------- 5 files changed, 160 insertions(+), 59 deletions(-) diff --git a/.DS_Store b/.DS_Store index c12028fb2a58293953af605522cb231ba7394332..a3236e6d75c946cf258221c59a84b4ac033c864f 100644 GIT binary patch literal 10244 zcmeHMYitx%6h3EK=nM-ot>r0`-CB8AO4(vtKoqy#)>xpFbPIhzsk1vnJ2IUqJG0x? ziq>BmP>g>xzW)&uQA~J*C?@{#fj=}Mgn-e+sNt81(ZmOusOQd|ZMxg`hY1p5<|g-^ zd+t5w-aX$v=gz%%0RWpaS}i~X07S}+%JZq1A+dgzrzOE}SwIv?AM8}pw3Eci(`18& z0D%C30D%C30D%C3TLS_5X0s%QI1S1GfdGL3fk_0|{t%(eXw0V*oO)Xa72XwqXfZW= ziR#|rpJ;?N5RLhCf>Uatgj|&1{s$9RN zqyL~eWZB2KspxMP!0eowo+HM$D4R%Irfm&S3zJ?58W@lQmr5HfP$3OtPy>&E!}tP} zPLf7GN(KzUK)w_z1yb{9Y>Vd6VU$K5q`8K`EYLYxpd&#Cv_K;?LmSZ=($EStu20i& z$c{A4HB2*j7UXLP%;u@TUL+ecB&|Y{vIF{*)joIAS9|IB_{KU~>!`QV4fRx~^{y*e z=_6zVrCC>+;W*7M)5*rU7Z-Ks?E2F8Vo{nG3N28&dwTco>+6^0jH7qxrs^75y;*g2 zr(a&^kW;s&Y-?9q&yq8DrED`ZWKmCPN;Oj%64o0{GhsZ+rBi9!GdE11BRZKN{bb#(^7}@OJz%!Ex%)h zq7+K!M(NHZjYAp39g{0Xkp~5ptVz2~HL07tX>(|cC~ud#vfN_cxHS|LOR-w& zPT504WRJs}<4TJte=6>0bMH_cw|!W*a=$9Bw2ES{GpMHZ_7U1`avE)-=&dBRGnE$2 zaP71QNPC5%^su2CHBHyZ?&V9Dl_{Od9#OtXI&uM zSMW7_9gpKld>2pQY5V{`#!v79euh`^Tl@~c#~<-`ypDee^Myr1ScnJ}LY1&qSSQp7 zjY5;KUFa10grsngTOs6j*!`1Mc@^O?A~VIE|Md_~%^Y{(hg!F7-_dr<#@*H1Sft%v zzG(51$jYkqb(`*Qnsi3)zB!Mb8VR2B=Y@>t{GN9ddMyvhPcq+7LVbJ_!7DOtK|5{@lAXSPvF~l7SG{( z_&%QZ0^ke0f?wmc0+-&I@6!0&OfFq;e30o=%Z>~K^l^8gi;fIw`p`SG9w68;>%^2} zR{oR$7a$NI5FijB5Fl{F5h&s#M%ep*%k2OE-|%^Z0RsdA1a2J!u(&zV+(f@8ca^Xi7WhU!ie!Y6kZ=2xMQzscuo#;*aGoVa~ VE5ZFgxc~c4a{uPt?f-KB{|EhRSCaq$ delta 155 zcmZn(XfcprU|?W$DortDU=RQ@Ie-{Mvv5r;6q~50$jH4hU^g=(_hudeO(w=|lRX3q zRmG~SEp!wNYIPK<4b6=VbQBB?EKF)^IXOg?^{s>AvvYFu@;fG<6Ow1_+-xZ+&A6DI pgF}!RXebZ}a03Zfkog-6zcWwfSMdbd#=r!z8RQs-&G9^Qm;q}!9i{*P diff --git a/CSS/styles.css b/CSS/styles.css index 13775d2..4df5f05 100644 --- a/CSS/styles.css +++ b/CSS/styles.css @@ -114,8 +114,7 @@ a { .brand small, .deck, .status-row, -.alert, -.definition-alert { +.alert { color: var(--muted); } @@ -360,29 +359,6 @@ h2 { transform: translateY(-6px); } -.definition-alert { - pointer-events: auto; - width: min(440px, calc(100vw - 32px)); - text-align: start; -} - -.definition-alert strong { - display: block; - margin-bottom: 6px; - color: var(--fg); - font-family: var(--font-display); - font-size: 1.2rem; - font-weight: 500; -} - -.definition-alert::after { - display: block; - margin-top: 10px; - color: var(--muted); - content: "Click to dismiss"; - font-size: 0.8rem; -} - .stats-modal { width: min(520px, calc(100vw - 32px)); padding: 0; @@ -462,6 +438,59 @@ h2 { line-height: 1.5; } +.leaderboard-result { + display: grid; + gap: 12px; + padding: 14px; + border: 1px solid color-mix(in oklch, var(--accent), var(--border) 45%); + border-radius: 16px; + background: color-mix(in oklch, var(--accent), var(--surface) 88%); + color: var(--fg); + line-height: 1.55; +} + +.leaderboard-result[hidden] { + display: none; +} + +.leaderboard-result-summary { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + border-bottom: 1px solid color-mix(in oklch, var(--accent), var(--border) 62%); + padding-bottom: 10px; +} + +.leaderboard-result strong { + display: block; + font-family: var(--font-display); + font-size: 1.35rem; + font-weight: 500; +} + +.leaderboard-result span { + color: var(--muted); +} + +.leaderboard-result-summary span { + flex: 0 0 auto; + font-size: 0.9rem; + font-weight: 900; +} + +.leaderboard-definition { + display: grid; + gap: 6px; +} + +.leaderboard-result em { + display: block; + margin-top: 10px; + color: var(--muted); + font-style: normal; +} + .definition-panel { display: grid; gap: 10px; diff --git a/fancy-wordle-modern.html b/fancy-wordle-modern.html index b957108..7e64855 100644 --- a/fancy-wordle-modern.html +++ b/fancy-wordle-modern.html @@ -97,6 +97,7 @@ +

Next word unlocks soon.

diff --git a/index.html b/index.html index b957108..7e64855 100644 --- a/index.html +++ b/index.html @@ -97,6 +97,7 @@ +

Next word unlocks soon.

diff --git a/script.js b/script.js index ac6e3a1..7c9ef4c 100644 --- a/script.js +++ b/script.js @@ -25,6 +25,7 @@ const DANCE_ANIMATION_DURATION = 500 const STATS_KEY = "fancy-wordle-stats-v2" const LOCAL_ROUND_KEY = "fancy-wordle-hourly-round-v1" const PLAY_INTERVAL_MS = 60 * 60 * 1000 +const GUESS_TIMEOUT_MS = 10000 const KEY_ROWS = ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"] const FALLBACK_TARGET_WORDS = [ "about", @@ -74,6 +75,7 @@ const statsButton = document.getElementById("Stats-button") const leaderboardButton = document.getElementById("leaderboard-button") const leaderboardModal = document.getElementById("leaderboard-modal") const leaderboardList = document.getElementById("leaderboard-list") +const leaderboardResult = document.getElementById("leaderboard-result") const leaderboardCountdown = document.getElementById("leaderboard-countdown") const leaderboardTabs = document.querySelectorAll("[data-leaderboard-scope]") const authButton = document.getElementById("auth-button") @@ -210,9 +212,9 @@ async function initializeAuth() { authProfile = session ? await getAuthProfile() : null updateAuthControls() - client.auth.onAuthStateChange(async (event, session) => { + client.auth.onAuthStateChange((event, session) => { authSession = session - authProfile = session ? await getAuthProfile() : null + authProfile = null updateAuthControls() if (event === "PASSWORD_RECOVERY") { @@ -221,7 +223,12 @@ async function initializeAuth() { return } - if (event === "SIGNED_IN") window.location.reload() + if (session) { + getAuthProfile().then(profile => { + authProfile = profile + updateAuthControls() + }) + } }) } catch (error) { console.warn("Supabase auth unavailable:", error) @@ -410,6 +417,7 @@ function applyHourlyRound(round) { hourlyRound = round targetWord = round.word || "" if (targetWord) dictionarySet.add(targetWord) + resetBoardForRound() restoreGuesses(round.guesses || []) currentGuessIndex = round.completedAt ? round.guessCount || currentGuessIndex : currentGuessIndex updateTriesStatus() @@ -429,6 +437,22 @@ function applyHourlyRound(round) { startInteraction() } +function resetBoardForRound() { + [...guessGrid.children].forEach(tile => { + tile.textContent = "" + tile.removeAttribute("data-state") + tile.removeAttribute("data-letter") + tile.setAttribute("aria-label", "Empty letter") + }) + + keyboard.querySelectorAll(".wrong, .wrong-position, .correct").forEach(key => { + key.classList.remove("wrong", "wrong-position", "correct") + }) + + currentGuessIndex = 0 + currentTileIndex = 0 +} + function restoreGuesses(guesses) { if (!Array.isArray(guesses) || guesses.length === 0) { currentGuessIndex = 0 @@ -605,7 +629,10 @@ async function signInWithPassword(event) { if (error) { setCreateAccountMode(true) authStatus.textContent = `${readableAuthError(error)} Create an account below if this is your first time.` + return } + + window.location.reload() } async function isUsernameAvailable(username) { @@ -809,7 +836,7 @@ async function submitGuess() { const states = normalizeGuessStates(result.states) if (states.length !== WORD_LENGTH) { - recoverFromGuessError(activeTiles, "Guess could not be scored") + await recoverFromGuessError(activeTiles, "Guess could not be scored") return } @@ -827,7 +854,7 @@ async function resolveGuess(guess, activeTiles) { return await submitRemoteGuess(guess) } catch (error) { console.warn("Failed to submit guess:", error) - recoverFromGuessError(activeTiles, error.message || "Guess could not be saved") + await recoverFromGuessError(activeTiles, error.message || "Guess could not be saved") return null } } @@ -846,10 +873,22 @@ async function submitRemoteGuess(guess) { const client = getSupabaseClient() if (!client || !hourlyRound?.id) throw new Error("Sign in again to save this guess") - const { data, error } = await client.rpc("submit_guess", { - target_round_id: hourlyRound.id, - submitted_guess: guess - }) + const { data: { session }, error: sessionError } = await withTimeout( + client.auth.getSession(), + GUESS_TIMEOUT_MS, + "Session check timed out. Try again." + ) + + if (sessionError || !session) throw new Error("Please sign in again.") + + const { data, error } = await withTimeout( + client.rpc("submit_guess", { + target_round_id: hourlyRound.id, + submitted_guess: guess + }), + GUESS_TIMEOUT_MS, + "Connection timed out. Resyncing your round." + ) if (error) throw error @@ -867,6 +906,15 @@ async function submitRemoteGuess(guess) { } } +function withTimeout(promise, timeout, message) { + let timeoutId + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => reject(new Error(message)), timeout) + }) + + return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timeoutId)) +} + function normalizeGuessStates(states) { if (Array.isArray(states)) return states @@ -882,12 +930,22 @@ function normalizeGuessStates(states) { return [] } -function recoverFromGuessError(activeTiles, message) { +async function recoverFromGuessError(activeTiles, message) { showAlert(message) shakeTiles(activeTiles) roundStatus.textContent = "Try again" isAnimating = false - startInteraction() + if (hourlyRound?.backend === "supabase") await resyncRemoteRound() + if (!gameFinished) startInteraction() +} + +async function resyncRemoteRound() { + try { + const round = await startRemoteHourlyRound() + if (round) applyHourlyRound(round) + } catch (error) { + console.warn("Failed to resync round:", error) + } } function isValidGuess(guess) { @@ -1018,7 +1076,7 @@ function checkWinLose(guess, tiles, roundResult) { danceTiles(tiles) celebrateWithConfetti() roundStatus.textContent = `Solved in ${currentGuessIndex}` - setTimeout(() => showWordDefinition(targetWord, true), 1800) + loadWordDefinition(targetWord, true) startLockCountdown(2400) showLeaderboardAfterCompletion() stopInteraction() @@ -1031,9 +1089,8 @@ function checkWinLose(guess, tiles, roundResult) { gameFinished = true lastResult = { won: false, guesses: MAX_GUESSES, word: targetWord } saveGameResult(lastResult) - showAlert(targetWord.toUpperCase(), null) roundStatus.textContent = "Round complete" - showWordDefinition(targetWord, false) + loadWordDefinition(targetWord, false) startLockCountdown(2400) showLeaderboardAfterCompletion() stopInteraction() @@ -1051,6 +1108,7 @@ function updateTriesStatus() { function showLeaderboardAfterCompletion() { setTimeout(() => { if (statsModal.open || authModal?.open) return + if (leaderboardModal?.open) return leaderboardScope = "hour" leaderboardTabs.forEach(tab => { tab.classList.toggle("active", tab.dataset.leaderboardScope === leaderboardScope) @@ -1124,11 +1182,6 @@ function playCelebrationSound() { }) } -async function showWordDefinition(word, isWin = true) { - const definition = await loadWordDefinition(word, isWin) - showDefinitionAlert(definition) -} - async function loadWordDefinition(word, isWin = true) { try { const response = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`) @@ -1158,24 +1211,10 @@ async function loadWordDefinition(word, isWin = true) { } renderStatsDefinition() + renderLeaderboardResult() return lastDefinition } -function showDefinitionAlert({ title, body, example, isWin }) { - const alert = document.createElement("button") - alert.type = "button" - alert.className = `alert definition-alert ${isWin ? "win-definition" : "lose-definition"}` - alert.innerHTML = ` - ${escapeHtml(title)} - ${escapeHtml(body)} - ${example ? `

Example: “${escapeHtml(example)}”
` : ""} - ` - - alertContainer.prepend(alert) - alert.addEventListener("click", () => dismissAlert(alert)) - setTimeout(() => dismissAlert(alert), 10000) -} - function escapeHtml(value) { return String(value) .replaceAll("&", "&") @@ -1228,6 +1267,7 @@ async function openStats() { async function openLeaderboard() { renderLeaderboardLoading() + renderLeaderboardResult() leaderboardModal?.showModal() await renderLeaderboard() } @@ -1408,6 +1448,36 @@ function renderLeaderboardLoading() { leaderboardList.innerHTML = '
Loading this hour\'s finishers...
' updateLeaderboardCountdown() + renderLeaderboardResult() +} + +function renderLeaderboardResult() { + if (!leaderboardResult) return + + if (!lastResult) { + leaderboardResult.hidden = true + leaderboardResult.innerHTML = "" + return + } + + leaderboardResult.hidden = false + const status = lastResult.won ? `Solved in ${lastResult.guesses}/6` : "Round complete" + const definition = lastDefinition + ? ` + ${escapeHtml(lastDefinition.body)} + ${lastDefinition.example ? `Example: ${escapeHtml(lastDefinition.example)}` : ""} + ` + : "Loading definition..." + + leaderboardResult.innerHTML = ` +
+ ${escapeHtml(lastResult.word || "Word reveal")} + ${status} +
+
+ ${definition} +
+ ` } async function renderLeaderboard() {