JSFiddle - React, Tailwind, and code Playground

HTML

<canvas id='c' style="width:384px;height:216px" width="1920" height="1080"></canvas>

JavaScript

const c = document.getElementById('c')
const x = c.getContext('2d');


function random(seed) {
    var x = Math.sin(seed) * 10000;
    return x - Math.floor(x);
}

const rate = 2;
const lifespan = 1000
const particles = lifespan/rate;
const v_scale = 0.5;
const g = -0.001;
const base = 100;
const size = 80;
function S(x) {return Math.sin(x);}
function C(x) {return Math.cos(x);}

function draw() {
  const t = Date.now();

  // The dwitter version (smoke instead of fire)
  /*
  w=1000
  c.width=w
  x.globalAlpha=.1
  for(y=0;y<w;y++){a=(t+y)%w
  d=(S(t-a)*w%1)*6
  x.fillRect(d%.1*w+(w+C(d)*a)/2,(w+S(d)*a)/2-a*a/w,70,70)}
  */
  
  c.width = c.width;
  x.fillRect(0, 0, c.width, c.height);
  x.globalCompositeOperation='lighter';
  gradient = x.createRadialGradient(size, size, size*0.4, size, size, size);
  gradient.addColorStop(0, '#421')
  gradient.addColorStop(1, '#000')
  x.fillStyle = gradient;
  phase1 = 0;
  phase2 = 0;
  for (var particle=0; particle < particles; particle++ ) {
    age = (t + particle*rate) % lifespan;
    birth = t-age;
    
    // generate a pseudo-random number between 0 and 2pi,seeded by the particle's birth time
    d = Math.sin(birth) * 10000;
    d = (d - Math.floor(d)) * 6.28;
    // particle's velocity at birth
    vx = Math.cos(d) * v_scale;
    vy = Math.sin(d) * v_scale; 
    // position at time t
    i = c.width/2 - 3.14*base + phase1*base + vx * age;
    j = 900 + vy * age + Math.max(g*age*age,-600);
    // decay
    x.globalAlpha=(lifespan-age)/lifespan;
    x.save();
    x.translate(i,j);
    x.fillRect(0, 0, size*2, size*2);
    x.restore();
    phase2 = phase1;
    phase1 = d;
  }
  requestAnimationFrame(draw);
}

requestAnimationFrame(draw);