- show Wordle's praise (Genius ... Phew) for wins on the board and stats pages instead of repeating the score - history: month-style calendar with dates and results, next to the summary numbers, replacing the small heatmap strip - recent form as two rows of a week each - fuller identicons at small sizes - put "The word was" above the answer tiles, wrap the terminal demo on phones and hide keyboard hints on touch screens - say where followed players show up (the home page leaderboard) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
5.1 KiB
JavaScript
117 lines
5.1 KiB
JavaScript
import {
|
|
h, icon, miniBoard, emptyState, skeleton, mountShell, profileHeader, normalizeUser, boardPage,
|
|
getHistory, today, formatDay, relativeDay, weekdayIndex, dayOfMonth, resultText,
|
|
} from "./wordle.js?v=3";
|
|
|
|
mountShell();
|
|
const main = document.getElementById("main");
|
|
const user = normalizeUser(new URLSearchParams(location.search).get("user"));
|
|
const PAGE_DAYS = 35; // five weeks at a time
|
|
|
|
if (!user) {
|
|
document.title = "Wordle history";
|
|
main.append(emptyState("Whose history?", "No player was given in the link.",
|
|
h("a", { class: "btn btn-primary", href: "/wordle/#find" }, icon("search"), "Find a player")));
|
|
} else {
|
|
document.title = `${user} · Wordle history`;
|
|
show(user);
|
|
}
|
|
|
|
async function show(user) {
|
|
const header = profileHeader(user, "history", { meta: "Loading games…" });
|
|
const summary = h("div", { class: "stat-grid stat-grid-2" }, Array.from({ length: 4 }, () => skeleton("104px")));
|
|
const calendar = h("div", { class: "card" }, skeleton("150px"));
|
|
const games = h("div", { class: "game-grid" }, Array.from({ length: 6 }, () => skeleton("190px")));
|
|
const more = h("button", { class: "btn", type: "button", hidden: true }, icon("calendar"), `Load ${PAGE_DAYS} more days`);
|
|
main.append(header, h("div", { class: "stack", style: { "margin-top": "28px" } },
|
|
h("div", { class: "grid-2" }, calendar, summary),
|
|
h("div", { class: "card-head", style: { "margin-bottom": "0" } }, h("h2", {}, "Games"), h("span", { class: "sub" }, "Newest first")),
|
|
games,
|
|
h("div", { style: { "text-align": "center" } }, more)));
|
|
|
|
const end = today();
|
|
let start = end - PAGE_DAYS + 1;
|
|
let boards = await getHistory(user, end, PAGE_DAYS);
|
|
|
|
const render = () => {
|
|
const wins = boards.filter(b => b.won);
|
|
const guesses = wins.reduce((sum, b) => sum + b.guesses, 0);
|
|
const span = end - start + 1;
|
|
header.setMeta(`${boards.length} games in the last ${span} days`);
|
|
|
|
summary.replaceChildren(
|
|
stat(boards.length, "Games"),
|
|
stat(wins.length, "Wins"),
|
|
stat(boards.length ? `${Math.round((wins.length * 100) / boards.length)}%` : "-", "Win rate"),
|
|
stat(wins.length ? (Math.floor((guesses * 100) / wins.length) / 100).toFixed(2) : "-", "Average guesses"));
|
|
|
|
calendar.replaceChildren(
|
|
h("div", { class: "card-head" },
|
|
h("h2", {}, "Calendar"),
|
|
h("span", { class: "sub" }, `${formatDay(start, "medium")} - ${formatDay(end, "medium")} (UTC)`)),
|
|
calendarGrid(user, boards, start, end));
|
|
|
|
games.replaceChildren(...(boards.length
|
|
? boards.map(board => gameCard(user, board))
|
|
: [h("p", { style: { color: "var(--muted)", "grid-column": "1 / -1" } },
|
|
`No games in the last ${span} days. Try loading more.`)]));
|
|
more.hidden = false;
|
|
};
|
|
render();
|
|
|
|
more.addEventListener("click", async () => {
|
|
more.disabled = true;
|
|
more.replaceChildren("Loading…");
|
|
const older = await getHistory(user, start - 1, PAGE_DAYS);
|
|
boards = [...boards, ...older];
|
|
start -= PAGE_DAYS;
|
|
more.disabled = false;
|
|
more.replaceChildren(icon("calendar"), `Load ${PAGE_DAYS} more days`);
|
|
render();
|
|
});
|
|
}
|
|
|
|
function stat(value, label) {
|
|
return h("div", { class: "stat" }, h("div", { class: "value" }, String(value)), h("div", { class: "label" }, label));
|
|
}
|
|
|
|
// Month-style calendar, a row per week starting on Monday. Played days
|
|
// show the result and link to their board.
|
|
function calendarGrid(user, boards, start, end) {
|
|
const byDay = new Map(boards.map(board => [board.day, board]));
|
|
const cells = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map(name => h("span", { class: "cal-head" }, name));
|
|
for (let i = 0; i < weekdayIndex(start); i++) cells.push(h("span"));
|
|
|
|
for (let day = start; day <= end; day++) {
|
|
const board = byDay.get(day);
|
|
const date = h("span", { class: "date" }, dayOfMonth(day) === 1 ? formatDay(day, "month") : String(dayOfMonth(day)));
|
|
const cls = day === end ? "cal-day today" : "cal-day";
|
|
if (!board) {
|
|
cells.push(h("span", { class: cls, title: `${formatDay(day, "short")}: not played` }, date));
|
|
continue;
|
|
}
|
|
const level = !board.won ? "loss" : board.guesses <= 2 ? "1" : board.guesses <= 4 ? "2" : "3";
|
|
cells.push(h("a", {
|
|
class: cls,
|
|
href: boardPage(user, day),
|
|
"data-level": level,
|
|
title: `${formatDay(day, "short")}: ${resultText(board)}`,
|
|
"aria-label": `${formatDay(day, "short")}: ${resultText(board)}`,
|
|
}, date, board.won ? String(board.guesses) : "✗"));
|
|
}
|
|
|
|
const key = (level, label) => [h("span", { class: "cal-key", "data-level": level }), label];
|
|
return h("div", {},
|
|
h("div", { class: "cal" }, cells),
|
|
h("div", { class: "legend" },
|
|
key("1", "1-2 guesses"), key("2", "3-4"), key("3", "5-6"), key("loss", "missed"),
|
|
[h("span", { class: "cal-key" }), "not played"]));
|
|
}
|
|
|
|
function gameCard(user, board) {
|
|
return h("a", { class: "game-card", href: boardPage(user, board.day) },
|
|
h("span", { class: "date" }, relativeDay(board.day)),
|
|
miniBoard(board, { large: true }),
|
|
h("span", { class: board.won ? "badge win" : "badge loss" }, board.won ? `Solved ${resultText(board)}` : "Missed X/6"));
|
|
}
|