feat: uuroverzicht per dag (temperatuur, regen, wind)

Tik op een dag in de verwachtingsstrook voor een uuroverzicht van
temperatuur, regen en wind (richting + snelheid), om de 2 uur, 12
punten per dag. Sluit na 20 seconden vanzelf of bij een tik op de
overlay zelf.

Backend: Open-Meteo levert hourly-data in dezelfde call als daily
(geen extra verzoek, dezelfde 60min-cache). Nieuwe HourlyForecastMapper
groepeert per kalenderdatum en bemonstert om het andere uur, robuust
voor de zomertijddag (23/25 uur). OpenMeteoClient::dailyForecast()
hernoemd naar forecast(), geeft nu {daily, hourly} terug. Elke dag in
de forecast-array krijgt een hourly-lijst.

Frontend: temperatuurlijn hergebruikt sparklinePath (nu ook als
sparklinePoints voor exacte uitlijning met de 12-koloms grid), regen
als kleine staafjes, windpijl (nieuw icoon) wijst waarheen de wind
waait. 20s-balk via CSS transition met reflow-restart. Servicewerker
naar v5.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-13 10:18:11 +02:00
co-authored by Claude Sonnet 5
parent 90e979f562
commit 8e90eb1129
20 changed files with 1420 additions and 62 deletions
+45 -1
View File
@@ -62,7 +62,7 @@ html, body {
.warm { color: var(--warm); }
.muted { color: var(--muted); }
.ic { fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; width: 100%; height: 100%; display: block; }
.ic, .wind-ic { fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; width: 100%; height: 100%; display: block; }
.i-sun { stroke: var(--sun); }
.i-drop { stroke: var(--rain); }
.i-cloud { fill: var(--iconfill); }
@@ -162,6 +162,50 @@ html, body {
}
.banner small { display: block; margin-top: 16px; font-size: 20px; color: var(--muted); }
/* Uuroverzicht per dag: tik op een dagkolom om te openen, tik erop of wacht 20s om te sluiten. */
.hour-overlay {
position: absolute;
left: 44px;
right: 44px;
top: 40px;
bottom: 40px;
display: flex;
flex-direction: column;
gap: 22px;
padding: 26px 0 22px;
background: var(--bg);
border: 1px solid var(--line);
border-radius: 18px;
box-shadow: 0 12px 32px rgba(27, 42, 53, .12);
z-index: 5;
}
.hour-overlay[hidden] { display: none; }
.ho-bar { margin: 0 30px; height: 4px; border-radius: 2px; background: var(--line); overflow: hidden; }
.ho-bar i { display: block; width: 100%; height: 100%; background: var(--rain); transform-origin: left; }
.ho-head { margin: 0 30px; display: flex; justify-content: space-between; align-items: baseline; }
.ho-date { font-size: 22px; font-weight: 500; }
.ho-hint { font-size: 16px; color: var(--muted); }
.ho-grid { display: grid; grid-template-columns: repeat(12, 1fr); text-align: center; }
.ho-temp { display: flex; flex-direction: column; gap: 6px; }
#ho-temp-nums .num { font-size: 22px; font-weight: 500; }
.ho-temp-svg { display: block; width: 100%; height: 64px; }
.ho-temp-line { fill: none; stroke: var(--ink); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.ho-rain .ho-rbar { display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 6px; height: 66px; }
.ho-rbar i { display: block; width: 14px; border-radius: 3px 3px 0 0; background: var(--rain); opacity: .55; }
.ho-rbar .num { font-size: 14px; color: var(--muted); }
.ho-wind .ho-wcell { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.ho-warrow { display: flex; align-items: center; justify-content: center; width: 26px; height: 26px; color: var(--muted); }
.ho-warrow.off { font-size: 18px; }
.ho-wcell .num { font-size: 15px; color: var(--muted); }
.ho-hours span { font-size: 16px; color: var(--muted); }
#rotate {
display: none;
position: fixed;