Skip to content

Instantly share code, notes, and snippets.

@derak-kilgo
Created March 4, 2026 18:30
Show Gist options
  • Select an option

  • Save derak-kilgo/ee6d6d9675fe2c41ed199365c29123ed to your computer and use it in GitHub Desktop.

Select an option

Save derak-kilgo/ee6d6d9675fe2c41ed199365c29123ed to your computer and use it in GitHub Desktop.
xss confetti
<script>
(function () {
// Avoid double-injection
if (window.__confettiRunning) return;
window.__confettiRunning = true;
// Create canvas
var cv = document.createElement('canvas');
var ctx = cv.getContext('2d');
cv.style.position = 'fixed';
cv.style.top = 0;
cv.style.left = 0;
cv.style.width = '100%';
cv.style.height = '100%';
cv.style.pointerEvents = 'none';
cv.style.zIndex = 2147483647; // on top
document.documentElement.appendChild(cv);
// Resize handling
function resize() {
cv.width = window.innerWidth;
cv.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
// Particle factory
var TAU = Math.PI * 2;
var colors = ['#E91E63','#9C27B0','#3F51B5','#2196F3','#009688','#4CAF50','#FFC107','#FF5722','#795548','#00BCD4'];
function rand(min, max){ return Math.random()*(max-min)+min; }
function pick(arr){ return arr[(Math.random()*arr.length)|0]; }
function makeParticle() {
return {
x: rand(0, cv.width),
y: rand(-cv.height*0.2, -20),
vx: rand(-1.2, 1.2),
vy: rand(2.0, 5.0),
size: rand(6, 12),
color: pick(colors),
angle: rand(0, TAU),
spin: rand(-0.25, 0.25),
drag: rand(0.985, 0.995),
tilt: rand(-0.7, 0.7),
life: 0,
ttl: rand(180, 360) // frames
};
}
// Burst configuration
var particles = [];
var maxParticles = 220; // cap
var emission = 60; // per burst
var bursts = 6; // how many bursts
var burstGap = 180; // frames between bursts
var frame = 0;
var gravity = 0.06;
var windBase = rand(-0.02, 0.02);
function emit(count) {
for (var i = 0; i < count && particles.length < maxParticles; i++) {
var p = makeParticle();
// Launch near top-center
p.x = cv.width/2 + rand(-cv.width*0.15, cv.width*0.15);
p.vx += rand(-1.6, 1.6);
p.vy = rand(0.5, 3.5);
particles.push(p);
}
}
// Kick off first burst immediately
emit(emission);
var rafId;
function tick() {
frame++;
// Periodic bursts
if (bursts > 0 && frame % burstGap === 0) {
emit(emission);
bursts--;
}
// Wind oscillation
var wind = windBase + Math.sin(frame/90) * 0.06;
// Clear
ctx.clearRect(0, 0, cv.width, cv.height);
// Update & draw
for (var i = particles.length - 1; i >= 0; i--) {
var p = particles[i];
p.vx += wind * 0.1;
p.vy += gravity;
p.vx *= p.drag;
p.vy *= p.drag;
p.x += p.vx;
p.y += p.vy;
p.angle += p.spin;
p.life++;
// Wrap horizontally
if (p.x < -20) p.x = cv.width + 20;
if (p.x > cv.width + 20) p.x = -20;
// Remove if out of view or expired
if (p.y > cv.height + 40 || p.life > p.ttl) {
particles.splice(i, 1);
continue;
}
// Draw as tilted rectangle (simulating flutter)
var w = p.size;
var h = p.size * (0.6 + 0.4 * Math.sin(p.angle * 1.7));
ctx.save();
ctx.translate(p.x, p.y);
ctx.rotate(p.angle + p.tilt);
ctx.fillStyle = p.color;
ctx.fillRect(-w/2, -h/2, w, h);
ctx.restore();
}
// Stop when animation is done (no particles, no bursts pending)
if (particles.length === 0 && bursts <= 0) {
cleanup();
return;
}
rafId = requestAnimationFrame(tick);
}
function cleanup() {
cancelAnimationFrame(rafId);
window.removeEventListener('resize', resize);
if (cv && cv.parentNode) cv.parentNode.removeChild(cv);
window.__confettiRunning = false;
}
// Auto-start after load (or immediately if already loaded)
if (document.readyState === 'complete' || document.readyState === 'interactive') {
requestAnimationFrame(tick);
} else {
window.addEventListener('DOMContentLoaded', function(){ requestAnimationFrame(tick); }, { once: true });
}
})();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment