Components / KATAYABURI / Fireworks

Fireworks beta

Click the night and it blooms: rockets, bursts and falling sparks on one canvas.

Preview
<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

  1. Load tokens.css once, before any part.
  2. Copy the CSS tab into your stylesheet.
  3. 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-fwclass
data-fwdata hook
aria-labelARIA
aria-hiddenARIA

Related parts