feat: add ParticleOverlay component for tsParticles effects

This commit is contained in:
Aaron Wood 2026-04-10 19:25:03 -04:00
parent a6e3ca6066
commit e947e8e127

View file

@ -0,0 +1,75 @@
import { useEffect, useState } from "react";
import Particles, { initParticlesEngine } from "@tsparticles/react";
import { loadSlim } from "@tsparticles/slim";
import type { AtmosphereState } from "../lib/atmosphereTypes";
import { getFireConfig, getRainConfig, getEmbersConfig } from "../lib/particleConfigs";
// Module-level singleton so the engine is only initialised once
let enginePromise: Promise<void> | null = null;
function ensureEngine(): Promise<void> {
if (!enginePromise) {
enginePromise = initParticlesEngine(async (engine) => {
await loadSlim(engine);
});
}
return enginePromise;
}
const overlayStyle: React.CSSProperties = {
position: "fixed",
inset: 0,
zIndex: 9997,
pointerEvents: "none",
};
const instanceStyle: React.CSSProperties = {
position: "absolute",
inset: 0,
};
interface ParticleOverlayProps {
atmosphere: AtmosphereState;
}
export default function ParticleOverlay({ atmosphere }: ParticleOverlayProps) {
const [ready, setReady] = useState(false);
useEffect(() => {
ensureEngine().then(() => setReady(true));
}, []);
if (!ready) return null;
const { fire, rain, embers } = atmosphere;
const anyActive = fire.active || rain.active || embers.active;
if (!anyActive) return null;
return (
<div style={overlayStyle}>
{fire.active && (
<Particles
key={`fire-${fire.intensity}`}
id="particles-fire"
options={getFireConfig(fire.intensity)}
style={instanceStyle}
/>
)}
{rain.active && (
<Particles
key={`rain-${rain.intensity}`}
id="particles-rain"
options={getRainConfig(rain.intensity)}
style={instanceStyle}
/>
)}
{embers.active && (
<Particles
key={`embers-${embers.intensity}`}
id="particles-embers"
options={getEmbersConfig(embers.intensity)}
style={instanceStyle}
/>
)}
</div>
);
}