3.8.3
Stop event banner size shifting
This commit is contained in:
+20
-4
@@ -257,18 +257,34 @@ body {
|
|||||||
background: rgba(0, 0, 0, 0.55);
|
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 {
|
.event-note-slide {
|
||||||
|
grid-area: 1 / 1; /* stack all slides on top of each other */
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 4px 10px;
|
gap: 4px 10px;
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
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;
|
transition: opacity 0.35s ease;
|
||||||
}
|
}
|
||||||
.event-note-slide.is-fading {
|
.event-note-slide.is-active {
|
||||||
opacity: 0;
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
user-select: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-note-emoji {
|
.event-note-emoji {
|
||||||
|
|||||||
+32
-14
@@ -692,9 +692,18 @@ export function UTCIForecast() {
|
|||||||
const fmtDate = (iso) => iso
|
const fmtDate = (iso) => iso
|
||||||
? new Date(iso + 'T00:00Z').toLocaleDateString('en-GB', { day: 'numeric', month: 'short', timeZone: 'UTC' })
|
? new Date(iso + 'T00:00Z').toLocaleDateString('en-GB', { day: 'numeric', month: 'short', timeZone: 'UTC' })
|
||||||
: null;
|
: null;
|
||||||
const startFmt = fmtDate(ev.start);
|
// Date line for one event — computed per slide because every event is
|
||||||
const peakFmt = fmtDate(ev.peak);
|
// rendered (see the stack below), not just the visible one.
|
||||||
const endFmt = fmtDate(ev.end);
|
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
|
// Warning banners take their background from the day tabs' weather
|
||||||
// palette (ev.tint, set in events/weather-checks.js) so a heat alert
|
// 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
|
// 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(',')})`,
|
borderColor: `rgb(${ev.tint.map(c => Math.round(c * 0.72)).join(',')})`,
|
||||||
};
|
};
|
||||||
})() : undefined;
|
})() : 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`
|
return html`
|
||||||
<div class=${`event-note${isPriority ? ' is-priority' : ''}${noteTintStyle ? ' is-tinted' : ''}`} style=${noteTintStyle}>
|
<div class=${`event-note${isPriority ? ' is-priority' : ''}${noteTintStyle ? ' is-tinted' : ''}`} style=${noteTintStyle}>
|
||||||
<span class="event-note-accent" style=${{ background: ev.color === '#fdf8ee' ? '#c8922a' : ev.color }} />
|
<span class="event-note-accent" style=${{ background: ev.color === '#fdf8ee' ? '#c8922a' : ev.color }} />
|
||||||
<div class=${`event-note-slide${noteFading ? ' is-fading' : ''}`}>
|
${/* Every slide is rendered, all stacked in one CSS grid cell, so the
|
||||||
<span class="event-note-emoji">${ev.emoji}</span>
|
strip is always as tall as the LONGEST event's copy. Rotating to
|
||||||
<span class="event-note-title">${ev.title}</span>
|
a two-line message then back no longer resizes the banner and
|
||||||
<span class="event-note-msg">${ev.message}</span>
|
shunts the page up and down. Only the active slide is visible;
|
||||||
|
the rest sit at opacity 0 and are hidden from assistive tech. */''}
|
||||||
|
<div class="event-note-stack">
|
||||||
|
${activeEvents.map((e, i) => {
|
||||||
|
const dateLine = dateLineFor(e);
|
||||||
|
const isActive = i === noteIndex && !noteFading;
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
key=${e.id || i}
|
||||||
|
class=${`event-note-slide${isActive ? ' is-active' : ''}`}
|
||||||
|
aria-hidden=${isActive ? undefined : 'true'}
|
||||||
|
>
|
||||||
|
<span class="event-note-emoji">${e.emoji}</span>
|
||||||
|
<span class="event-note-title">${e.title}</span>
|
||||||
|
<span class="event-note-msg">${e.message}</span>
|
||||||
${dateLine && html`<span class="event-note-dates">${dateLine}</span>`}
|
${dateLine && html`<span class="event-note-dates">${dateLine}</span>`}
|
||||||
|
</div>`;
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
${activeEvents.length > 1 && html`
|
${activeEvents.length > 1 && html`
|
||||||
<div class="event-note-dots">
|
<div class="event-note-dots">
|
||||||
|
|||||||
Reference in New Issue
Block a user