71 lines
2.6 KiB
TypeScript
71 lines
2.6 KiB
TypeScript
/* ============================================================
|
||
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<FestivalId, string> = {
|
||
christmas: "Merry Christmas",
|
||
halloween: "Happy Halloween",
|
||
newyear: "Happy New Year",
|
||
};
|