/* ============================================================ Festival skin gating — date-driven, URL-overridable. Only Christmas is implemented today; add more festivals by extending FESTIVAL_WINDOWS plus the matching components under components/festival/. ============================================================ */ import { useMemo } from "react"; export type FestivalId = "christmas" | "halloween" | "newyear"; /** Inclusive [month, day] windows. Christmas: Dec 20 – Dec 26; Halloween: Oct 26 – Nov 1. */ const FESTIVAL_WINDOWS: Record< FestivalId, { from: [number, number]; to: [number, number] } > = { christmas: { from: [12, 20], to: [12, 26] }, halloween: { from: [10, 26], to: [11, 1] }, // 元旦接续圣诞节,跨年窗口包含首尾日期。 newyear: { from: [12, 27], to: [1, 3] }, }; function inWindow( month: number, day: number, w: { from: [number, number]; to: [number, number] }, ) { const cur = month * 100 + day; const from = w.from[0] * 100 + w.from[1]; const to = w.to[0] * 100 + w.to[1]; // Support windows that wrap the new year (from > to). return from <= to ? cur >= from && cur <= to : cur >= from || cur <= to; } /** * Resolve the active festival skin. * `?festival=christmas` forces it on and `?festival=off` forces it off (QA hook, * so the skin can be verified without waiting for the real date window). * Otherwise falls back to the system-date window. */ export function getFestival( now: Date = new Date(), search: string = typeof location !== "undefined" ? location.search : "", ): FestivalId | null { const override = new URLSearchParams(search).get("festival"); if (override === "off" || override === "none") return null; if (override && Object.prototype.hasOwnProperty.call(FESTIVAL_WINDOWS, override)) { return override as FestivalId; } for (const id of Object.keys(FESTIVAL_WINDOWS) as FestivalId[]) { if (inWindow(now.getMonth() + 1, now.getDate(), FESTIVAL_WINDOWS[id])) return id; } return null; } /** Stable per-mount festival resolution (the date won't matter mid-session). */ export function useFestival(): FestivalId | null { return useMemo(() => getFestival(), []); } /** 跨年夜展示即将到来的年份,其余月份预览展示当年。 */ export function getNewYearDisplayYear(now: Date = new Date()): number { return now.getFullYear() + (now.getMonth() === 11 ? 1 : 0); } /** 首页 VU 区的节日问候语,键与 FestivalId 对应。 */ export const FESTIVAL_GREETINGS: Record = { christmas: "Merry Christmas", halloween: "Happy Halloween", newyear: "Happy New Year", };