day swap animation

This commit is contained in:
Vincent van der Wal
2026-08-01 14:37:25 +02:00
parent fe961a27ae
commit 9806396476
6 changed files with 646 additions and 18 deletions
+36 -2
View File
@@ -208,6 +208,17 @@ export interface WeekDailyData {
windspeed_10m_max: number[];
windgusts_10m_max: number[];
winddirection_10m_dominant: number[];
// Only the live forecast carries these; the archive-backed views reuse this
// shape without them, so they stay optional.
/** Seconds between sunrise and sunset. */
daylight_duration?: number[];
uv_index_max?: number[];
precipitation_probability_max?: number[];
/** Unix seconds; 0 on the days the moon doesn't rise / set at all. */
moonrise?: number[];
moonset?: number[];
/** 0 and 1 are new moon, 0.5 is full moon. */
moon_phase?: number[];
}
export interface WeekForecastResult {
@@ -361,7 +372,13 @@ const WEEK_DAILY_VARS = [
'precipitation_sum',
'wind_speed_10m_max',
'wind_gusts_10m_max',
'wind_direction_10m_dominant'
'wind_direction_10m_dominant',
'daylight_duration',
'uv_index_max',
'precipitation_probability_max',
'moonrise',
'moonset',
'moon_phase'
] as const;
/**
@@ -463,6 +480,17 @@ export async function fetchWeekForecast(params: WeekForecastParams): Promise<Wee
const sunriseVar = dailyBlock.variables(3)!;
const sunsetVar = dailyBlock.variables(4)!;
// Optional tail variables: a model that doesn't carry them yields fewer
// entries, so read them defensively instead of asserting.
const dailyAt = (i: number): number[] => {
const v = dailyBlock.variables(i);
return v ? getValues(v) : [];
};
const dailyInt64At = (i: number): number[] => {
const v = dailyBlock.variables(i);
return v ? getInt64Values(v) : [];
};
const daily: WeekDailyData = {
weather_code: getValues(dailyBlock.variables(0)!),
temperature_2m_max: getValues(dailyBlock.variables(1)!),
@@ -473,7 +501,13 @@ export async function fetchWeekForecast(params: WeekForecastParams): Promise<Wee
precipitation_sum: getValues(dailyBlock.variables(6)!),
windspeed_10m_max: getValues(dailyBlock.variables(7)!),
windgusts_10m_max: getValues(dailyBlock.variables(8)!),
winddirection_10m_dominant: getValues(dailyBlock.variables(9)!)
winddirection_10m_dominant: getValues(dailyBlock.variables(9)!),
daylight_duration: dailyAt(10),
uv_index_max: dailyAt(11),
precipitation_probability_max: dailyAt(12),
moonrise: dailyInt64At(13),
moonset: dailyInt64At(14),
moon_phase: dailyAt(15)
};
const daylightBands = buildDaylightBands(daily.sunrise, daily.sunset);
+31
View File
@@ -0,0 +1,31 @@
/**
* Fades a block back in whenever the value passed to it changes - used to make
* a day switch visible in the hourly table, the written forecast and the
* meteograms.
*
* It animates the existing node instead of remounting it: the meteograms hold
* canvas charts with their own zoom state, and rebuilding those on every day
* change would be both expensive and lossy.
*/
export function daySwap(node: HTMLElement, key: unknown) {
let current = key;
const play = () => {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
node.animate(
[
{ opacity: 0.15, transform: 'translateY(4px)' },
{ opacity: 1, transform: 'translateY(0)' }
],
{ duration: 280, easing: 'cubic-bezier(0.22, 0.61, 0.36, 1)' }
);
};
return {
update(next: unknown) {
if (next === current) return;
current = next;
play();
}
};
}