feat grey out night periods on weather forecast chart

Adds a nightShadePlugin that draws subtle grey rectangles over 21:00–06:00 periods, making day/night boundaries immediately visible on the 48-hour forecast chart.

Commit
0218bfcf73af80fec7bd54eaa79e603ab8719054
Author
GPT-5 medium <codex@openai.com>
Author date
Committer
GPT-5 medium <codex@openai.com>
Committer date
roles/dashboard/public/js/dashboard/weather-forecast.js
index acb517dc..8163c2eb 100644..100644
@@ -61,6 +61,57 @@
61 61 summaryEl.textContent = `${currentSentence} ${trendSentence}`;
62 62 }
63 63
64 Added: // Chart.js plugin to shade night periods (21:00–06:00)
65 Added: const nightShadePlugin = {
66 Added: id: "nightShade",
67 Added: beforeDraw(chart) {
68 Added: const { ctx, chartArea, scales } = chart;
69 Added: const xScale = scales.x;
70 Added: const timestamps = chart._weatherTimestamps;
71 Added: if (!xScale || !timestamps || !timestamps.length) return;
72 Added:
73 Added: ctx.save();
74 Added: ctx.fillStyle = "rgba(100, 116, 139, 0.08)";
75 Added:
76 Added: let inNight = false;
77 Added: let nightStart = 0;
78 Added:
79 Added: for (let i = 0; i <= timestamps.length; i++) {
80 Added: const hour = i < timestamps.length ? new Date(timestamps[i]).getHours() : -1;
81 Added: const isNight = hour >= 21 || (hour >= 0 && hour < 6);
82 Added:
83 Added: if (isNight && !inNight) {
84 Added: nightStart = i;
85 Added: inNight = true;
86 Added: } else if (!isNight && inNight) {
87 Added: // Draw the night block
88 Added: const x1 = xScale.getPixelForValue(nightStart);
89 Added: const x2 = xScale.getPixelForValue(i);
90 Added: ctx.fillRect(
91 Added: Math.max(x1, chartArea.left),
92 Added: chartArea.top,
93 Added: Math.min(x2, chartArea.right) - Math.max(x1, chartArea.left),
94 Added: chartArea.bottom - chartArea.top
95 Added: );
96 Added: inNight = false;
97 Added: }
98 Added: }
99 Added:
100 Added: // If still in night at the end
101 Added: if (inNight) {
102 Added: const x1 = xScale.getPixelForValue(nightStart);
103 Added: ctx.fillRect(
104 Added: Math.max(x1, chartArea.left),
105 Added: chartArea.top,
106 Added: chartArea.right - Math.max(x1, chartArea.left),
107 Added: chartArea.bottom - chartArea.top
108 Added: );
109 Added: }
110 Added:
111 Added: ctx.restore();
112 Added: }
113 Added: };
114 Added:
64 115 // Chart.js plugin to draw a vertical "now" line
65 116 const nowLinePlugin = {
66 117 id: "nowLine",
@@ -212,7 +263,7 @@
212 263 }
213 264 }
214 265 },
215 Removed: plugins: [nowLinePlugin]
266 Added: plugins: [nightShadePlugin, nowLinePlugin]
216 267 });
217 268 // Store timestamps for the now-line plugin
218 269 chart._weatherTimestamps = timestamps;