96 lines
2.7 KiB
TypeScript
96 lines
2.7 KiB
TypeScript
|
|
import { SeasonTheme } from './types';
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Single source of truth for when each seasonal theme auto-activates.
|
|||
|
|
*
|
|||
|
|
* Both `getActiveSeason` (the runtime "Auto" selector) and the settings UI read
|
|||
|
|
* this list, so the date windows shown to the user can never drift from the
|
|||
|
|
* dates actually used. Order matters: it is the activation PRIORITY — the first
|
|||
|
|
* entry whose window matches wins (e.g. Deep Space outranks Autumn in their
|
|||
|
|
* early-October overlap).
|
|||
|
|
*/
|
|||
|
|
export type SeasonScheduleEntry = {
|
|||
|
|
theme: SeasonTheme;
|
|||
|
|
/** Human-readable activation window for display in settings. */
|
|||
|
|
dateRange: string;
|
|||
|
|
/** Whether this theme is active on the given month (1-12) and day (1-31). */
|
|||
|
|
matches: (month: number, day: number) => boolean;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export const SEASON_SCHEDULE: SeasonScheduleEntry[] = [
|
|||
|
|
{
|
|||
|
|
theme: 'newyear',
|
|||
|
|
dateRange: 'Dec 31 – Jan 2',
|
|||
|
|
matches: (m, d) => (m === 12 && d === 31) || (m === 1 && d <= 2),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
theme: 'valentines',
|
|||
|
|
dateRange: 'Feb 10 – 15',
|
|||
|
|
matches: (m, d) => m === 2 && d >= 10 && d <= 15,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
theme: 'stpatricks',
|
|||
|
|
dateRange: 'Mar 15 – 18',
|
|||
|
|
matches: (m, d) => m === 3 && d >= 15 && d <= 18,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
theme: 'aprilfools',
|
|||
|
|
dateRange: 'Apr 1',
|
|||
|
|
matches: (m, d) => m === 4 && d === 1,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
theme: 'earthday',
|
|||
|
|
dateRange: 'Apr 20 – 23',
|
|||
|
|
matches: (m, d) => m === 4 && d >= 20 && d <= 23,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
theme: 'lunar',
|
|||
|
|
dateRange: 'Jan 22 – Feb 5',
|
|||
|
|
matches: (m, d) => (m === 1 && d >= 22) || (m === 2 && d <= 5),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
theme: 'arcade',
|
|||
|
|
dateRange: 'Sep 12',
|
|||
|
|
matches: (m, d) => m === 9 && d === 12,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
theme: 'deepspace',
|
|||
|
|
dateRange: 'Oct 4 – 10',
|
|||
|
|
matches: (m, d) => m === 10 && d >= 4 && d <= 10,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
theme: 'halloween',
|
|||
|
|
dateRange: 'Oct 15 – Nov 1',
|
|||
|
|
matches: (m, d) => (m === 10 && d >= 15) || (m === 11 && d === 1),
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
theme: 'christmas',
|
|||
|
|
dateRange: 'Dec 10 – 30',
|
|||
|
|
matches: (m, d) => m === 12 && d >= 10,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
theme: 'autumn',
|
|||
|
|
dateRange: 'Sep 21 – Oct 14',
|
|||
|
|
matches: (m, d) => (m === 9 && d >= 21) || (m === 10 && d <= 14),
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
/** Map of theme → human-readable activation window (for settings captions). */
|
|||
|
|
export const SEASON_DATE_RANGES: Record<SeasonTheme, string> = SEASON_SCHEDULE.reduce(
|
|||
|
|
(acc, entry) => {
|
|||
|
|
acc[entry.theme] = entry.dateRange;
|
|||
|
|
return acc;
|
|||
|
|
},
|
|||
|
|
{} as Record<SeasonTheme, string>,
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* The seasonal theme that should be active on `now`, or null if none. First
|
|||
|
|
* matching entry in SEASON_SCHEDULE priority order wins.
|
|||
|
|
*/
|
|||
|
|
export function getActiveSeason(now: Date): SeasonTheme | null {
|
|||
|
|
const month = now.getMonth() + 1; // 1-12
|
|||
|
|
const day = now.getDate();
|
|||
|
|
return SEASON_SCHEDULE.find((entry) => entry.matches(month, day))?.theme ?? null;
|
|||
|
|
}
|