From ec211314e0207b7045e120aec680ca40c3baed57 Mon Sep 17 00:00:00 2001 From: fraxle Date: Sun, 2 Aug 2026 17:06:31 +0100 Subject: [PATCH] 3.8.3 Stop event banner size shifting --- assets/css/base.css | 24 +++++++++++++++++++---- assets/js/app.js | 48 +++++++++++++++++++++++++++++++-------------- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/assets/css/base.css b/assets/css/base.css index e084285..fb23db2 100644 --- a/assets/css/base.css +++ b/assets/css/base.css @@ -257,18 +257,34 @@ body { background: rgba(0, 0, 0, 0.55); } +/* Slide stack. Every active event is rendered and every slide is placed in the + same single grid cell, so the strip's height is set by the TALLEST slide and + stays put as the rotation cycles — a one-line event followed by a message + that wraps to two lines no longer grows the banner and shunts the page. */ +.event-note-stack { + display: grid; + grid-template-columns: minmax(0, 1fr); + align-items: start; + flex: 1; + min-width: 0; +} + .event-note-slide { + grid-area: 1 / 1; /* stack all slides on top of each other */ display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; - flex: 1; min-width: 0; - opacity: 1; + opacity: 0; /* only .is-active is visible */ + pointer-events: none; + user-select: none; /* keeps hidden copy out of a drag-selection */ transition: opacity 0.35s ease; } -.event-note-slide.is-fading { - opacity: 0; +.event-note-slide.is-active { + opacity: 1; + pointer-events: auto; + user-select: auto; } .event-note-emoji { diff --git a/assets/js/app.js b/assets/js/app.js index 330b1a3..ad24c69 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -692,9 +692,18 @@ export function UTCIForecast() { const fmtDate = (iso) => iso ? new Date(iso + 'T00:00Z').toLocaleDateString('en-GB', { day: 'numeric', month: 'short', timeZone: 'UTC' }) : null; - const startFmt = fmtDate(ev.start); - const peakFmt = fmtDate(ev.peak); - const endFmt = fmtDate(ev.end); + // Date line for one event — computed per slide because every event is + // rendered (see the stack below), not just the visible one. + const dateLineFor = (e) => { + const startFmt = fmtDate(e.start); + const peakFmt = fmtDate(e.peak); + const endFmt = fmtDate(e.end); + if (!startFmt || !endFmt) return null; + if (startFmt === endFmt) return peakFmt ? `Peak ${peakFmt}` : startFmt; + return (peakFmt && peakFmt !== startFmt && peakFmt !== endFmt) + ? `${startFmt} – ${endFmt} · Peak ${peakFmt}` + : `${startFmt} – ${endFmt}`; + }; // Warning banners take their background from the day tabs' weather // palette (ev.tint, set in events/weather-checks.js) so a heat alert // reads the same orange as a hot day tab and a rain alert the same @@ -709,21 +718,30 @@ export function UTCIForecast() { borderColor: `rgb(${ev.tint.map(c => Math.round(c * 0.72)).join(',')})`, }; })() : undefined; - const dateLine = (startFmt && endFmt) - ? (startFmt === endFmt - ? (peakFmt ? `Peak ${peakFmt}` : startFmt) - : (peakFmt && peakFmt !== startFmt && peakFmt !== endFmt - ? `${startFmt} – ${endFmt} · Peak ${peakFmt}` - : `${startFmt} – ${endFmt}`)) - : null; return html`
-
- ${ev.emoji} - ${ev.title} - ${ev.message} - ${dateLine && html`${dateLine}`} + ${/* Every slide is rendered, all stacked in one CSS grid cell, so the + strip is always as tall as the LONGEST event's copy. Rotating to + a two-line message then back no longer resizes the banner and + shunts the page up and down. Only the active slide is visible; + the rest sit at opacity 0 and are hidden from assistive tech. */''} +
+ ${activeEvents.map((e, i) => { + const dateLine = dateLineFor(e); + const isActive = i === noteIndex && !noteFading; + return html` +
+ ${e.emoji} + ${e.title} + ${e.message} + ${dateLine && html`${dateLine}`} +
`; + })}
${activeEvents.length > 1 && html`