Components / KATAYABURI / Confetti Cannon
Confetti Cannon beta
A button that fires confetti out of both corners of its stage. You already pressed it twice.
<div class="k-conf" data-conf> <canvas aria-hidden="true"></canvas> <button class="k-conf-btn" type="button" data-conf-fire>Payment complete — fire</button> </div>
/* KATA Confetti Cannon — KATAYABURI. On-token confetti. */
.k-conf{position:relative;width:100%;background:var(--k-zinc-950);border-radius:14px;overflow:hidden;font-family:var(--k-font);min-height:210px;display:flex;align-items:center;justify-content:center}
.k-conf canvas{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
.k-conf-btn{position:relative;z-index:2;background:var(--k-on-color);color:var(--k-zinc-950);border:0;font-family:inherit;font-size:14.5px;font-weight:650;padding:12px 24px;border-radius:99px;cursor:pointer}
.k-conf-btn:active{transform:scale(.96)}
// KATA Confetti Cannon — canvas pieces, token colors, real gravity.
document.querySelectorAll('[data-conf]').forEach(function (stage) {
var canvas = stage.querySelector('canvas');
var ctx = canvas.getContext('2d');
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var cs = getComputedStyle(stage);
var COLORS = ['--k-accent', '--k-on-color', '--k-zinc-400', '--k-amber-ink'].map(function (v) { return cs.getPropertyValue(v).trim(); }).filter(Boolean);
var pieces = [], raf = null;
function size() { canvas.width = stage.clientWidth; canvas.height = stage.clientHeight; }
function burst() {
if (reduced) return;
size();
[0, canvas.width].forEach(function (ox, side) {
var dir = side === 0 ? 1 : -1;
for (var i = 0; i < 70; i++) {
var a = (-58 - Math.random() * 38) * Math.PI / 180;
var sp = 9 + Math.random() * 9;
pieces.push({ x: ox, y: canvas.height + 4, vx: Math.cos(a) * sp * dir, vy: Math.sin(a) * sp, w: 5 + Math.random() * 5, h: 3 + Math.random() * 5, rot: Math.random() * Math.PI, vr: (Math.random() - .5) * .3, c: COLORS[i % COLORS.length], life: 1 });
}
});
if (!raf) tick();
}
function tick() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
pieces = pieces.filter(function (p) { return p.life > 0 && p.y < canvas.height + 30; });
pieces.forEach(function (p) {
p.vy += .22; p.vx *= .99; p.vy *= .995;
p.x += p.vx; p.y += p.vy; p.rot += p.vr;
if (p.y > canvas.height - 90) p.life -= .02;
ctx.save();
ctx.globalAlpha = Math.max(0, Math.min(1, p.life));
ctx.translate(p.x, p.y); ctx.rotate(p.rot);
ctx.fillStyle = p.c;
ctx.fillRect(-p.w / 2, -p.h / 2, p.w, p.h * (0.4 + Math.abs(Math.sin(p.rot * 2)) * .6));
ctx.restore();
});
raf = pieces.length ? requestAnimationFrame(tick) : null;
if (!raf) ctx.clearRect(0, 0, canvas.width, canvas.height);
}
stage.querySelector('[data-conf-fire]').addEventListener('click', burst);
size();
if ('IntersectionObserver' in window && !reduced) {
var io = new IntersectionObserver(function (es) { if (es[0].isIntersecting) { setTimeout(burst, 350); io.disconnect(); } }, { threshold: .5 });
io.observe(stage);
}
});
Installation
CLI
node kata-ui.mjs add confetti-cannon
Writes the part’s CSS and JS into your project. New to the CLI? See the CLI doc.
Manual
- Load
tokens.cssonce, before any part. - Copy the CSS tab into your stylesheet.
- Copy the HTML where it belongs. The part has a script — include it after the markup; it initializes itself.
Usage
For the moments software should celebrate out loud: the deploy, the signup, the invoice marked paid. Two cannons fire from the stage corners in the token palette — even the confetti is on-token. It fires once when it scrolls into view, then waits for the button like a loaded party.
Accessibility
The celebration is visual only: the success it marks must be announced in words by the thing it celebrates ("Payment complete" — the Result part exists for exactly this). Under prefers-reduced-motion the cannon stays unloaded and the button simply says its line.
Anatomy
The public surface of this part, read from its own source. Class names are the API — the CSS tab above is the full contract.
.k-conf | class |
.k-conf-btn | class |
data-conf | data hook |
data-conf-fire | data hook |
aria-hidden | ARIA |
Related parts
Bounce Letters beta
A headline whose letters hop when touched — and do the wave when you click.
Carousel Ring beta
A merry-go-round of cards in real 3D: eight panels riding a ring that never stops turning.
Fireworks beta
Click the night and it blooms: rockets, bursts and falling sparks on one canvas.
Goo Cursor beta
A slime that follows the cursor: blobs with surface tension, courtesy of an SVG goo filter.