import { CLOSE_ICON } from './icons.js'; import { logoLockup } from '../logo.js'; import { getProfileStats } from '../profileStats.js'; import { getAllPersonalBests } from '../pb.js'; import { getWeakCharDetails } from '../keyStats.js'; import { escapeHtml } from '../util.js'; import { drawProgressChart } from '../chart.js'; import { renderCornerRail } from '../cornerRail.js'; import { attachTooltips } from '../tooltip.js'; import { renderTopRail } from '../topRail.js'; // modeKey format is set in app.js: "words-25", "time-30", "quote-". function modeLabel(modeKey) { const [kind, arg] = modeKey.split('-'); if (kind === 'words') return `Words · ${arg}`; if (kind === 'time') return `Time · ${arg}s`; if (kind === 'quote') return arg === 'random' ? 'Random' : arg.charAt(0).toUpperCase() + arg.slice(1); return modeKey; } function formatDate(ts) { if (!ts) return ''; return new Date(ts).toLocaleDateString(); } export function renderStatsScreen(root, { onBack, onShowPlaceholder, onShowAccount, onShowLeaderboard, onShowFriends, onShowMultiplayer, onShowStore }) { const profile = getProfileStats(); const bests = getAllPersonalBests(); const weakKeys = getWeakCharDetails(8); // Derived from the stored history rather than kept as separate counters, // so these stay correct for anyone who already has a history recorded. const history = profile.history || []; const recent = history.slice(-10); const recentAvg = recent.length ? Math.round(recent.reduce((a, h) => a + h.wpm, 0) / recent.length) : 0; const bestAcc = history.length ? Math.round(Math.max(...history.map(h => h.accuracy))) : 0; const weekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000; const thisWeek = history.filter(h => h.date && new Date(h.date).getTime() >= weekAgo).length; // Recent form against the lifetime average: the single most useful thing // a stats page can tell you is whether you are getting better. const trend = recentAvg && profile.averageWpm ? recentAvg - profile.averageWpm : 0; root.innerHTML = `
${logoLockup()}

Stats

${profile.testsCompleted > 0 ? `
` : `
No tests completed yet - finish a test to start tracking stats.
`}

Personal bests

${bests.length > 0 ? `
${bests.map(b => `
${escapeHtml(modeLabel(b.modeKey))}
${Math.round(b.wpm)} wpm
${formatDate(b.date)}
`).join('')}
` : `
No personal bests recorded yet.
`} ${weakKeys.length ? `

Weakest keys

What Practice mode targets. Ranked by how often you mistype a key and how long you pause before it.
${weakKeys.map(k => `
${escapeHtml(k.char)} ${Math.round(k.errorRate * 100)}% ${Math.round(k.avgLatency)}ms
`).join('')}
` : ''}

Progress

${profile.history.length > 1 ? `
` : `
Complete a few more tests to see a progress chart.
`}
`; root.querySelectorAll('[data-action="menu"]').forEach(el => el.addEventListener('click', onBack)); attachTooltips(root); const cleanupTheme = renderTopRail(root, { onShowAccount, onShowFriends }); const canvas = root.querySelector('.stats-chart canvas'); let cleanupResize = () => {}; if (canvas) { const redraw = () => drawProgressChart(canvas, profile.history); redraw(); window.addEventListener('resize', redraw); cleanupResize = () => window.removeEventListener('resize', redraw); } const cleanupRail = renderCornerRail(root, { onShowStats: null, onShowPlaceholder, onShowAccount, onShowLeaderboard, onShowFriends, onShowMultiplayer, onShowStore }); return () => { cleanupResize(); cleanupTheme(); cleanupRail(); }; }