Files
controller-v2/client/src/lib/festival.ts
T

71 lines
2.6 KiB
TypeScript
Raw Normal View History

2026-09-30 17:48:32 +08:00
/* ============================================================
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";
2026-10-09 13:56:57 +08:00
export type FestivalId = "christmas" | "halloween" | "newyear";
2026-09-30 17:48:32 +08:00
/** 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] },
2026-10-09 13:56:57 +08:00
// 元旦接续圣诞节,跨年窗口包含首尾日期。
newyear: { from: [12, 27], to: [1, 3] },
2026-09-30 17:48:32 +08:00
};
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(), []);
}
2026-10-09 13:56:57 +08:00
/** 跨年夜展示即将到来的年份,其余月份预览展示当年。 */
export function getNewYearDisplayYear(now: Date = new Date()): number {
return now.getFullYear() + (now.getMonth() === 11 ? 1 : 0);
}
2026-09-30 17:48:32 +08:00
/** 首页 VU 区的节日问候语,键与 FestivalId 对应。 */
export const FESTIVAL_GREETINGS: Record<FestivalId, string> = {
christmas: "Merry Christmas",
halloween: "Happy Halloween",
2026-10-09 13:56:57 +08:00
newyear: "Happy New Year",
2026-09-30 17:48:32 +08:00
};