Align the by-season scores into a columns-aligned table (#198)

The by-season chips wrapped freely, so a metric's scores didn't line up
across seasons. Render it as a table instead — seasons as rows, one aligned
column per metric (plus an Overall column), tinted cells, horizontal scroll
on narrow screens.
This commit is contained in:
Emi Griffith 2026-07-19 16:41:03 -07:00 committed by GitHub
parent e82323f11a
commit 91a780dd55
2 changed files with 15 additions and 30 deletions

View file

@ -122,19 +122,17 @@ function render(data) {
const cards = METRIC_ORDER.map((k) => metricCard(annual.metrics[k])).join(""); const cards = METRIC_ORDER.map((k) => metricCard(annual.metrics[k])).join("");
const wbNote = wetbulbNote(annual.metrics.wetbulb, baseYrs); const wbNote = wetbulbNote(annual.metrics.wetbulb, baseYrs);
// ---- by-season rows ---- // ---- by-season table (seasons × metrics, columns aligned) ----
const seasonHead = `<tr><th></th><th>Overall</th>${METRIC_ORDER.map((k) =>
`<th>${shortLabel(k)}</th>`).join("")}</tr>`;
const seasonRows = SEASON_ORDER.map((sk) => { const seasonRows = SEASON_ORDER.map((sk) => {
const sl = s.slices[sk]; const sl = s.slices[sk];
const chips = METRIC_ORDER.map((k) => seasonChip(sl.metrics[k])).join("");
const oc = sl.overall; const oc = sl.overall;
const overallChip = oc const overallCell = oc
? `<span class="score-chip ${tintClass(oc.class)}" title="Overall">${oc.score}</span>` ? `<td class="${tintClass(oc.class)}"><strong>${oc.score}</strong></td>`
: `<span class="score-chip t-none" title="Overall">—</span>`; : `<td class="t-none">—</td>`;
return `<div class="season-scores"> const cells = METRIC_ORDER.map((k) => seasonCell(sl.metrics[k])).join("");
<span class="season-scores-name">${SEASON_NAMES[sk]}</span> return `<tr><th>${SEASON_NAMES[sk]}</th>${overallCell}${cells}</tr>`;
<span class="season-scores-overall">${overallChip}</span>
<span class="season-scores-chips">${chips}</span>
</div>`;
}).join(""); }).join("");
scoreBody.innerHTML = ` scoreBody.innerHTML = `
@ -142,8 +140,8 @@ function render(data) {
${wbNote} ${wbNote}
<section class="score-section"> <section class="score-section">
<p class="section-title">By season</p> <p class="section-title">By season</p>
<p class="score-legend">${METRIC_ORDER.map((k) => shortLabel(k)).join(" · ")}</p> <div class="score-table-wrap"><table class="score-table season-table">
${seasonRows} <thead>${seasonHead}</thead><tbody>${seasonRows}</tbody></table></div>
</section> </section>
<button type="button" class="summary-toggle" aria-expanded="false" aria-controls="score-summary"> <button type="button" class="summary-toggle" aria-expanded="false" aria-controls="score-summary">
Summary <span class="summary-caret" aria-hidden="true"></span></button> Summary <span class="summary-caret" aria-hidden="true"></span></button>
@ -192,9 +190,9 @@ function scoreDirection(m) {
return `~${pts} pts ${esc(m.direction)}`; return `~${pts} pts ${esc(m.direction)}`;
} }
function seasonChip(m) { function seasonCell(m) {
if (!m || m.score == null) return `<span class="score-chip t-none">—</span>`; if (!m || m.score == null) return `<td class="t-none">—</td>`;
return `<span class="score-chip ${tintClass(m.class)}" title="${esc(m.label)}: ${esc(m.grade)}">${m.score}</span>`; return `<td class="${tintClass(m.class)}" title="${esc(m.label)}: ${esc(m.grade)}">${m.score}</td>`;
} }
const shortLabel = (k) => ({ precip: "Precip", tmax: "High", tmin: "Low", feels: "Feels", const shortLabel = (k) => ({ precip: "Precip", tmax: "High", tmin: "Low", feels: "Feels",

View file

@ -1730,7 +1730,6 @@ td.rec-date { color: var(--muted); font-size: 13px; }
.score-note { font-size: 12px; color: var(--muted); margin: 12px 0 0; line-height: 1.4; } .score-note { font-size: 12px; color: var(--muted); margin: 12px 0 0; line-height: 1.4; }
.score-section { margin: 20px 0; } .score-section { margin: 20px 0; }
.score-legend { font-size: 12px; color: var(--muted); margin: -6px 0 10px; }
/* Wet-bulb explainer + heat-stress-day share. */ /* Wet-bulb explainer + heat-stress-day share. */
.score-callout { .score-callout {
@ -1738,19 +1737,8 @@ td.rec-date { color: var(--muted); font-size: 13px; }
padding: 12px 14px; margin: 14px 0 4px; font-size: 13px; line-height: 1.5; color: var(--text); padding: 12px 14px; margin: 14px 0 4px; font-size: 13px; line-height: 1.5; color: var(--text);
} }
/* One row per season: name · overall · a chip per metric. */ /* By-season scores: seasons as rows, one aligned column per metric. */
.season-scores { .season-table th:first-child, .season-table td:first-child { text-align: left; }
display: grid; grid-template-columns: 64px 40px 1fr; align-items: center;
gap: 10px; padding: 7px 0; border-top: 1px solid var(--border);
}
.season-scores-name { font-size: 13px; font-weight: 600; color: var(--text); }
.season-scores-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.score-chip {
display: inline-flex; align-items: center; justify-content: center;
min-width: 30px; padding: 3px 6px; border-radius: 7px;
font-size: 12px; font-weight: 700; color: var(--text);
}
.season-scores-overall .score-chip { min-width: 34px; font-weight: 800; }
/* Summary reveal — pill button + caret, mirrors the season-expand pattern. */ /* Summary reveal — pill button + caret, mirrors the season-expand pattern. */
.summary-toggle { .summary-toggle {
@ -1787,7 +1775,6 @@ td.rec-date { color: var(--muted); font-size: 13px; }
@media (max-width: 640px) { @media (max-width: 640px) {
.score-num { font-size: 40px; } .score-num { font-size: 40px; }
.score-hero { padding: 14px 15px; } .score-hero { padding: 14px 15px; }
.season-scores { grid-template-columns: 56px 36px 1fr; gap: 8px; }
} }
/* Monthly temperature-range strip: one gradient bar per month, lowhigh on a shared /* Monthly temperature-range strip: one gradient bar per month, lowhigh on a shared