Merge pull request #52 from griffemi/fix/day-mobile-overflow

Day page: stop metric cards overflowing the viewport on phones
This commit is contained in:
Emi Griffith 2026-07-11 14:21:16 -07:00 committed by GitHub
commit 94dde1ebc6
2 changed files with 35 additions and 10 deletions

View file

@ -132,6 +132,22 @@ function render(data) {
].join("");
}
// Collapse a space-separated unit shared by both bounds ("17.3 g/m³19.9 g/m³"
// → "17.319.9 g/m³") so ranges fit the narrow value column on phones. Attached
// units (76°, 0.25") pass through untouched.
function fmtRange(fmt, lo, hi) {
const a = fmt(lo), b = fmt(hi);
const i = a.lastIndexOf(" ");
if (i > 0 && b.endsWith(a.slice(i))) return `${a.slice(0, i)}${b}`;
return `${a}${b}`;
}
// The unit-less value for the ◀ marker — the unit is already on the range beside
// it and in the card's summary, and dropping it keeps the marker on one line.
const bareVal = (fmt, v) => {
const s = fmt(v), i = s.lastIndexOf(" ");
return i > 0 ? s.slice(0, i) : s;
};
// One metric card: an observed summary line + the tier ladder (highest tier on
// top). The tier the observed value falls into is highlighted with its value.
function ladderCard(title, m, fmt, kind) {
@ -153,7 +169,7 @@ function ladderCard(title, m, fmt, kind) {
? `median ${fmt(m.ladder.median)}`
: `${m.ladder.rain_days.toLocaleString()} rain days · dry ${m.ladder.dry_pct}%`;
const note = `<span class="ladder-note-left">${noteLeft}</span>` +
`<span class="ladder-range">Historical Range ${fmt(m.ladder.min)}${fmt(m.ladder.max)}</span>`;
`<span class="ladder-range">Historical Range ${fmtRange(fmt, m.ladder.min, m.ladder.max)}</span>`;
const rows = m.ladder.tiers.map((t) => {
const active = t.c === activeClass ? " active" : "";
@ -163,9 +179,9 @@ function ladderCard(title, m, fmt, kind) {
if (t.c === "dry") { val = t.range; pct = ""; }
else if (t.hi == null) val = `> ${fmt(t.lo)}`; // top tier: strictly beyond p99
else if (t.lo == null) val = `< ${fmt(t.hi)}`; // bottom tier: strictly below p1
else val = `${fmt(t.lo)}${fmt(t.hi)}`;
else val = fmtRange(fmt, t.lo, t.hi);
const marker = t.c === activeClass && obs
? `<span class="ladder-mark">◀ ${fmt(obs.value)}</span>` : "";
? `<span class="ladder-mark">◀ ${bareVal(fmt, obs.value)}</span>` : "";
return `<div class="ladder-row${active}">
<span class="ladder-sw" style="background:${tierColor(t.c)}"></span>
<span class="ladder-label" style="--cat:${tierColor(t.c)}">${t.label}</span>

View file

@ -459,10 +459,12 @@ main { max-width: 1200px; margin: 0 auto; padding: 20px 24px 60px; }
.day-step:disabled { opacity: .4; cursor: default; }
/* Metric cards: one column on phones, side by side on monitors so the page
doesn't run as a single long strip of full-width ladders. */
#day-body { display: grid; grid-template-columns: 1fr; gap: 16px; align-items: start; }
@media (min-width: 1000px) { #day-body { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 3400px) { #day-body { grid-template-columns: repeat(3, 1fr); } }
doesn't run as a single long strip of full-width ladders. minmax(0, 1fr)
(not bare 1fr, whose min is the content) so one wide ladder row can't inflate
the shared column track past the viewport and push the whole page off-center. */
#day-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px; align-items: start; }
@media (min-width: 1000px) { #day-body { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 3400px) { #day-body { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.ladder-card {
background: var(--surface); border: 1px solid var(--border);
@ -502,10 +504,14 @@ main { max-width: 1200px; margin: 0 auto; padding: 20px 24px 60px; }
.ladder-sw { width: 14px; height: 14px; border-radius: 4px; }
.ladder-label { font-weight: 700; color: color-mix(in oklab, var(--cat, var(--text)), var(--text) 22%); }
.ladder-pct { color: var(--muted); font-size: 12px; }
.ladder-val { justify-self: end; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Value ranges may wrap (long unit strings like "8.1 g/m³12.9 g/m³" on narrow
phones) nowrap here would widen the row past the card and scroll the page. */
.ladder-val { justify-self: end; text-align: right; font-variant-numeric: tabular-nums; }
/* Observed marker: stays in the rightmost column but reads from its left edge,
so the arrow sits right beside the value range it points at. */
.ladder-mk { justify-self: start; min-width: 0; }
/* Markers are unit-less ("◀ 11.5") so nowrap can't push them past the card
the widest (precip, 0.25") bleeds a few px into the row padding at most. */
.ladder-mark { color: var(--accent); font-weight: 800; white-space: nowrap; font-size: 12.5px; }
@ -857,10 +863,13 @@ main { max-width: 1200px; margin: 0 auto; padding: 20px 24px 60px; }
#cal-tip .tt-type { font-size: 13.5px; margin: 1px 0 5px; padding-bottom: 5px; }
#cal-tip .tt-grid { column-gap: 8px; row-gap: 2px; }
/* Day ladder on mobile: tighten the fixed tracks but keep every column (including
the percentile range) so the ranges still line up down the ladder. */
the percentile range) so the ranges still line up down the ladder. Slack moved
from the pct/marker tracks to the value column keeps the longest range
("17.319.9 g/m³", ~88px) on one line down to ~375px screens; narrower than
that it wraps at the unit. The label track can't give ("Above Normal" 89px). */
.day-nav { width: 100%; }
.day-date { flex: 1; }
.ladder-row { grid-template-columns: 13px 90px 54px 1fr 48px; column-gap: 8px; font-size: 12.5px; }
.ladder-row { grid-template-columns: 13px 90px 46px 1fr 42px; column-gap: 8px; font-size: 12.5px; }
header { padding: 14px 16px; }
h1 { font-size: 19px; }