Components / KATAYABURI / Fireworks
Fireworks beta
Click the night and it blooms: rockets, bursts and falling sparks on one canvas.
Click anywhere in the sky.
<div class="k-fw" data-fw role="img" aria-label="A fireworks show over a black sky"> <canvas aria-hidden="true"></canvas> <p>Click anywhere in the sky.</p> </div>
/* KATA Fireworks — KATAYABURI. The night, on demand. */
.k-fw{position:relative;width:100%;background:var(--k-zinc-950);border-radius:14px;overflow:hidden;font-family:var(--k-font)}
.k-fw canvas{display:block;width:100%;height:250px;cursor:crosshair}
.k-fw p{position:absolute;left:0;right:0;bottom:12px;text-align:center;font-size:12px;color:var(--k-zinc-400);pointer-events:none}
// KATA Fireworks — rockets rise, burst, and rain sparks.
document.querySelectorAll('[data-fw]').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-amber-ink', '--k-on-color'].map(function (v) { return cs.getPropertyValue(v).trim(); }).filter(Boolean);
var W, H, rockets = [], sparks = [];
function size() { W = canvas.clientWidth; H = canvas.clientHeight; canvas.width = W; canvas.height = H; }
function launch(tx, ty) {
rockets.push({ x: tx != null ? tx : 40 + Math.random() * (W - 80), y: H, ty: ty != null ? ty : 40 + Math.random() * H * .45, vy: -(9.5 + Math.random() * 3), c: COLORS[Math.floor(Math.random() * COLORS.length)] });
}
function burst(r) {
for (var i = 0; i < 96; i++) {
var a = Math.PI * 2 * i / 96 + Math.random() * .1;
var sp = 1.4 + Math.random() * 3.6;
sparks.push({ x: r.x, y: r.ty, vx: Math.cos(a) * sp, vy: Math.sin(a) * sp, life: 1, c: Math.random() < .3 ? COLORS[1] : r.c });
}
}
function tick() {
ctx.fillStyle = 'rgba(9,9,11,.18)';
ctx.fillRect(0, 0, W, H);
rockets = rockets.filter(function (r) {
r.y += r.vy; r.vy += .12;
ctx.fillStyle = r.c; ctx.fillRect(r.x - 1, r.y, 2.4, 7);
if (r.vy >= -1 || r.y <= r.ty) { burst(r); return false; }
return true;
});
sparks = sparks.filter(function (s) { return s.life > 0; });
ctx.globalCompositeOperation = 'lighter';
sparks.forEach(function (s) {
s.vy += .045; s.vx *= .985; s.vy *= .985;
s.x += s.vx; s.y += s.vy; s.life -= .010;
ctx.globalAlpha = Math.max(0, s.life);
ctx.fillStyle = s.c;
ctx.fillRect(s.x, s.y, 3, 3);
});
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 1;
requestAnimationFrame(tick);
}
canvas.addEventListener('pointerdown', function (e) {
var r = canvas.getBoundingClientRect();
launch(e.clientX - r.left, e.clientY - r.top);
});
size();
window.addEventListener('resize', size);
if (!reduced) {
launch(); setTimeout(launch, 500);
setInterval(function () { if (rockets.length < 3 && Math.random() > .35) launch(); }, 1100);
requestAnimationFrame(tick);
} else {
ctx.fillStyle = '#09090b'; ctx.fillRect(0, 0, W, H);
}
});
Installation
CLI
node kata-ui.mjs add fireworks
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
The sky fires on its own every second or so, and any click launches a rocket at that point. Colors come from the tokens, so the show matches the site throwing it. For launches and milestones — and for the one page a year that gets to be a festival.
Accessibility
A light show, labeled as one: the canvas is aria-hidden behind the words that carry the news. Under prefers-reduced-motion the sky stays dark and quiet, and the announcement does the celebrating.
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-fw | class |
data-fw | data hook |
aria-label | ARIA |
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.
Confetti Cannon beta
A button that fires confetti out of both corners of its stage. You already pressed it twice.
Goo Cursor beta
A slime that follows the cursor: blobs with surface tension, courtesy of an SVG goo filter.