npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zakkster/lite-fireworks

v1.0.0

Published

Zero-GC, SoA fireworks engine supporting OKLCH colors, cinematic bloom trails, vector-comet mode, and multi-phase shell explosions. Designed for high-performance displays with no allocations in the hot path.

Readme

@zakkster/lite-fireworks

npm version npm bundle size npm downloads npm total downloads TypeScript Dependencies License: MIT

Zero-GC SoA fireworks engine with OKLCH colors. Bloom trails on dark backgrounds, vector-comets on light. One dependency. 165 lines.

→ Live Interactive Playground

Why lite-fireworks?

| Feature | lite-fireworks | fireworks-js | tsparticles | p5.js | |---|---|---|---|---| | Zero-GC hot path | Yes | No | No | No | | SoA flat arrays | Yes | No | No | No | | OKLCH colors | Yes | No | No | No | | Physics-correct arcs | Yes | Approximate | No | Manual | | Two render modes | Bloom + Vector | One | One | Manual | | Live config | Yes | Partial | Yes | No | | Seeded RNG (optional) | Yes | No | No | No | | Dependencies | 1 | 0 | 10+ | 0 | | Bundle size | < 2KB | ~4KB | ~40KB | ~800KB |

Installation

npm install @zakkster/lite-fireworks

Quick Start

import { FireworksEngine } from '@zakkster/lite-fireworks';

const canvas = document.getElementById('stage');
const ctx = canvas.getContext('2d');
const sky = new FireworksEngine(5000);

let w = canvas.width, h = canvas.height;
let last = performance.now();

function loop(time) {
    const dt = Math.min((time - last) / 1000, 0.1);
    last = time;
    sky.updateAndDraw(ctx, dt, w, h);
    requestAnimationFrame(loop);
}

// Launch a shell from the bottom, explode at 30% height
sky.launch(w / 2, h, h * 0.3);

requestAnimationFrame(loop);

Two Render Modes

lite-fireworks has two distinct visual modes, switched via a single boolean:

Bloom Mode (default — dark backgrounds)

Particles render as glowing dots with additive blending (globalCompositeOperation: 'lighter'). A semi-transparent overlay (fadeColor) creates cinematic bloom trails. This is the classic fireworks look.

const sky = new FireworksEngine(5000, {
    transparentBackground: false,        // bloom mode
    fadeColor: 'rgba(10, 10, 10, 0.25)', // trail persistence
});

Vector-Comet Mode (light/transparent backgrounds)

Particles render as velocity-direction strokes with lineCap: 'round'. The canvas is fully cleared each frame. Perfect for overlaying on light-colored pages or photos.

const sky = new FireworksEngine(5000, {
    transparentBackground: true, // vector-comet mode
});

Full Config Reference

All config values are live-mutable — change them between frames for real-time tuning.

| Option | Type | Default | Description | |---|---|---|---| | gravity | number | 250 | Downward acceleration in px/s². Controls shell arc height and star fall speed. | | friction | number | 0.96 | Per-frame velocity retention (0–1). Lower = stars slow faster. 0.98 = willow effect. | | starCount | number | 60 | Stars spawned per shell explosion. | | transparentBackground | boolean | false | false = bloom mode, true = vector-comet mode. | | fadeColor | string | 'rgba(10,10,10,0.25)' | Fade overlay color for bloom trails. Lower alpha = longer trails. | | colors | Array | 7 OKLCH defaults | Array of OKLCH objects { l, c, h } or CSS strings. Pre-parsed at construction. | | rng | Function | Math.random | RNG function () => number in [0, 1). Inject seeded RNG for determinism. |


Particle Physics

Shell Phase (state = 1)

A shell launches from (startX, startY) with velocity computed from real ballistic physics:

vy = -√(2 × gravity × |startY - targetY|)

This guarantees the shell reaches exactly targetY before deceleration triggers the explosion. No magic tuning needed — change gravity and the arcs stay physically correct.

Shells receive a slight horizontal wobble (±25 px/s) for natural variation. Shells do not experience drag — they are powered projectiles.

Star Phase (state = 2)

When a shell's vertical velocity crosses -20 px/s (near apex), it dies and spawns starCount stars in a radial burst. Each star gets a random angle, speed (50–350 px/s), and lifetime (1.0–1.2s).

Stars experience full physics: gravity pulls them down, friction decelerates them. The friction coefficient controls the "feel" — 0.92 is sharp and snappy, 0.98 is long and willow-like.

Stars are culled when: life ≤ 0, or they exit the viewport bounds.


Canvas Setup (No Built-in Resize — By Design)

lite-fireworks is a pure engine. It takes (ctx, dt, w, h) and renders — it doesn't own the canvas, doesn't create DOM elements, doesn't listen for resize events. This is intentional:

  • The engine stays < 2KB with zero DOM coupling
  • You control DPR scaling, ResizeObserver strategy, and canvas lifecycle
  • It composes cleanly with lite-viewport or any custom setup

Here's the recommended canvas setup:

import { FireworksEngine } from '@zakkster/lite-fireworks';

const canvas = document.getElementById('stage');
const ctx = canvas.getContext('2d');
const sky = new FireworksEngine();

let w = 0, h = 0;
const dpr = window.devicePixelRatio || 1;

function updateSize() {
    w = canvas.clientWidth || window.innerWidth;
    h = canvas.clientHeight || window.innerHeight;
    canvas.width = w * dpr;
    canvas.height = h * dpr;
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.scale(dpr, dpr);
}

// RAF-debounced ResizeObserver (prevents layout thrashing)
let scheduled = false;
new ResizeObserver(() => {
    if (!scheduled) {
        scheduled = true;
        requestAnimationFrame(() => { scheduled = false; updateSize(); });
    }
}).observe(canvas.parentElement || document.body);

updateSize();

// Game loop
let last = performance.now();
function loop(time) {
    const dt = Math.min((time - last) / 1000, 0.1);
    last = time;
    sky.updateAndDraw(ctx, dt, w, h);
    requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

Or with lite-viewport (even simpler):

import { FireworksEngine } from '@zakkster/lite-fireworks';
import { Viewport } from 'lite-viewport';

const canvas = document.getElementById('stage');
const sky = new FireworksEngine();
let W, H;

const vp = new Viewport({
    canvas,
    onResize(w, h, dpr) { W = w; H = h; }
});

function loop(time) {
    sky.updateAndDraw(vp.ctx, 0.016, W, H);
    requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

Seeded Random (Deterministic Replays)

lite-fireworks uses Math.random by default for zero extra dependencies. For deterministic output, inject @zakkster/lite-random:

import { FireworksEngine } from '@zakkster/lite-fireworks';
import { Random } from '@zakkster/lite-random';

const rng = new Random(42);

const sky = new FireworksEngine(5000, {
    rng: () => rng.next(),  // inject seeded RNG
});

// Same seed + same launch sequence = identical visual output
sky.launch(400, 600, 200);
sky.launch(200, 600, 150);

// Reset for replay
rng.reset(42);
sky.clear();
sky.launch(400, 600, 200);  // exact same firework
sky.launch(200, 600, 150);  // exact same firework

Recipes

const sky = new FireworksEngine(8000);

setInterval(() => {
    if (document.hidden) return;
    const startX = w * 0.2 + Math.random() * (w * 0.6);
    const targetY = h * 0.1 + Math.random() * (h * 0.4);
    sky.launch(startX, h, targetY);
}, 800);
canvas.addEventListener('click', (e) => {
    sky.launch(e.clientX, h, e.clientY);
});
const sky = new FireworksEngine(8000, {
    gravity: 80,
    friction: 0.98,
    starCount: 120,
    fadeColor: 'rgba(10, 10, 10, 0.12)', // very persistent trails
});
const sky = new FireworksEngine(3000, {
    gravity: 450,
    friction: 0.92,
    starCount: 40,
    transparentBackground: true,
});
import { FireworksEngine } from '@zakkster/lite-fireworks';

const sky = new FireworksEngine(5000, {
    colors: [
        { l: 0.6, c: 0.25, h: 280 },  // brand purple
        { l: 0.8, c: 0.15, h: 60 },   // warm gold
        { l: 0.9, c: 0.08, h: 0 },    // soft white
    ],
});
// Skip the launch phase — explode directly at a position
canvas.addEventListener('click', (e) => {
    const colorId = Math.floor(Math.random() * sky.colors.length);
    sky.explode(e.clientX, e.clientY, colorId);
});
// All config values are live-mutable
gravitySlider.oninput = () => sky.config.gravity = +gravitySlider.value;
frictionSlider.oninput = () => sky.config.friction = +frictionSlider.value;
starSlider.oninput = () => sky.config.starCount = +starSlider.value;
modeToggle.onchange = () => sky.config.transparentBackground = modeToggle.checked;

API

new FireworksEngine(maxParticles?, config?)

Creates a fireworks engine with a pre-allocated SoA particle pool.

| Parameter | Type | Default | Description | |---|---|---|---| | maxParticles | number | 5000 | Pool capacity. Shared between shells and stars. | | config | FireworksConfig | see above | All options. Live-mutable after construction. |

Methods

| Method | Description | |---|---| | .launch(startX, startY, targetY, colorId?) | Fire a shell. Velocity from physics. Color random if omitted. | | .explode(originX, originY, colorId) | Manual radial burst. Called automatically at shell apex. | | .updateAndDraw(ctx, dt, w, h) | Physics + render. Call once per frame. dt in seconds. | | .clear() | Kill all particles immediately. | | .destroy() | Null all typed arrays. Idempotent. |


License

MIT

Part of the @zakkster ecosystem

Zero-GC, deterministic, tree-shakeable micro-libraries for high-performance web presentation.