130 lines
4.8 KiB
JavaScript
130 lines
4.8 KiB
JavaScript
// ------------------------------------------------------------------------
|
|
// events.js - Cosmic & weather event engine for SunScope.
|
|
//
|
|
// This is the public-facing barrel module. It composes the smaller files
|
|
// under ./events/ and exposes the same API as before, so callers
|
|
// (app.js, components.js) keep working without changes.
|
|
//
|
|
// Returns the "active event" (or null) based on:
|
|
// 1. PROMO_OVERRIDE - manually set a message for promotions etc.
|
|
// 2. Hardcoded cosmic calendar (eclipses, meteor showers, alignments)
|
|
// 3. Weather-derived events (stargazing, sunset, heat spike, storm)
|
|
//
|
|
// To set a promo banner, edit PROMO_OVERRIDE below.
|
|
// Set it back to null when done.
|
|
//
|
|
// Each event object:
|
|
// { id, emoji, title, message, color, textColor, type }
|
|
//
|
|
// type: 'cosmic' | 'weather' | 'promo'
|
|
//
|
|
// Where to find things:
|
|
// Cosmic calendar data ........ ./events/cosmic-calendar.js
|
|
// Almanac calendar data ....... ./events/almanac-calendar.js
|
|
// Weather check functions ..... ./events/weather-checks.js
|
|
// Dynamic message helper ...... ./events/dynamic-message.js
|
|
// Lens overlay SVG renderer ... ./events/lens-overlay.js
|
|
// ------------------------------------------------------------------------
|
|
|
|
import { COSMIC_CALENDAR } from './events/cosmic-calendar.js';
|
|
import {
|
|
checkStargazing,
|
|
checkSunset,
|
|
checkHeatSpike,
|
|
checkStorm,
|
|
checkFrost,
|
|
} from './events/weather-checks.js';
|
|
import { dynamicCosmicMessage } from './events/dynamic-message.js';
|
|
|
|
// Re-export so callers that imported these from events.js keep working.
|
|
export { getUpcomingEvents } from './events/almanac-calendar.js';
|
|
export { getLensOverlaySVG } from './events/lens-overlay.js';
|
|
|
|
// --- PROMO OVERRIDE ------------------------------------------------------
|
|
// Set this to show a custom banner regardless of weather or cosmic events.
|
|
// Leave as null for automatic event detection.
|
|
//
|
|
// Example:
|
|
// export const PROMO_OVERRIDE = {
|
|
// id: 'promo-summer',
|
|
// emoji: '--',
|
|
// title: 'Summer Sale',
|
|
// message: 'SunScope Extra - 20% off this weekend only.',
|
|
// color: '#c8922a',
|
|
// textColor: '#fff',
|
|
// type: 'promo',
|
|
// };
|
|
//
|
|
export const PROMO_OVERRIDE = null;
|
|
|
|
// --- CELL TAG LOGIC ------------------------------------------------------
|
|
// Returns the subset of active events that should show an icon for this row.
|
|
export function getCellTagEvents(events, row) {
|
|
if (!events || events.length === 0) return [];
|
|
return events.filter(ev => {
|
|
if (ev.type === 'promo') return false;
|
|
if (ev.nightOnly && row.elev >= -5) return false;
|
|
if (ev.id === 'heat-spike' && row.elev < 0) return false;
|
|
// Sunset/sunrise events: only show icon during the actual twilight window
|
|
if (ev.isoRange) {
|
|
if (row.iso < ev.isoRange[0] || row.iso > ev.isoRange[1]) return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
// --- MAIN EXPORT ---------------------------------------------------------
|
|
// Returns ALL active events for the given rows/date as an array.
|
|
// Empty array = nothing active.
|
|
//
|
|
// Order within array:
|
|
// 1. PROMO_OVERRIDE (if set, returned alone)
|
|
// 2. All matching cosmic calendar events for this date
|
|
// 3. All matching weather-derived events
|
|
|
|
export function getActiveEvents(rows, location) {
|
|
// 1. Manual promo override - shown alone, no mixing with other events
|
|
if (PROMO_OVERRIDE) return [PROMO_OVERRIDE];
|
|
|
|
// 2. Cosmic calendar - use the date of the rows being viewed, not today.
|
|
const dateStr = (rows && rows.length > 0)
|
|
? rows[0].iso.slice(0, 10)
|
|
: new Date().toISOString().slice(0, 10);
|
|
const cosmicHits = COSMIC_CALENDAR
|
|
.filter(ev => dateStr >= ev.start && dateStr <= ev.end)
|
|
.map(ev => Object.assign({}, ev, { message: dynamicCosmicMessage(ev, dateStr) }));
|
|
|
|
// 3. Weather-derived events (all that match, not just first)
|
|
const weatherEvents = [];
|
|
if (rows && rows.length > 0) {
|
|
const checks = [
|
|
checkHeatSpike(rows),
|
|
checkStorm(rows),
|
|
checkFrost(rows),
|
|
checkSunset(rows, location),
|
|
checkStargazing(rows),
|
|
];
|
|
checks.forEach(ev => { if (ev) weatherEvents.push(ev); });
|
|
}
|
|
|
|
return [...cosmicHits, ...weatherEvents];
|
|
}
|
|
|
|
// --- LENS EVENT PICKER ---------------------------------------------------
|
|
// From an array of active events, returns the single one closest to its peak
|
|
// (most "intense"). Weather events without a peak use today's date.
|
|
// Returns null if events array is empty.
|
|
|
|
export function getLensEvent(events) {
|
|
if (!events || events.length === 0) return null;
|
|
if (events.length === 1) return events[0];
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
return events.reduce((best, ev) => {
|
|
const peakB = best.peak || today;
|
|
const peakE = ev.peak || today;
|
|
const distB = Math.abs(new Date(today) - new Date(peakB));
|
|
const distE = Math.abs(new Date(today) - new Date(peakE));
|
|
return distE < distB ? ev : best;
|
|
});
|
|
}
|