Use the effect context
An effect is a culturally neutral visual primitive. It receives one owned layer, a seeded random source, viewport snapshot, effective motion, scheduler, animation registry, resize hook and abort signal. It should not know about React, routing or the host application.
import { defineEffect } from "zeenat/core";
export function fallingHearts(color: string) {
return defineEffect({
id: "falling-hearts",
layer: "ambient",
mount(context) {
const heart = context.layer.ownerDocument.createElement("span");
heart.textContent = "♥";
heart.style.color = color;
heart.style.position = "absolute";
heart.style.left = `${context.randomBetween(5, 95)}%`;
context.layer.append(heart);
if (context.motion === "full") {
context.animate(heart, [{ opacity: 0 }, { opacity: 0.7 }], {
duration: 3000, iterations: Infinity, direction: "alternate",
});
}
},
});
}Resource and accessibility requirements
Bound node, animation, timer and RAF counts. Reduce density on small screens. Provide a deliberate static or absent reduced-motion mode. Do not inject global CSS, fetch remote assets, add focusable nodes or capture pointer events.
Let the scope own resources
Nodes inside context.layer, animations created with context.animate, scheduler callbacks and resize callbacks are owned by the effect scope. Return a cleanup function for any observer, worker or external resource created directly.