diff --git a/frontend/src/routes/weather/+page.svelte b/frontend/src/routes/weather/+page.svelte index 0dfc063..497ae0d 100644 --- a/frontend/src/routes/weather/+page.svelte +++ b/frontend/src/routes/weather/+page.svelte @@ -5,6 +5,30 @@ let { data }: { data: PageData } = $props(); const weather = $derived(data.weather); const unitLabel = $derived(weather.unit === 'celsius' ? 'C' : 'F'); + + // The hourly strip covers the next 24h starting from "now" (see weather/client.ts), which + // almost always crosses a day boundary partway through — grouping by calendar day and + // labeling each group is what actually answers "which day is this hour in", rather than + // leaving it to be inferred from the hour-of-day alone (ambiguous for anything after + // midnight, and easy to misread near the boundary either way). + function dayLabel(d: Date): string { + const startOfDay = (x: Date) => new Date(x.getFullYear(), x.getMonth(), x.getDate()).getTime(); + const diffDays = Math.round((startOfDay(d) - startOfDay(new Date())) / 86_400_000); + if (diffDays === 0) return 'Today'; + if (diffDays === 1) return 'Tomorrow'; + return d.toLocaleDateString([], { weekday: 'long' }); + } + + const hourlyGroups = $derived.by(() => { + const groups: { label: string; hours: typeof weather.hourly }[] = []; + for (const hour of weather.hourly) { + const label = dayLabel(new Date(hour.time)); + const last = groups[groups.length - 1]; + if (last && last.label === label) last.hours.push(hour); + else groups.push({ label, hours: [hour] }); + } + return groups; + });
@@ -75,15 +99,20 @@
Hourly -
- {#each weather.hourly as hour (hour.time)} -
- {new Date(hour.time).toLocaleTimeString([], { hour: 'numeric' })} - {hour.icon} - {Math.round(hour.temp)}° + {#each hourlyGroups as group (group.label + group.hours[0]?.time)} +
+ {group.label} +
+ {#each group.hours as hour (hour.time)} +
+ {new Date(hour.time).toLocaleTimeString([], { hour: 'numeric' })} + {hour.icon} + {Math.round(hour.temp)}° +
+ {/each}
- {/each} -
+
+ {/each}
@@ -228,6 +257,19 @@ color: var(--text-secondary); margin: 4px 0 0; } + .hourly-day { + margin-bottom: 18px; + } + .hourly-day:last-child { + margin-bottom: 0; + } + .hourly-day-label { + display: block; + font-size: 12px; + font-weight: 500; + color: var(--text-muted); + margin-bottom: 10px; + } .hourly-strip { display: grid; grid-template-columns: repeat(12, 1fr);