299 lines
10 KiB
JavaScript
299 lines
10 KiB
JavaScript
// ------------------------------------------------------------------------
|
||
// almanac-calendar.js - Extended event calendar for the "What's Coming"
|
||
// panel and getUpcomingEvents() export.
|
||
//
|
||
// Includes all events from COSMIC_CALENDAR plus multi-year entries.
|
||
// Each entry has a 'latHint':
|
||
// null = global / no hint
|
||
// 'north' = better from northern latitudes
|
||
// 'south' = better from southern latitudes
|
||
// 'path:...' = specific eclipse path note
|
||
// ------------------------------------------------------------------------
|
||
|
||
export const ALMANAC_CALENDAR = [
|
||
// 2026 events (covers rest of year from today)
|
||
{
|
||
id: 'lunar-eclipse-2026-mar',
|
||
emoji: '🌕',
|
||
title: 'Total Lunar Eclipse',
|
||
desc: 'The Moon turns deep red as it passes through Earth\'s shadow. Visible from Europe, Africa, and the Americas.',
|
||
start: '2026-03-03', peak: '2026-03-03',
|
||
color: '#3a0a0a', textColor: '#ffd8d8',
|
||
latHint: null,
|
||
type: 'eclipse',
|
||
},
|
||
{
|
||
id: 'lyrids-2026',
|
||
emoji: '☄️',
|
||
title: 'Lyrid Meteor Shower',
|
||
desc: 'Up to 20 meteors/hour at peak. Active Apr 16–25, best after midnight from a dark site.',
|
||
start: '2026-04-16', peak: '2026-04-22',
|
||
color: '#2a1a5a', textColor: '#e8d8ff',
|
||
latHint: 'north',
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'eta-aquariids-2026',
|
||
emoji: '☄️',
|
||
title: 'Eta Aquariid Meteor Shower',
|
||
desc: 'Debris from Halley\'s Comet — up to 50/hour before dawn. Best from southern latitudes but visible worldwide.',
|
||
start: '2026-04-19', peak: '2026-05-06',
|
||
color: '#2a1a5a', textColor: '#e8d8ff',
|
||
latHint: 'south',
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'mars-conjunction-2026',
|
||
emoji: '🔴',
|
||
title: 'Mars & Venus Conjunction',
|
||
desc: 'Mars and Venus appear strikingly close in the evening sky — a beautiful naked-eye pairing.',
|
||
start: '2026-06-30', peak: '2026-07-01',
|
||
color: '#2a1520', textColor: '#ffc8d8',
|
||
latHint: null,
|
||
type: 'planetary',
|
||
},
|
||
{
|
||
id: 'perseids-2026',
|
||
emoji: '☄️',
|
||
title: 'Perseid Meteor Shower',
|
||
desc: 'One of the best showers of the year — up to 100/hour at peak, no need for a telescope.',
|
||
start: '2026-07-17', peak: '2026-08-12',
|
||
color: '#3a1a00', textColor: '#ffe8c0',
|
||
latHint: 'north',
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'solar-eclipse-2026-aug',
|
||
emoji: '🌑',
|
||
title: 'Total Solar Eclipse',
|
||
desc: 'The path of totality crosses Spain, Iceland, and Greenland. Partial eclipse visible across most of Europe.',
|
||
start: '2026-08-12', peak: '2026-08-12',
|
||
color: '#1a0a2a', textColor: '#d8c8f8',
|
||
latHint: 'path:Spain, Iceland, Greenland — partial eclipse across UK & Europe',
|
||
type: 'eclipse',
|
||
},
|
||
{
|
||
id: 'saturn-opposition-2026',
|
||
emoji: '🪐',
|
||
title: 'Saturn at Opposition',
|
||
desc: 'Saturn is at its closest and brightest — rings tilted beautifully toward Earth. Visible all night.',
|
||
start: '2026-09-23', peak: '2026-09-23',
|
||
color: '#1a2a3a', textColor: '#c8e8ff',
|
||
latHint: null,
|
||
type: 'planetary',
|
||
},
|
||
{
|
||
id: 'orionids-2026',
|
||
emoji: '☄️',
|
||
title: 'Orionid Meteor Shower',
|
||
desc: 'Fast, bright meteors from Halley\'s Comet — up to 25/hour. Active Oct 2 – Nov 7.',
|
||
start: '2026-10-02', peak: '2026-10-21',
|
||
color: '#2a1a5a', textColor: '#e8d8ff',
|
||
latHint: null,
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'jupiter-opposition-2026',
|
||
emoji: '🪐',
|
||
title: 'Jupiter at Opposition',
|
||
desc: 'Jupiter at its closest — you can see its cloud bands and four Galilean moons with binoculars.',
|
||
start: '2026-10-08', peak: '2026-10-08',
|
||
color: '#1a2a3a', textColor: '#c8e8ff',
|
||
latHint: null,
|
||
type: 'planetary',
|
||
},
|
||
{
|
||
id: 'leonids-2026',
|
||
emoji: '☄️',
|
||
title: 'Leonid Meteor Shower',
|
||
desc: 'Fast, bright meteors from comet Tempel-Tuttle. Up to 15/hour at peak.',
|
||
start: '2026-11-06', peak: '2026-11-17',
|
||
color: '#2a1a5a', textColor: '#e8d8ff',
|
||
latHint: null,
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'geminids-2026',
|
||
emoji: '☄️',
|
||
title: 'Geminid Meteor Shower',
|
||
desc: 'The finest shower of the year — up to 150 meteors/hour, visible even before midnight.',
|
||
start: '2026-12-04', peak: '2026-12-13',
|
||
color: '#3a1a00', textColor: '#ffe8c0',
|
||
latHint: 'north',
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'ursids-2026',
|
||
emoji: '☄️',
|
||
title: 'Ursid Meteor Shower',
|
||
desc: 'A quieter shower near the winter solstice — circumpolar, so best from northern latitudes.',
|
||
start: '2026-12-17', peak: '2026-12-22',
|
||
color: '#2a1a5a', textColor: '#e8d8ff',
|
||
latHint: 'north',
|
||
type: 'meteor',
|
||
},
|
||
|
||
// -- 2027 -------------------------------------------------------------
|
||
{
|
||
id: 'quadrantids-2027',
|
||
emoji: '☄️',
|
||
title: 'Quadrantid Meteor Shower',
|
||
desc: 'Up to 120 meteors/hour at peak — one of the strongest showers but with a very sharp peak. Best in the hours before dawn on 4 Jan.',
|
||
start: '2027-01-01', peak: '2027-01-04',
|
||
color: '#2a1a5a', textColor: '#e8d8ff',
|
||
latHint: 'north',
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'annular-solar-eclipse-2027-feb',
|
||
emoji: '🌑',
|
||
title: 'Annular Solar Eclipse',
|
||
desc: 'A "ring of fire" eclipse visible from parts of South America, Africa and the Indian Ocean. Partial eclipse across much of the southern hemisphere.',
|
||
start: '2027-02-06', peak: '2027-02-06',
|
||
color: '#1a0a2a', textColor: '#d8c8f8',
|
||
latHint: 'path:South America, southern Africa, Indian Ocean',
|
||
type: 'eclipse',
|
||
},
|
||
{
|
||
id: 'mars-opposition-2027',
|
||
emoji: '🔴',
|
||
title: 'Mars at Opposition',
|
||
desc: 'Mars is at its closest and brightest — a vivid red beacon visible all night long. Good binoculars reveal its distinct colour.',
|
||
start: '2027-02-19', peak: '2027-02-19',
|
||
color: '#2a1520', textColor: '#ffc8d8',
|
||
latHint: null,
|
||
type: 'planetary',
|
||
},
|
||
{
|
||
id: 'lyrids-2027',
|
||
emoji: '☄️',
|
||
title: 'Lyrid Meteor Shower',
|
||
desc: 'Up to 20 meteors/hour at peak. Note: bright waning gibbous moon may reduce visibility this year. Active Apr 16–25.',
|
||
start: '2027-04-16', peak: '2027-04-23',
|
||
color: '#2a1a5a', textColor: '#e8d8ff',
|
||
latHint: 'north',
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'eta-aquariids-2027',
|
||
emoji: '☄️',
|
||
title: 'Eta Aquariid Meteor Shower',
|
||
desc: 'Debris from Halley\'s Comet — up to 50/hour before dawn. New moon on 6 May means excellent dark skies this year.',
|
||
start: '2027-04-19', peak: '2027-05-05',
|
||
color: '#2a1a5a', textColor: '#e8d8ff',
|
||
latHint: 'south',
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'perseids-2027',
|
||
emoji: '☄️',
|
||
title: 'Perseid Meteor Shower',
|
||
desc: 'One of the best showers of the year — up to 100/hour at peak, no telescope needed. Active Jul 17 – Aug 24.',
|
||
start: '2027-07-17', peak: '2027-08-13',
|
||
color: '#3a1a00', textColor: '#ffe8c0',
|
||
latHint: 'north',
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'total-solar-eclipse-2027-aug',
|
||
emoji: '🌑',
|
||
title: 'Total Solar Eclipse',
|
||
desc: 'One of the longest total solar eclipses of the century. Path of totality crosses Morocco, Spain, Algeria, Libya, Egypt and Saudi Arabia.',
|
||
start: '2027-08-02', peak: '2027-08-02',
|
||
color: '#1a0a2a', textColor: '#d8c8f8',
|
||
latHint: 'path:Morocco, Spain, Algeria, Libya, Egypt, Saudi Arabia',
|
||
type: 'eclipse',
|
||
},
|
||
{
|
||
id: 'venus-jupiter-conjunction-2027',
|
||
emoji: '🪐',
|
||
title: 'Venus & Jupiter Conjunction',
|
||
desc: 'Venus and Jupiter appear strikingly close together in the evening sky — a dazzling naked-eye pairing of the two brightest planets.',
|
||
start: '2027-08-23', peak: '2027-08-25',
|
||
color: '#1a2a3a', textColor: '#c8e8ff',
|
||
latHint: null,
|
||
type: 'planetary',
|
||
},
|
||
{
|
||
id: 'orionids-2027',
|
||
emoji: '☄️',
|
||
title: 'Orionid Meteor Shower',
|
||
desc: 'Fast, bright meteors from Halley\'s Comet debris — up to 25/hour. Active Oct 2 – Nov 7.',
|
||
start: '2027-10-02', peak: '2027-10-21',
|
||
color: '#2a1a5a', textColor: '#e8d8ff',
|
||
latHint: null,
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'leonids-2027',
|
||
emoji: '☄️',
|
||
title: 'Leonid Meteor Shower',
|
||
desc: 'Fast meteors from comet Tempel-Tuttle. Up to 15/hour at peak. Active Nov 6–30.',
|
||
start: '2027-11-06', peak: '2027-11-17',
|
||
color: '#2a1a5a', textColor: '#e8d8ff',
|
||
latHint: null,
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'geminids-2027',
|
||
emoji: '☄️',
|
||
title: 'Geminid Meteor Shower',
|
||
desc: 'The finest shower of the year — up to 150 meteors/hour, visible even before midnight. Active Dec 4–20.',
|
||
start: '2027-12-04', peak: '2027-12-14',
|
||
color: '#3a1a00', textColor: '#ffe8c0',
|
||
latHint: 'north',
|
||
type: 'meteor',
|
||
},
|
||
{
|
||
id: 'ursids-2027',
|
||
emoji: '☄️',
|
||
title: 'Ursid Meteor Shower',
|
||
desc: 'A quieter shower near the winter solstice — circumpolar, best from northern latitudes. Active Dec 17–26.',
|
||
start: '2027-12-17', peak: '2027-12-22',
|
||
color: '#2a1a5a', textColor: '#e8d8ff',
|
||
latHint: 'north',
|
||
type: 'meteor',
|
||
},
|
||
];
|
||
|
||
// Returns a location-aware visibility note for an almanac entry.
|
||
// lat = user's latitude.
|
||
export function getVisibilityNote(entry, lat) {
|
||
if (!entry.latHint) return null;
|
||
if (entry.latHint.startsWith('path:')) {
|
||
return entry.latHint.replace('path:', '').trim();
|
||
}
|
||
if (entry.latHint === 'north') {
|
||
if (lat >= 45) return 'Well placed for your latitude';
|
||
if (lat >= 20) return 'Visible from your location';
|
||
return 'Better from northern latitudes';
|
||
}
|
||
if (entry.latHint === 'south') {
|
||
if (lat <= 10) return 'Well placed for your latitude';
|
||
if (lat <= 40) return 'Visible from your location';
|
||
return 'Better from southern latitudes';
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// --- MAIN ALMANAC EXPORT -------------------------------------------------
|
||
// Returns events with peak dates in the next 'days' days (default 90),
|
||
// sorted by peak date, with a visibility note added.
|
||
export function getUpcomingEvents(location, days = 90) {
|
||
const today = new Date();
|
||
today.setHours(0, 0, 0, 0);
|
||
const cutoff = new Date(today);
|
||
cutoff.setDate(cutoff.getDate() + days);
|
||
const todayStr = today.toISOString().slice(0, 10);
|
||
const cutoffStr = cutoff.toISOString().slice(0, 10);
|
||
|
||
return ALMANAC_CALENDAR
|
||
.filter(ev => ev.peak >= todayStr && ev.peak <= cutoffStr)
|
||
.sort((a, b) => a.peak.localeCompare(b.peak))
|
||
.map(ev => ({
|
||
...ev,
|
||
visibilityNote: getVisibilityNote(ev, location?.lat ?? 51),
|
||
daysUntil: Math.round((new Date(ev.peak + 'T00:00Z') - today) / 86400000),
|
||
}));
|
||
}
|