Files
controller-v2/client/src/lib/festival.ts
T
2026-10-09 13:56:57 +08:00

71 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
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",
};