# extendify/3.1.6/src/Agent/lib/confetti.js

Extendify, version 3.1.6. 135 lines.

- Page: https://pluginprobe.com/plugins/extendify/3.1.6/code/src/Agent/lib/confetti.js
- Raw: https://pluginprobe.com/plugins/extendify/3.1.6/raw/src/Agent/lib/confetti.js
- Modified: 2026-08-20T19:51:40+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/extendify/3.1.6/code/src/Agent/lib/confetti.js#L10-L20`.

```javascript
import confetti from 'canvas-confetti';

export const CANVAS_CONFETTI = 'extendify-agent:canvas-confetti';

let confettiInstance = null;

const ensureConfettiInstance = () => {
	if (confettiInstance) return confettiInstance;

	// Custom canvas to account for height issues
	const canvas = document.createElement('canvas');
	canvas.style.position = 'fixed';
	canvas.style.top = '0';
	canvas.style.left = '0';
	canvas.style.width = '100%';
	canvas.style.height = '100%';
	canvas.style.pointerEvents = 'none';
	canvas.style.zIndex = Number.MAX_SAFE_INTEGER;
	document.body.appendChild(canvas);

	confettiInstance = confetti.create(canvas, {
		disableForReducedMotion: true,
		resize: true,
	});
	return confettiInstance;
};

// Its own canvas is what confines a burst to the element that asked.
export const confettiIn = (canvas) =>
	confetti.create(canvas, { disableForReducedMotion: true, resize: true });

const TOP = () => ({ x: Math.random(), y: 0 });

// A zero start velocity is what makes it fall rather than be thrown.
export const FALLING = {
	interval: 55,
	perDrop: 2,
	drift: 0.5,
	origin: TOP,
	colors: [
		'#26ccff',
		'#a25afd',
		'#ff5e7e',
		'#88ff5a',
		'#fcff42',
		'#ffa62d',
		'#ff36ff',
	],
	shot: {
		particleCount: 1,
		startVelocity: 0,
		gravity: 0.9,
		ticks: 300,
		scalar: 0.85,
	},
};

// An absent interval is what makes it fire once instead of repeating.
export const THROW = {
	perDrop: 2,
	origin: (i) => ({ x: i, y: 0.75 }),
	shot: {
		particleCount: 70,
		startVelocity: 50,
		spread: 75,
		decay: 0.91,
		ticks: 220,
		scalar: 0.95,
	},
};

const pick = (list) => list[Math.floor(Math.random() * list.length)];

// A multi-particle shot cannot spread at zero velocity, so it lands as one clump.
// A one-particle shot takes only the first colour of the palette.
export const dropConfetti = (
	shoot,
	{ interval, shot, drift = 0, perDrop = 1, colors, origin = TOP },
) => {
	const drop = () => {
		for (let i = 0; i < perDrop; i++) {
			shoot({
				...shot,
				origin: origin(i),
				...(drift && { drift: (Math.random() * 2 - 1) * drift }),
				...(colors && { colors: [pick(colors)] }),
			});
		}
	};
	drop();
	if (!interval) return () => {};
	const timer = setInterval(drop, interval);
	return () => clearInterval(timer);
};

export const throwSideConfetti = (shoot) => {
	const LEFT = {
		count: 200,
		defaults: {
			origin: { y: 0.7, x: 0 },
		},
		shots: [
			{ ratio: 0.25, spread: 26, startVelocity: 55 },
			{ ratio: 0.2, spread: 60 },
			{ ratio: 0.35, spread: 100, decay: 0.91, scalar: 0.8 },
			{ ratio: 0.1, spread: 120, startVelocity: 25, decay: 0.92, scalar: 1.2 },
			{ ratio: 0.1, spread: 120, startVelocity: 45 },
		],
	};
	const shooter = shoot ?? ensureConfettiInstance();
	throwConfetti(LEFT, shooter);
	throwConfetti(
		{
			...LEFT,
			defaults: {
				origin: { y: 0.7, x: 1 },
			},
		},
		shooter,
	);
};

const throwConfetti = (config, shoot) => {
	const { count = 1, defaults, shots } = config;

	shots.forEach(({ ratio = 1, ...opts }) => {
		shoot({
			...(defaults ?? {}),
			...(opts ?? {}),
			particleCount: Math.floor(count * ratio),
			zIndex: Number.MAX_SAFE_INTEGER,
		});
	});
};

```
