Update Version (Beta): login + leaderboard/ word definition

This commit is contained in:
Zakaria 2026-05-13 11:24:27 -04:00
parent 66f8207985
commit fe5649dc2d
6 changed files with 60 additions and 107 deletions

View File

@ -462,43 +462,52 @@ h2 {
line-height: 1.5;
}
.history-panel {
.definition-panel {
display: grid;
gap: 10px;
margin-bottom: 22px;
}
.history-panel h3 {
.definition-panel h3 {
margin: 0;
font-family: var(--font-display);
font-size: 1.15rem;
font-weight: 500;
}
.history-list {
display: grid;
gap: 8px;
}
.history-row {
display: grid;
grid-template-columns: 1fr auto;
gap: 12px;
padding: 10px 12px;
.stats-definition {
padding: 14px;
border: 1px solid var(--border);
border-radius: 14px;
background: var(--bg);
color: var(--muted);
line-height: 1.55;
text-align: start;
}
.history-word {
.stats-definition strong {
display: block;
margin-bottom: 6px;
color: var(--fg);
font-family: var(--font-display);
font-size: 1.35rem;
font-weight: 500;
line-height: 1.1;
}
.stats-definition .word-label {
font-family: var(--font-body);
font-size: 0.76rem;
font-weight: 900;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.history-meta {
.stats-definition em {
display: block;
margin-top: 10px;
color: var(--muted);
font-size: 0.84rem;
font-style: normal;
}
.leaderboard-card {

View File

@ -19,7 +19,7 @@ The database chooses the word from `public.wordle_words` based on the current UT
The hourly leaderboard uses completed authenticated rounds for the current UTC hour. It ranks wins first, then fewer guesses, then earliest completion time.
Leaderboard tabs include this hour, today, and all time. Signed-in users are included even if their row falls outside the top 25. Stats also show recent personal history and this hour's average score summary.
Leaderboard tabs include this hour, today, and all time. Signed-in users are included even if their row falls outside the top 25. Stats also show the current revealed word definition and this hour's average score summary.
`supabase/seed-word-data.sql` is generated from `targetWords.json` and `dictionary.json`:

View File

@ -72,9 +72,9 @@
<div class="stats-grid" id="stats-grid"></div>
<div class="stats-note" id="stats-note"></div>
<div class="guess-bars" id="guess-bars" aria-label="Guess distribution"></div>
<div class="history-panel">
<h3>Recent history</h3>
<div class="history-list" id="history-list"></div>
<div class="definition-panel" id="stats-definition-panel">
<h3>Word reveal</h3>
<div class="stats-definition" id="stats-definition"></div>
</div>
<menu class="modal-actions">
<button type="button" class="button secondary" id="reset-stats">Reset stats</button>

View File

@ -72,9 +72,9 @@
<div class="stats-grid" id="stats-grid"></div>
<div class="stats-note" id="stats-note"></div>
<div class="guess-bars" id="guess-bars" aria-label="Guess distribution"></div>
<div class="history-panel">
<h3>Recent history</h3>
<div class="history-list" id="history-list"></div>
<div class="definition-panel" id="stats-definition-panel">
<h3>Word reveal</h3>
<div class="stats-definition" id="stats-definition"></div>
</div>
<menu class="modal-actions">
<button type="button" class="button secondary" id="reset-stats">Reset stats</button>

View File

@ -16,6 +16,7 @@ let authSession = null
let authProfile = null
let leaderboardScope = "hour"
let latestLeaderboardRows = []
let lastDefinition = null
const WORD_LENGTH = 5
const MAX_GUESSES = 6
@ -96,7 +97,7 @@ const statsModal = document.getElementById("stats-modal")
const statsGrid = document.getElementById("stats-grid")
const statsNote = document.getElementById("stats-note")
const guessBars = document.getElementById("guess-bars")
const historyList = document.getElementById("history-list")
const statsDefinition = document.getElementById("stats-definition")
const resetStatsButton = document.getElementById("reset-stats")
const shareResultsButton = document.getElementById("share-results")
@ -419,6 +420,7 @@ function applyHourlyRound(round) {
guesses: round.guessCount || MAX_GUESSES,
word: round.word || ""
}
if (round.word) loadWordDefinition(round.word, Boolean(round.won))
lockUntilNextWord()
return
}
@ -1123,6 +1125,11 @@ 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}`)
if (!response.ok) throw new Error(`API request failed: ${response.status}`)
@ -1135,22 +1142,25 @@ async function showWordDefinition(word, isWin = true) {
if (!definition) throw new Error("No definition found")
showDefinitionAlert({
lastDefinition = {
title: `${word.toUpperCase()} ${partOfSpeech ? `(${partOfSpeech})` : ""}`,
body: definition,
example,
isWin
})
}
} catch (error) {
console.info("Definition lookup unavailable:", error)
showDefinitionAlert({
lastDefinition = {
title: word.toUpperCase(),
body: "Definition not available at the moment.",
isWin
})
}
}
renderStatsDefinition()
return lastDefinition
}
function showDefinitionAlert({ title, body, example, isWin }) {
const alert = document.createElement("button")
alert.type = "button"
@ -1308,7 +1318,7 @@ async function renderStats() {
.join("")
renderStatsNote(summary)
await renderHistory()
renderStatsDefinition()
}
function renderStatsNote(summary) {
@ -1372,60 +1382,25 @@ async function getHourlySummary() {
}
}
async function renderHistory() {
if (!historyList) return
function renderStatsDefinition() {
if (!statsDefinition) return
if (hourlyRound?.backend !== "supabase") {
historyList.innerHTML = '<div class="leaderboard-empty">Sign in to keep a cross-device history.</div>'
if (!lastResult) {
statsDefinition.innerHTML = "Finish this hour's word to reveal its definition here."
return
}
const rows = await getPlayerHistory()
if (rows.length === 0) {
historyList.innerHTML = '<div class="leaderboard-empty">No completed rounds yet.</div>'
if (!lastDefinition) {
statsDefinition.innerHTML = "Loading definition..."
return
}
historyList.innerHTML = rows
.map(row => `
<div class="history-row">
<span>
<span class="history-word">${escapeHtml(row.word)}</span>
<span class="history-meta">${formatHistoryHour(row.hourStart)}</span>
</span>
<span class="history-meta">${row.won ? `${row.guessCount}/6` : "X/6"}</span>
</div>
`)
.join("")
}
async function getPlayerHistory() {
const client = getSupabaseClient()
if (!client || !authSession) return []
const { data, error } = await client.rpc("get_player_history", { history_limit: 8 })
if (error) {
console.warn("Failed to load history:", error)
return []
}
return (Array.isArray(data) ? data : []).map(row => ({
hourStart: row.hour_start,
word: row.word || "-----",
won: Boolean(row.won),
guessCount: row.guess_count || MAX_GUESSES,
completedAt: row.completed_at
}))
}
function formatHistoryHour(value) {
if (!value) return ""
return new Intl.DateTimeFormat(undefined, {
month: "short",
day: "numeric",
hour: "numeric"
}).format(new Date(value))
statsDefinition.innerHTML = `
<span class="word-label">${lastResult.won ? "Solved word" : "Answer"}</span>
<strong>${escapeHtml(lastDefinition.title)}</strong>
<span>${escapeHtml(lastDefinition.body)}</span>
${lastDefinition.example ? `<em>Example: ${escapeHtml(lastDefinition.example)}</em>` : ""}
`
}
function renderLeaderboardLoading() {

View File

@ -674,37 +674,7 @@ begin
end;
$$;
create or replace function public.get_player_history(history_limit integer default 10)
returns table (
hour_start timestamptz,
word text,
won boolean,
guess_count integer,
completed_at timestamptz
)
language plpgsql
security definer
set search_path = public
as $$
begin
if auth.uid() is null then
raise exception 'Authentication required';
end if;
return query
select
rounds.hour_start,
rounds.word,
rounds.won,
rounds.guess_count,
rounds.completed_at
from public.wordle_rounds rounds
where rounds.user_id = auth.uid()
and rounds.completed_at is not null
order by rounds.hour_start desc
limit greatest(1, least(coalesce(history_limit, 10), 25));
end;
$$;
drop function if exists public.get_player_history(integer);
create or replace function public.get_hourly_summary()
returns table (
@ -760,6 +730,5 @@ grant execute on function public.submit_guess(uuid, text) to authenticated;
grant execute on function public.get_user_stats() to authenticated;
grant execute on function public.get_hourly_leaderboard() to anon, authenticated;
grant execute on function public.get_leaderboard(text) to anon, authenticated;
grant execute on function public.get_player_history(integer) to authenticated;
grant execute on function public.get_hourly_summary() to anon, authenticated;
grant execute on function public.complete_hourly_round(uuid, boolean, integer) to authenticated;