Files
wordle/www/html/wordle/history.js
T
adamandClaude Opus 5.5 698fe9d14a polish the website
- 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>
2026-09-29 21:36:27 +00:00

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"));
}