import { FxGate, FxKind } from '../game/engine/kernel/fxqueue' import { seasonOf } from '../game/data/season' let gate: FxGate | null = null export function fx(): FxGate | null { return gate } export function ensureFx(opts?: { onEmit?: (kind: FxKind, payload?: Record) => void }): FxGate { if (gate) return gate gate = new FxGate({ emit: (e) => { switch (e.kind) { case 'mist': { window.dispatchEvent(new CustomEvent('fx:mist')) break } case 'spark': { window.dispatchEvent(new CustomEvent('fx:spark')) break } case 'ripple': { window.dispatchEvent(new CustomEvent('fx:ripple', { detail: e.payload ?? {} })) break } case 'blade': { window.dispatchEvent(new CustomEvent('fx:blade')) break } case 'seasonShift': { window.dispatchEvent(new CustomEvent('fx:season', { detail: e.payload ?? {} })) break } case 'drift': { window.dispatchEvent(new CustomEvent('fx:drift', { detail: e.payload ?? {} })) break } case 'pulse': { window.dispatchEvent(new CustomEvent('fx:pulse', { detail: e.payload ?? {} })) break } } opts?.onEmit?.(e.kind, e.payload) } }) if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') { const mq = window.matchMedia('(prefers-reduced-motion: reduce)') gate.setReduced(mq.matches) mq.addEventListener('change', (ev) => gate?.setReduced(ev.matches)) } return gate } /** store.advance 后调用:检测季节变化 → 季节脉动 + 季节色温 CSS 变量 */ export function fxSeason(prevMonth: number, nextMonth: number): void { const g = ensureFx() const prev = seasonOf(prevMonth) const next = seasonOf(nextMonth) if (prev !== next) { g.emit('seasonShift', { season: next, accent: labelOf(next) }) } applySeason(next) } function labelOf(s: string): string { return { spring: '春', summer: '夏', autumn: '秋', winter: '冬' }[s] ?? s } export function seasonTintOf(month: number): string { const s = seasonOf(month) return labelOf(s) } /** 直接应用季节色温 CSS 变量(单源 data-season) */ export function applySeason(season: string): void { if (typeof document === 'undefined') return document.documentElement.setAttribute('data-season', season) } /** 慢帧保险:document.hidden 时自动停 particle(detail 在 canvas 端监听) */ export function fxVisibilityAutopause(): () => void { const onVis = () => { const g = ensureFx() if (document.hidden) { // hidden 时禁止未来发射 g.setReduced(true) } else { const mq = window.matchMedia('(prefers-reduced-motion: reduce)') g.setReduced(mq.matches) } } document.addEventListener('visibilitychange', onVis) return () => document.removeEventListener('visibilitychange', onVis) }