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>
This commit is contained in:
1 parent
710c9c5b79
commit
698fe9d14a
5 files changed
+70
-41
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
h, fill, icon, tile, bigBoard, emptyState, skeleton, toast, copyText, confetti, mountShell,
|
||||
profileHeader, parseBoardName, boardPage, historyPage, getBoard, getLastBoard, findGame, today,
|
||||
formatDay, resultText, shareText,
|
||||
formatDay, resultText, shareText, verdict,
|
||||
} from "./wordle.js?v=3";
|
||||
|
||||
mountShell();
|
||||
@@ -57,12 +57,12 @@ async function show(user, day) {
|
||||
const answerEl = board.won || !board.answer ? null
|
||||
: h("div", { class: "answer-row", "data-letters": "hidden" },
|
||||
h("span", {}, "The word was"),
|
||||
[...board.answer].map(letter => tile(letter, "correct")));
|
||||
h("div", { class: "answer-tiles" }, [...board.answer].map(letter => tile(letter, "correct"))));
|
||||
|
||||
fill(stage,
|
||||
h("div", { class: "headline" },
|
||||
h("span", { class: board.won ? "badge win" : "badge loss", style: { "font-size": "0.95rem", padding: "6px 14px" } },
|
||||
board.won ? `Solved in ${resultText(board)}` : `Missed it - ${resultText(board)}`)),
|
||||
`${verdict(board)} · ${resultText(board)}`)),
|
||||
boardEl,
|
||||
answerEl,
|
||||
revealButton(boardEl, answerEl),
|
||||
|
||||
+18
-13
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
h, icon, miniBoard, emptyState, skeleton, mountShell, profileHeader, normalizeUser, boardPage,
|
||||
getHistory, today, formatDay, relativeDay, weekdayIndex, resultText,
|
||||
getHistory, today, formatDay, relativeDay, weekdayIndex, dayOfMonth, resultText,
|
||||
} from "./wordle.js?v=3";
|
||||
|
||||
mountShell();
|
||||
@@ -19,12 +19,12 @@ if (!user) {
|
||||
|
||||
async function show(user) {
|
||||
const header = profileHeader(user, "history", { meta: "Loading games…" });
|
||||
const summary = h("div", { class: "stat-grid" }, Array.from({ length: 4 }, () => skeleton("104px")));
|
||||
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" } },
|
||||
summary, calendar,
|
||||
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)));
|
||||
@@ -49,7 +49,7 @@ async function show(user) {
|
||||
h("div", { class: "card-head" },
|
||||
h("h2", {}, "Calendar"),
|
||||
h("span", { class: "sub" }, `${formatDay(start, "medium")} - ${formatDay(end, "medium")} (UTC)`)),
|
||||
heatmap(user, boards, start, end));
|
||||
calendarGrid(user, boards, start, end));
|
||||
|
||||
games.replaceChildren(...(boards.length
|
||||
? boards.map(board => gameCard(user, board))
|
||||
@@ -75,15 +75,19 @@ function stat(value, label) {
|
||||
return h("div", { class: "stat" }, h("div", { class: "value" }, String(value)), h("div", { class: "label" }, label));
|
||||
}
|
||||
|
||||
// GitHub-style grid: a column per week, Monday at the top
|
||||
function heatmap(user, boards, start, end) {
|
||||
// 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 = Array.from({ length: weekdayIndex(start) }, () => h("span", { class: "heat none" }));
|
||||
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 cls = day === end ? "heat today" : "heat";
|
||||
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` }));
|
||||
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";
|
||||
@@ -93,14 +97,15 @@ function heatmap(user, boards, start, end) {
|
||||
"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: "heat", "data-level": level }), label];
|
||||
|
||||
const key = (level, label) => [h("span", { class: "cal-key", "data-level": level }), label];
|
||||
return h("div", {},
|
||||
h("div", { class: "heatmap-wrap" }, h("div", { class: "heatmap" }, cells)),
|
||||
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: "heat" }), "not played"]));
|
||||
[h("span", { class: "cal-key" }), "not played"]));
|
||||
}
|
||||
|
||||
function gameCard(user, board) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
h, icon, miniBoard, emptyState, skeleton, toast, copyText, countUp, countdown, mountShell,
|
||||
profileHeader, normalizeUser, statsPage, boardPage, getStats, getHistory, today, formatDay,
|
||||
relativeDay, resultText, describeBoard,
|
||||
relativeDay, resultText, describeBoard, verdict,
|
||||
} from "./wordle.js?v=3";
|
||||
|
||||
mountShell();
|
||||
@@ -102,7 +102,7 @@ function todayCard(user, board, day) {
|
||||
? [
|
||||
h("a", { href: boardPage(user, day), title: "Open today's board" }, miniBoard(board, { large: true })),
|
||||
h("div", { class: "text" },
|
||||
h("div", { class: "big" }, board.won ? `Solved ${resultText(board)}` : "Missed it"),
|
||||
h("div", { class: "big" }, `${verdict(board)} ${resultText(board)}`),
|
||||
h("p", { style: { margin: "6px 0 14px", color: "var(--muted)" } }, describeBoard(board)),
|
||||
h("a", { class: "btn btn-sm", href: boardPage(user, day) }, icon("grid"), "View board"))]
|
||||
: [
|
||||
|
||||
+40
-22
@@ -379,11 +379,12 @@ main.container { padding-top: 32px; padding-bottom: 64px; }
|
||||
flex: none;
|
||||
}
|
||||
.avatar span { border-radius: 2px; }
|
||||
.avatar span[data-state="none"] { background: color-mix(in srgb, var(--text) 5%, transparent); }
|
||||
.avatar span[data-state="correct"] { background: var(--correct); }
|
||||
.avatar span[data-state="present"] { background: var(--present); }
|
||||
.avatar-lg { --size: 84px; gap: 3px; }
|
||||
.chip .avatar { --size: 26px; gap: 1px; padding: 3px; border-radius: 7px; }
|
||||
.lb-row .avatar { --size: 36px; }
|
||||
.lb-row .avatar { --size: 38px; gap: 1px; padding: 4px; }
|
||||
.avatar-lg span { border-radius: 3px; }
|
||||
|
||||
/* ---------- profile header (stats, history, board pages) ---------- */
|
||||
@@ -399,7 +400,6 @@ main.container { padding-top: 32px; padding-bottom: 64px; }
|
||||
.tabs { width: 100%; }
|
||||
.tabs a { flex: 1; justify-content: center; padding: 8px 6px; font-size: 0.88rem; }
|
||||
}
|
||||
.terminal-body .line { white-space: pre-wrap; }
|
||||
.tabs a:hover { color: var(--text); text-decoration: none; }
|
||||
.tabs a[aria-current="page"] { background: var(--surface-3); color: var(--text); }
|
||||
|
||||
@@ -443,12 +443,11 @@ main.container { padding-top: 32px; padding-bottom: 64px; }
|
||||
.dist-row.miss .dist-bar { background: color-mix(in srgb, var(--danger) 70%, var(--absent)); }
|
||||
@keyframes grow { from { transform: scaleX(0); } }
|
||||
|
||||
.form-strip { display: flex; gap: 6px; flex-wrap: wrap; }
|
||||
.form-strip { display: grid; grid-template-columns: repeat(7, minmax(0, 40px)); gap: 6px; }
|
||||
.form-day {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 8px;
|
||||
font: 800 0.9rem/1 var(--mono);
|
||||
background: var(--surface-2);
|
||||
@@ -467,22 +466,39 @@ a.form-day:hover { text-decoration: none; transform: translateY(-2px); }
|
||||
|
||||
/* ---------- history ---------- */
|
||||
|
||||
.heatmap-wrap { overflow-x: auto; padding-bottom: 4px; }
|
||||
.heatmap { display: grid; grid-auto-flow: column; grid-template-rows: repeat(7, 16px); grid-auto-columns: 16px; gap: 4px; width: max-content; }
|
||||
.heat {
|
||||
border-radius: 4px;
|
||||
background: var(--surface-3);
|
||||
border: 1px solid transparent;
|
||||
.stat-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
|
||||
.cal { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; }
|
||||
.cal-head { text-align: center; font: 700 0.7rem/1 var(--mono); color: var(--faint); text-transform: uppercase; letter-spacing: 0.08em; padding-bottom: 4px; }
|
||||
.cal-day {
|
||||
position: relative;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 10px;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
color: #fff;
|
||||
font: 800 1.05rem/1 var(--mono);
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
.heat.none { background: transparent; }
|
||||
.heat[data-level="1"] { background: color-mix(in srgb, var(--correct) 100%, transparent); }
|
||||
.heat[data-level="2"] { background: color-mix(in srgb, var(--correct) 75%, var(--surface-3)); }
|
||||
.heat[data-level="3"] { background: color-mix(in srgb, var(--correct) 50%, var(--surface-3)); }
|
||||
.heat[data-level="loss"] { background: color-mix(in srgb, var(--danger) 70%, var(--absent)); }
|
||||
.heat.today { border-color: var(--accent); }
|
||||
a.heat:hover { outline: 2px solid var(--text); outline-offset: 1px; }
|
||||
.legend { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 0.8rem; margin-top: 10px; flex-wrap: wrap; }
|
||||
.legend .heat { width: 12px; height: 12px; display: inline-block; }
|
||||
.cal-day .date { position: absolute; top: 5px; left: 7px; font-size: 0.64rem; font-weight: 600; color: var(--faint); }
|
||||
.cal-day[data-level] { border-color: transparent; }
|
||||
.cal-day[data-level] .date { color: rgba(255, 255, 255, 0.8); }
|
||||
.cal-day.today { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
a.cal-day:hover { transform: translateY(-2px); text-decoration: none; }
|
||||
@media (max-width: 480px) {
|
||||
.cal { gap: 4px; }
|
||||
.cal-day { border-radius: 7px; font-size: 0.85rem; }
|
||||
.cal-day .date { top: 3px; left: 4px; font-size: 0.55rem; }
|
||||
}
|
||||
.cal-key { width: 12px; height: 12px; border-radius: 3px; display: inline-block; background: var(--surface-3); }
|
||||
[data-level="1"] { background: var(--correct); }
|
||||
[data-level="2"] { background: color-mix(in srgb, var(--correct) 72%, var(--surface-3)); }
|
||||
[data-level="3"] { background: color-mix(in srgb, var(--correct) 48%, var(--surface-3)); }
|
||||
[data-level="loss"] { background: color-mix(in srgb, var(--danger) 70%, var(--absent)); }
|
||||
.legend { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 0.8rem; margin-top: 14px; flex-wrap: wrap; }
|
||||
.legend .cal-key:not(:first-child) { margin-left: 6px; }
|
||||
|
||||
.game-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
|
||||
.game-card {
|
||||
@@ -504,13 +520,15 @@ a.heat:hover { outline: 2px solid var(--text); outline-offset: 1px; }
|
||||
|
||||
.board-stage { display: grid; gap: 22px; justify-items: center; text-align: center; }
|
||||
.board-stage .headline { font: 800 1.1rem/1.2 var(--mono); }
|
||||
.answer-row { display: flex; gap: 6px; justify-content: center; align-items: center; color: var(--muted); }
|
||||
.answer-row { display: grid; gap: 8px; justify-items: center; color: var(--muted); font-size: 0.9rem; }
|
||||
.answer-tiles { display: flex; gap: 6px; }
|
||||
.answer-row .tile { width: 38px; font-size: 1.1rem; }
|
||||
.hold-btn { touch-action: none; min-width: 230px; }
|
||||
.hold-btn .icon { transition: transform 0.2s ease; }
|
||||
.hold-btn[aria-pressed="true"] .icon { transform: scale(1.2); }
|
||||
.nav-row { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
|
||||
.kbd-hint { color: var(--faint); font-size: 0.82rem; }
|
||||
@media (hover: none) { .kbd-hint { display: none; } } /* no keyboard on touch screens */
|
||||
.kbd-hint kbd { padding: 1px 6px; border-radius: 5px; border: 1px solid var(--border-strong); background: var(--surface-2); color: var(--muted); }
|
||||
|
||||
/* ---------- home page ---------- */
|
||||
@@ -562,7 +580,7 @@ a.heat:hover { outline: 2px solid var(--text); outline-offset: 1px; }
|
||||
.terminal-bar i:nth-child(3) { background: #28c840; }
|
||||
.terminal-bar span { margin-left: 8px; font-size: 0.8rem; color: #6c8a76; }
|
||||
.terminal-body { padding: 18px 20px 22px; min-height: 380px; font-size: 0.92rem; }
|
||||
.terminal-body .line { min-height: 1.5em; white-space: pre; overflow: hidden; }
|
||||
.terminal-body .line { min-height: 1.5em; white-space: pre-wrap; }
|
||||
.terminal-body .prompt { color: #51ff7a; }
|
||||
.terminal-body .dim { color: #5f7a68; }
|
||||
.caret { display: inline-block; width: 0.6em; height: 1.1em; vertical-align: text-bottom; background: #51ff7a; animation: blink 1s steps(1) infinite; }
|
||||
|
||||
@@ -107,6 +107,7 @@ export function formatDay(day, style = "long") {
|
||||
long: { weekday: "long", day: "numeric", month: "long", year: "numeric" },
|
||||
medium: { day: "numeric", month: "short", year: "numeric" },
|
||||
short: { weekday: "short", day: "numeric", month: "short" },
|
||||
month: { day: "numeric", month: "short" },
|
||||
};
|
||||
return new Date(day * DAY_MS).toLocaleDateString(undefined, { timeZone: "UTC", ...styles[style] });
|
||||
}
|
||||
@@ -119,6 +120,7 @@ export function relativeDay(day) {
|
||||
}
|
||||
|
||||
export const weekdayIndex = day => (new Date(day * DAY_MS).getUTCDay() + 6) % 7; // Monday = 0
|
||||
export const dayOfMonth = day => new Date(day * DAY_MS).getUTCDate();
|
||||
|
||||
export function formatCountdown(ms) {
|
||||
const total = Math.max(0, Math.floor(ms / 1000));
|
||||
@@ -329,6 +331,10 @@ export function score(guess, answer) {
|
||||
|
||||
export const resultText = board => `${board.won ? board.guesses : "X"}/6`;
|
||||
|
||||
// What the real Wordle says for a win in 1-6 guesses
|
||||
const PRAISE = ["Genius", "Magnificent", "Impressive", "Splendid", "Great", "Phew"];
|
||||
export const verdict = board => (board.won ? PRAISE[board.guesses - 1] : "Missed it");
|
||||
|
||||
export function describeBoard(board) {
|
||||
return board.won ? `Solved in ${board.guesses} of 6 guesses` : "Didn't find the word";
|
||||
}
|
||||
@@ -637,7 +643,7 @@ export function followButton(user) {
|
||||
const on = !isFollowing(user);
|
||||
setFollowing(user, on);
|
||||
render();
|
||||
toast(on ? `Added ${user} to your leaderboard` : `Removed ${user} from your leaderboard`);
|
||||
toast(on ? `Added ${user} to your leaderboard on the home page` : `Removed ${user} from your leaderboard`);
|
||||
});
|
||||
render();
|
||||
return button;
|
||||
|
||||
Reference in new issue
Block a user