From ede982f61026a9c5dee0dc5f1829b830e0a33601 Mon Sep 17 00:00:00 2001 From: Emi Griffith Date: Fri, 10 Jul 2026 18:27:45 -0700 Subject: [PATCH 1/2] Weekly page: no-rain visuals adopt the dry-streak look MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the Weekly page, no-rain (0") days now warm with the dry-streak ramp (tan→red, deepening to a 14-day cap) instead of a flat blue/tan, matching the Dry chart and calendar's dry-period language: - Precip trend chart: no-rain day dots colored by drynessColor(dsr). - "Daily, graded" timeline strip: Rain-row dry cells tinted by dsr via a new optional color override on rdCell. Rain days keep their intensity-tier colors; unknown streaks fall back to the prior flat color, so nothing regresses. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_019VP23wKmjS2ozk1g5a9g1Z --- static/app.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/static/app.js b/static/app.js index e27d13c..08703a3 100644 --- a/static/app.js +++ b/static/app.js @@ -169,11 +169,11 @@ function render(data) { // One tinted value cell: background + value colored by the day's grade tier for // that metric (the same diverging scale as everywhere else). Carries the date so // tapping any cell opens that day's full breakdown. -function rdCell(g, fmt, date, isFc, isToday) { +function rdCell(g, fmt, date, isFc, isToday, override) { const dd = date ? ` data-date="${date}"` : ""; const cls = "rd-c" + (isFc ? " rd-fc" : isToday ? " rd-today" : ""); if (!g) return ``; - const col = TIER_COLORS[g.class] || ""; + const col = override || TIER_COLORS[g.class] || ""; return `${fmt(g.value)}`; } @@ -197,7 +197,14 @@ function daysTable(rows, todayDate) { }).join(""); const body = RD_METRICS.map(([key, label, fmt]) => `
${label}
` + - rows.map((d) => rdCell(d[key], fmt, d.date, isFc(d), d.date === todayDate)).join("") + rows.map((d) => { + // No-rain days in the Rain row take the dry-streak warmth (tan→red, deepening + // with the streak) — matching the Dry chart and calendar — instead of one flat + // "dry" tint. Falls back to the flat dry color when the streak is unknown. + const dry = key === "precip" && d.precip && d.precip.class === "dry"; + const override = dry ? drynessColor(d.dsr) : null; + return rdCell(d[key], fmt, d.date, isFc(d), d.date === todayDate, override); + }).join("") ).join(""); return `
${header}${body}
`; } @@ -578,7 +585,9 @@ function precipChart(days, n, xFor, C) { days, n, xFor, C, key: "precip", color: C.precip, fan: RAIN_FAN, hasNormals: true, baseZero: true, getVal: (d) => (d.precip ? d.precip.value : null), getPct: (d) => (d.precip ? d.precip.percentile : null), - dotColor: (d) => (d.precip && d.precip.value > 0 ? (TIER_COLORS[d.precip.class] || C.precip) : C.precip), + // Rain days take their intensity-tier color; no-rain days warm with the dry + // streak (tan→red) so a dry spell reads as dry, matching the Dry chart. + dotColor: (d) => (d.precip && d.precip.value > 0 ? (TIER_COLORS[d.precip.class] || C.precip) : (drynessColor(d.dsr) || C.precip)), fmtAxis: (v) => `${v.toFixed(2)}"`, fmtLabel: (v) => `${v.toFixed(2)}"`, labelOn: (v) => v > 0, // only label rain days; dry days rest on the baseline From 27a6508b7280d1a58a5f47cc76ace1e1b4e9ceea Mon Sep 17 00:00:00 2001 From: Emi Griffith Date: Fri, 10 Jul 2026 18:38:34 -0700 Subject: [PATCH 2/2] Calendar totals strip: wider bars, 0.1%-precision small shares (#5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Widen the per-category deviation bars (3px→10px) so each reads clearly, and print sub-1% shares as e.g. "0.4%" instead of "<1%". Claude-Session: https://claude.ai/code/session_019VP23wKmjS2ozk1g5a9g1Z Co-authored-by: Claude Opus 4.8 --- static/calendar.js | 2 +- static/style.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/static/calendar.js b/static/calendar.js index a79fe4a..cad7ae6 100644 --- a/static/calendar.js +++ b/static/calendar.js @@ -678,7 +678,7 @@ function renderTotals(visible) { } const total = buckets.reduce((n, b) => n + b[2], 0); - const pctText = (n) => { const r = Math.round(100 * n / total); return n > 0 && r === 0 ? "<1%" : `${r}%`; }; + const pctText = (n) => { const p = 100 * n / total, r = Math.round(p); return n > 0 && r === 0 ? `${p.toFixed(1)}%` : `${r}%`; }; const head = wActive ? `${days.length.toLocaleString()} of ${visible.length.toLocaleString()} shown days match the weather filter — by ${title}:` : `${visible.length.toLocaleString()} days shown — by ${title}:`; diff --git a/static/style.css b/static/style.css index d3baed5..02d1ac0 100644 --- a/static/style.css +++ b/static/style.css @@ -754,7 +754,7 @@ main { max-width: 1200px; margin: 0 auto; padding: 20px 24px 60px; } .ct-num-zero { font-weight: 600; opacity: .38; } .ct-line { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); - width: 3px; border-radius: 2px 2px 0 0; background: var(--c); + width: 10px; border-radius: 3px 3px 0 0; background: var(--c); } .ct-col-median .ct-num { color: var(--muted); font-weight: 800; } .ct-mid {