v1.41 - Add 2027 Dates and Fix Cosmic Events
This commit is contained in:
+83
-31
@@ -31,7 +31,7 @@ import {
|
||||
cloudCategory, confidenceBand, moonGlyph,
|
||||
} from './utils.js';
|
||||
import { SkyScope, WindVane, CloudIcon, ScopeReticle, PrecipIcon, CustomSelect, VentPill } from './components.js';
|
||||
import { getActiveEvent, shouldShowCellTag, getUpcomingEvents } from './events.js';
|
||||
import { getActiveEvents, getLensEvent, getCellTagEvents, getUpcomingEvents } from './events.js';
|
||||
|
||||
const html = htm.bind(h);
|
||||
|
||||
@@ -719,12 +719,30 @@ export function UTCIForecast() {
|
||||
? utciCategory(currentRow.utciAdj)
|
||||
: { bg: '#4a4228', fg: '#ede4cc', label: 'No data' };
|
||||
|
||||
// ─── COSMIC/WEATHER EVENT ─────────────────────────────────────────────
|
||||
// Compute once per render. todayRows = the rows for today (day 0).
|
||||
const [dismissedEventId, setDismissedEventId] = useState(null);
|
||||
// ─── COSMIC/WEATHER EVENTS ────────────────────────────────────────────
|
||||
// activeEvents — today's events → drives banner & lens overlay.
|
||||
// selectedDayEvents — viewed day's events → drives cell tags.
|
||||
const [dismissedEventIds, setDismissedEventIds] = useState([]);
|
||||
const [bannerIndex, setBannerIndex] = useState(0);
|
||||
const todayRows = days[0]?.rows || [];
|
||||
const activeEvent = getActiveEvent(todayRows, location);
|
||||
const showBanner = activeEvent && activeEvent.id !== dismissedEventId;
|
||||
const activeEvents = getActiveEvents(todayRows, location)
|
||||
.filter(ev => !dismissedEventIds.includes(ev.id));
|
||||
const lensEvent = getLensEvent(activeEvents);
|
||||
const selectedDayEvents = getActiveEvents(visible, location);
|
||||
|
||||
// Auto-advance banner slideshow every 10 seconds when multiple events
|
||||
useEffect(() => {
|
||||
if (activeEvents.length <= 1) { setBannerIndex(0); return; }
|
||||
const id = setInterval(() => {
|
||||
setBannerIndex(i => (i + 1) % activeEvents.length);
|
||||
}, 10000);
|
||||
return () => clearInterval(id);
|
||||
}, [activeEvents.length, activeEvents.map(e => e.id).join(',')]);
|
||||
|
||||
// Keep index in bounds if events change
|
||||
useEffect(() => {
|
||||
if (bannerIndex >= activeEvents.length) setBannerIndex(0);
|
||||
}, [activeEvents.length]);
|
||||
|
||||
// ─── 4. JSX RETURN ───────────────────────────────────────────────────
|
||||
// Everything below is the actual page markup, written as one big HTM
|
||||
@@ -766,27 +784,61 @@ export function UTCIForecast() {
|
||||
</nav>
|
||||
<main class="utci-shell">
|
||||
|
||||
<!-- ── EVENT BANNER ── slides down when an event is active ── -->
|
||||
<div class=${`event-banner-wrap${showBanner ? ' visible' : ''}`}>
|
||||
${showBanner && html`
|
||||
<div class="event-banner" style=${{
|
||||
background: activeEvent.color,
|
||||
color: activeEvent.textColor,
|
||||
}}>
|
||||
<span class="event-banner-emoji">${activeEvent.emoji}</span>
|
||||
<div class="event-banner-body">
|
||||
<div class="event-banner-title">${activeEvent.title}</div>
|
||||
<div class="event-banner-msg">${activeEvent.message}</div>
|
||||
</div>
|
||||
<button
|
||||
class="event-banner-dismiss"
|
||||
style=${{ color: activeEvent.textColor }}
|
||||
onClick=${() => setDismissedEventId(activeEvent.id)}
|
||||
aria-label="Dismiss"
|
||||
title="Dismiss"
|
||||
>✕</button>
|
||||
</div>
|
||||
`}
|
||||
<!-- ── EVENT BANNER ── slideshow when multiple events active ── -->
|
||||
<div class=${`event-banner-wrap${activeEvents.length > 0 ? ' visible' : ''}`}>
|
||||
${activeEvents.length > 0 && (() => {
|
||||
const ev = activeEvents[bannerIndex] || activeEvents[0];
|
||||
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);
|
||||
// Only show date line if the event has start/end (cosmic events do; weather events don't)
|
||||
const showDates = startFmt && endFmt;
|
||||
const dateLine = showDates
|
||||
? (startFmt === endFmt
|
||||
? peakFmt ? `Peak: ${peakFmt}` : `Date: ${startFmt}`
|
||||
: peakFmt && peakFmt !== startFmt && peakFmt !== endFmt
|
||||
? `Active ${startFmt} – ${endFmt} · Peak: ${peakFmt}`
|
||||
: `Active ${startFmt} – ${endFmt}`)
|
||||
: null;
|
||||
return html`
|
||||
<div class="event-banner"
|
||||
style=${{ background: ev.color, color: ev.textColor }}
|
||||
onMouseEnter=${() => {}}
|
||||
>
|
||||
<span class="event-banner-emoji">${ev.emoji}</span>
|
||||
<div class="event-banner-body">
|
||||
<div class="event-banner-title">${ev.title}</div>
|
||||
<div class="event-banner-msg">${ev.message}</div>
|
||||
${dateLine && html`
|
||||
<div class="event-banner-dates" style=${{ opacity: 0.75, fontSize: '11px', fontFamily: 'JetBrains Mono, monospace', letterSpacing: '0.08em', textTransform: 'uppercase', marginTop: '5px' }}>
|
||||
${dateLine}
|
||||
</div>
|
||||
`}
|
||||
${activeEvents.length > 1 && html`
|
||||
<div class="event-banner-dots">
|
||||
${activeEvents.map((_, i) => html`
|
||||
<span
|
||||
key=${i}
|
||||
class=${`event-banner-dot${i === bannerIndex ? ' active' : ''}`}
|
||||
onClick=${() => setBannerIndex(i)}
|
||||
style=${{ background: ev.textColor }}
|
||||
/>
|
||||
`)}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
<button
|
||||
class="event-banner-dismiss"
|
||||
style=${{ color: ev.textColor }}
|
||||
onClick=${() => setDismissedEventIds(ids => [...ids, ev.id])}
|
||||
aria-label="Dismiss"
|
||||
title="Dismiss this event"
|
||||
>✕</button>
|
||||
</div>`;
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<div class="utci-header">
|
||||
@@ -811,7 +863,7 @@ export function UTCIForecast() {
|
||||
elev=${currentRow?.elev ?? 0}
|
||||
dt=${currentRow?.dt ?? new Date()}
|
||||
glob=${currentRow?.glob ?? 0}
|
||||
activeEvent=${activeEvent}
|
||||
activeEvent=${lensEvent}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1457,9 +1509,9 @@ ${isPro && (activeProfile === 'custom' || FILTER_PROFILES[activeProfile].cols['a
|
||||
<span style=${{ display: 'inline-flex', alignItems: 'center', gap: '7px', verticalAlign: 'middle' }}>
|
||||
<${SkyScope} elev=${r.elev} dt=${r.dt} glob=${r.glob} size=${30} />
|
||||
<span>${localHHMM}</span>
|
||||
${shouldShowCellTag(activeEvent, r) && html`
|
||||
<span class="event-cell-tag" title=${activeEvent.title}>${activeEvent.emoji}</span>
|
||||
`}
|
||||
${getCellTagEvents(selectedDayEvents, r).map(ev => html`
|
||||
<span key=${ev.id} class="event-cell-tag" title=${ev.title}>${ev.emoji}</span>
|
||||
`)}
|
||||
</span>
|
||||
</td>
|
||||
${visibleCols.air && html`<td style=${{ background: tempBg(r.Ta) }}>${r.Ta.toFixed(1)}</td>`}
|
||||
|
||||
Reference in New Issue
Block a user