v1.4 - Cosmic Events
This commit is contained in:
@@ -31,6 +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';
|
||||
|
||||
const html = htm.bind(h);
|
||||
|
||||
@@ -718,6 +719,13 @@ 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);
|
||||
const todayRows = days[0]?.rows || [];
|
||||
const activeEvent = getActiveEvent(todayRows, location);
|
||||
const showBanner = activeEvent && activeEvent.id !== dismissedEventId;
|
||||
|
||||
// ─── 4. JSX RETURN ───────────────────────────────────────────────────
|
||||
// Everything below is the actual page markup, written as one big HTM
|
||||
// template. Search tips:
|
||||
@@ -758,6 +766,29 @@ 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>
|
||||
`}
|
||||
</div>
|
||||
|
||||
<div class="utci-header">
|
||||
<div>
|
||||
<h1 class="utci-title">
|
||||
@@ -780,6 +811,7 @@ export function UTCIForecast() {
|
||||
elev=${currentRow?.elev ?? 0}
|
||||
dt=${currentRow?.dt ?? new Date()}
|
||||
glob=${currentRow?.glob ?? 0}
|
||||
activeEvent=${activeEvent}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -859,6 +891,9 @@ export function UTCIForecast() {
|
||||
const dayName = i === 0 ? 'Today'
|
||||
: i === 1 ? 'Tomorrow'
|
||||
: dDate.toLocaleDateString('en-GB', { weekday: 'short', timeZone: 'UTC' });
|
||||
const utcipVals = d.rows.map(r => r.utciAdj).filter(v => isFinite(v));
|
||||
const dayHi = utcipVals.length ? Math.round(Math.max(...utcipVals)) : null;
|
||||
const dayLo = utcipVals.length ? Math.round(Math.min(...utcipVals)) : null;
|
||||
return html`
|
||||
<button
|
||||
key=${d.key}
|
||||
@@ -893,6 +928,20 @@ export function UTCIForecast() {
|
||||
<span class="utci-day-date" style=${{ color: '#5a3f24' }}>
|
||||
${dDate.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', timeZone: 'UTC' })}
|
||||
</span>
|
||||
${dayHi !== null && html`
|
||||
<span style=${{
|
||||
display: 'block',
|
||||
fontFamily: 'Fraunces, serif',
|
||||
fontStyle: 'italic',
|
||||
fontSize: '11px',
|
||||
marginTop: '3px',
|
||||
letterSpacing: 0,
|
||||
textTransform: 'none',
|
||||
}}>
|
||||
<span style=${{ color: '#c0622a', fontWeight: 600 }}>${dayHi}°</span>
|
||||
<span style=${{ opacity: 0.5 }}> / </span>
|
||||
<span style=${{ color: '#3a6080' }}>${dayLo}°</span>
|
||||
</span>`}
|
||||
</button>`;
|
||||
})}
|
||||
</div>
|
||||
@@ -1408,6 +1457,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>
|
||||
`}
|
||||
</span>
|
||||
</td>
|
||||
${visibleCols.air && html`<td style=${{ background: tempBg(r.Ta) }}>${r.Ta.toFixed(1)}</td>`}
|
||||
@@ -1538,6 +1590,55 @@ ${isPro && (activeProfile === 'custom' || FILTER_PROFILES[activeProfile].cols['a
|
||||
</span>`)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
ALMANAC — upcoming cosmic events in the next 3 months.
|
||||
Location-aware: visibility notes adjust by latitude.
|
||||
-->
|
||||
${(() => {
|
||||
const upcoming = getUpcomingEvents(location, 90);
|
||||
const formatPeak = (iso) => {
|
||||
const d = new Date(iso + 'T00:00Z');
|
||||
return d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' });
|
||||
};
|
||||
const countdownLabel = (days) => {
|
||||
if (days === 0) return 'Tonight!';
|
||||
if (days === 1) return 'Tomorrow';
|
||||
if (days < 7) return `In ${days} days`;
|
||||
if (days < 14) return 'Next week';
|
||||
if (days < 60) return `In ${Math.round(days / 7)} weeks`;
|
||||
return `In ${Math.round(days / 30)} months`;
|
||||
};
|
||||
return html`
|
||||
<div class="almanac-panel">
|
||||
<div class="almanac-header">
|
||||
<span class="almanac-title">🔭 What's Coming</span>
|
||||
<span class="almanac-subtitle">Cosmic events · next 90 days · ${location.name}</span>
|
||||
</div>
|
||||
<div class="almanac-list">
|
||||
${upcoming.length === 0
|
||||
? html`<div class="almanac-empty">No major cosmic events in the next 90 days — clear skies ahead.</div>`
|
||||
: upcoming.map(ev => html`
|
||||
<div key=${ev.id} class="almanac-entry">
|
||||
<div class="almanac-entry-accent" style=${{ background: ev.color === '#fdf8ee' ? '#c8922a' : ev.color }} />
|
||||
<div class="almanac-entry-icon">${ev.emoji}</div>
|
||||
<div class="almanac-entry-body">
|
||||
<div class="almanac-entry-head">
|
||||
<span class="almanac-entry-title">${ev.title}</span>
|
||||
<span class="almanac-entry-date">${formatPeak(ev.peak)}</span>
|
||||
<span class="almanac-entry-countdown">${countdownLabel(ev.daysUntil)}</span>
|
||||
</div>
|
||||
<div class="almanac-entry-desc">${ev.desc}</div>
|
||||
${ev.visibilityNote && html`
|
||||
<span class="almanac-entry-visibility">📍 ${ev.visibilityNote}</span>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
`)
|
||||
}
|
||||
</div>
|
||||
</div>`;
|
||||
})()}
|
||||
</>`}
|
||||
|
||||
<div class="utci-about">
|
||||
|
||||
Reference in New Issue
Block a user