Authoring

Creating custom effects

Author a Zeenat.js effect with scoped DOM ownership, seeded randomness, reduced motion and deterministic cleanup.

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.

falling-hearts.ts
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.